C11 标准特性研究

时间:2023-03-09 14:37:33
C11 标准特性研究

前言 - 需要点开头

  C11标准是C语言标准的第三版(2011年由ISO/IEC发布),前一个标准版本是C99标准。

相比C99,C11有哪些变化呢!!所有的测试全部基于能够和标准贴合的特性平台. 但是绝大部

分来源于 GCC. 这里不妨教大家源码安装最新的GCC吧。

a. 首先去 GNU GCC官网下载最新的 GCC 源码

  GCC  : https://gcc.gnu.org/

下载最新源码, 安装过程中可能提示下面这句话

configure: error: Building GCC requires GMP 4.2+, MPFR 2.4.+ and MPC 0.8.+.

说白了缺少上面 GMP,MPFR,MPC 三个组件。 那么开始下载

  GMP  : ftp://ftp.gnu.org/gnu/gmp/

  MPFR: http://www.mpfr.org/mpfr-current/

  MPC  : ftp://ftp.gnu.org/gnu/mpc/

b. 开始挨个解压安装 GMP → MPFR → MPC → GCC

C11 标准特性研究

开始执行命令跑起来。

cd gmp-6.1./
mkdir gmp-6.1.-build
cd gmp-6.1.-build
../configure

我们如果出现

checking for suitable m4... configure: error: No usable m4 in $PATH or /usr/5bin (see config.log for reasons).

不用怕,那就继续安装 m4

  m4ftp://ftp.gnu.org/gnu/m4/

cd m4-1.4.

mkdir m4-1.4.-build

cd m4-1.4.-build

../configure

make

sudo make install

那继续安装 GMP。

cd ../../gmp-6.1. /gmp-6.1.-build

../configure

make

sudo make install

随后就是 MPFR

cd ../../mpfr-3.1.

mkdir mpfr-3.1.-build

cd mpfr-3.1.-build

../configure

make

sudo make install

然后就是 MPC

cd ../../mpc-1.0.

mkdir mpc-1.0.-build

cd mpc-1.0.-build

../configure

make

sudo make install

最后还是回到我们的 gcc

cd ../../gcc-7.2.

mkdir gcc-7.2.-build

cd gcc-7.2.-build

../configure

又是不好意思,提示下面错误信息

configure: error: I suspect your system does not have -bit development libraries (libc and headers). 
If you have them, rerun configure with --enable-multilib. If you do not have them,
and want to build a -bit-only compiler, rerun configure with –disable-multilib.

继续

../configure --enable-multilib

make

不好意思又来了

checking dynamic linker characteristics... configure: error: Link tests are not allowed after GCC_NO_EXECUTABLES.
Makefile:: recipe for target 'configure-stage1-zlib' failed
make[]: *** [configure-stage1-zlib] Error
make[]: Leaving directory '/home/wangzhi/桌面/gcc-7.2.0/gcc-7.2.0-build'
Makefile:: recipe for target 'stage1-bubble' failed
make[]: *** [stage1-bubble] Error
make[]: Leaving directory '/home/wangzhi/桌面/gcc-7.2.0/gcc-7.2.0-build'
Makefile:: recipe for target 'all' failed
make: *** [all] Error

没关系我们继续搞,存在首次安装GCC不彻底污染问题,清理后继续安装

make distclean

../configure –enable-multilib

make

还是不行更换思路, 走插件全安装

sudo apt-get install gawk

sudo apt-get install gcc-multilib
sudo apt-get install binutils
sudo apt-get install lzip make distclean ../configure make sudo make install

见过漫长的等待,下面就是见证历史奇迹的时候了。

C11 标准特性研究

到这里关于 GCC 升级到最新版本问题以及搞定。

正文  -  C11标准特性研究

1对齐处理

  alignof(T)返回T的对齐方式,aligned_alloc()以指定字节和对齐方式分配内存,头文件<stdalign.h>

定义了这些内容。我们首先看看 stdalign.h 中定义

/* ISO C1X: 7.15 Alignment <stdalign.h>.  */

#ifndef _STDALIGN_H
#define _STDALIGN_H #ifndef __cplusplus #define alignas _Alignas
#define alignof _Alignof #define __alignas_is_defined 1
#define __alignof_is_defined 1 #endif #endif /* stdalign.h */

alignas 设置内存的对其方式, alignof 返回内存的对其方式。

Aligned.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdalign.h> #define _INT_NAME (128) struct names {
int len;
char name[];
}; struct people {
int id;
alignas(struct names) char name[sizeof(struct names) + _INT_NAME];
}; static void test_aligned(void) {
printf("sizeof(struct people) = %zu.\n", sizeof(struct people)); // 控制内存布局
struct people pe = { };
struct names * name = (struct names *)pe.name;
name->len = _INT_NAME;
strcpy(name->name, "你好吗?");
printf("people len = %d, name = %s.\n", pe.id, name->name); // 测试内存对其
printf("alignof(struct people) = %zu.\n", alignof(struct people)); // 接着控制内存布局
alignas(struct names) char xname[sizeof(struct names) + _INT_NAME];
struct names * xna = (struct names *)xname;
strcpy(xna->name, "我还行!"); //
// 另一种内存申请, 一种演示, malloc已经够额
// aligned_alloc 相比 malloc 多了第一个参数, 这个参数必须是2的幂
// 在特定嵌入式平台会使用
//
void * ptr = aligned_alloc(alignof(struct names), _INT_NAME);
if (NULL == ptr)
exit(EXIT_FAILURE);
free(ptr);
}

C11 标准特性研究

2 _Noreturn

  _Noreturn是个函数修饰符,位置在函数返回类型的前面,声明函数无返回值,

有点类似于gcc的__attribute__((noreturn)),后者在声明语句尾部。

#include <stdio.h>
#include <stdlib.h> _Noreturn static void _test(void) {
puts("func _test C11 never returns");
abort();
} int main(int argc, char * argv[]) {
_test();
}

3 _Generic

  _Generic支持轻量级范型编程,可以把一组具有不同类型而却有相同功能的函数抽象为一个接口。

#include <stdio.h>

void sort_insert_int(int a[], int len);
void sort_insert_float(float a[], int len);
void sort_insert_double(double a[], int len); #define sort_insert(a, len) \
_Generic(a, \
int * : sort_insert_int, \
float * : sort_insert_float, \
double * : sort_insert_double)(a, len) //
// file : generic.c
// test : C11 泛型用法
//
int main(int argc, char * argv[]) {
int a[] = { , , , , , , , , , , , };
int i, len = sizeof a / sizeof (*a); sort_insert(a, len); for (i = ; i < len; ++i)
printf("%2d ", a[i]);
putchar('\n'); return ;
} #define sort_insert_definition(T) \
void \
sort_insert_##T (T a[], int len) { \
int i, j; \
for (i = ; i < len; ++i) { \
T key = a[j = i]; \
while (j > && a[j - ] < key) { \
a[j] = a[j - ]; \
--j; \
} \
a[j] = key; \
} \
} sort_insert_definition(int)
sort_insert_definition(float)
sort_insert_definition(double)

最终输出结果如下

C11 标准特性研究

4 _Static_assert()

  _Static_assert(),静态断言,在编译时刻进行,断言表达式必须是在编译时期可以计算的表达式,

而普通的assert()在运行时刻断言。

#include <stdio.h>

int main(void) {
printf("C version : %ld.\n", __STDC_VERSION__); _Static_assert(__STDC_VERSION__ < 201112L, "It is c11 version"); return ;
}

C11 标准特性研究

其实本质等同于, 真的有点鸡肋

#if __STDC_VERSION__ >= 201112L
# error "It is c11 version"
#endif

5、安全版本的几个函数

  gets_s()取代了gets(),原因是后者这个I/O函数的实际缓冲区大小不确定,

以至于发生常见的缓冲区溢出攻击,类似的函数还有其它的。

_Success_(return != )
_ACRTIMP char* __cdecl gets_s(
  _Out_writes_z_(_Size) char* _Buffer,
  _In_ rsize_t _Size
  );

目前在 VS 中有这个函数实现. C11 废弃了 gets, 这里是最接近的 api, 相比 fgets 它不会记录最后一个 '\n'.

并且会在最后一个字符添加 '\0'. 其中 rsize_t 和 size_t 类型是一样的, 但是

#if __STDC_WANT_SECURE_LIB__
typedef size_t rsize_t;
#endif #if __STDC_WANT_SECURE_LIB__
#ifndef RSIZE_MAX
#define RSIZE_MAX (SIZE_MAX >> 1)
#endif
#endif

也就是 gets_s 第二参数合法区间就是 [1, RSIZE_MAX], 否则它会什么都不做.

6 fopen() 新模式

  fopen() 增加了新的创建、打开模式“x”,在文件锁中比较常用。类似 POSIX 中的

O_CREAT | O_EXCL. 文件已存在或者无法创建(一般是路径不正确)都会导致 fopen

失败。文件以操作系统支持的独占模式打开。可惜的是当前 CL or GCC 都没有提供支持.

主要原因是 glibc 没有提供支持!

7匿名结构体、联合体。

  例如下面这样, 直接 struct cjson::vs 这种访问. 一种语法层面优化.

struct cjson {
struct cjson * next;
struct cjson * child; unsigned char type;
char * key;
union {
char * vs;
double vd;
};
};

8多线程

  头文件<threads.h>定义了创建和管理线程的函数,新的存储类修饰符_Thread_local限定了变

量不能在多线程之间共享。只能等待 glibc 去支持, 单纯而言可以将 pthread 引入标准线程库.

_Thread_local 等价于线程 pthread_key_t 的私有变量, 不是特别适合不推荐使用.

9 _Atomic类型修饰符和头文件<stdatomic.h>

  原子操作也算是 C11 看着 C++11 急眼了, 直接引入的类型. 把编译器提供的特性纳入标准中.

同样支持的很一般般. 但是可以一用. 展示一种最简单的自旋锁写法:

include <stdatomic.h>

// 标记类型, init lock
atomic_flag flag = ATOMIC_FLAG_INIT; // 尝试设置占用(原子操作), try lock
atomic_flag_test_and_set(&flag); // 释放(原子操作), unlock
atomic_flag_clear(&flag);

10、改进的Unicode支持和头文件<uchar.h>

  提供了utf-8和 utf-16, utf-32 字符之间转换. 其中 uchar.h 在 winds 一种实现如下:

//
// uchar.h
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// #pragma once
#define _UCHAR #include <corecrt.h> _CRT_BEGIN_C_HEADER #define __STDC_UTF_16__
#define __STDC_UTF_32__ typedef unsigned short _Char16_t;
typedef unsigned int _Char32_t; #if !defined __cplusplus || (defined _MSC_VER && _MSC_VER < 1900)
typedef unsigned short char16_t;
typedef unsigned int char32_t;
#endif _Check_return_ _ACRTIMP size_t __cdecl mbrtoc16(_Out_opt_ char16_t *_Pc16, _In_reads_or_z_opt_(_N) const char *_S, _In_ size_t _N, _Inout_ mbstate_t *_Ps);
_Check_return_ _ACRTIMP size_t __cdecl c16rtomb(_Out_writes_opt_() char *_S, _In_ char16_t _C16, _Inout_ mbstate_t *_Ps); _Check_return_ _ACRTIMP size_t __cdecl mbrtoc32(_Out_opt_ char32_t *_Pc32, _In_reads_or_z_opt_(_N) const char *_S, _In_ size_t _N, _Inout_ mbstate_t *_Ps);
_Check_return_ _ACRTIMP size_t __cdecl c32rtomb(_Out_writes_opt_() char *_S, _In_ char32_t _C32, _Inout_ mbstate_t *_Ps); _CRT_END_C_HEADER /*
* Copyright (c) 1992-2013 by P.J. Plauger. ALL RIGHTS RESERVED.
* Consult your license regarding permissions and restrictions.
V6.40:0009 */

使用起来也很简单.

#include <stdio.h>
#include <uchar.h>
#include <locale.h>
#include <string.h> //
// uchar test
//
int main(int argc, char * argv[]) {
size_t i, len;
const char * str = u8"z\u00df\u6c34\U0001F34C"; // 或 u8"zß水