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.0+ and MPC 0.8.0+.
说白了缺少上面 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
开始执行命令跑起来。
cd gmp-6.1.2/
mkdir gmp-6.1.2-build
cd gmp-6.1.2-build
../configure
我们如果出现
checking for suitable m4... configure: error: No usable m4 in $PATH or /usr/5bin (see config.log for reasons).
不用怕,那就继续安装 m4
cd m4-1.4.18 mkdir m4-1.4.18-build cd m4-1.4.18-build ../configure make sudo make install
那继续安装 GMP。
cd ../../gmp-6.1.2 /gmp-6.1.2-build ../configure make sudo make install
随后就是 MPFR
cd ../../mpfr-3.1.6 mkdir mpfr-3.1.6-build cd mpfr-3.1.6-build ../configure make sudo make install
然后就是 MPC
cd ../../mpc-1.0.3 mkdir mpc-1.0.3-build cd mpc-1.0.3-build ../configure make sudo make install
最后还是回到我们的 gcc
cd ../../gcc-7.2.0 mkdir gcc-7.2.0-build cd gcc-7.2.0-build ../configure
又是不好意思,提示下面错误信息
configure: error: I suspect your system does not have 32-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 64-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:11923: recipe for target 'configure-stage1-zlib' failed make[2]: *** [configure-stage1-zlib] Error 1 make[2]: Leaving directory '/home/wangzhi/桌面/gcc-7.2.0/gcc-7.2.0-build' Makefile:23803: recipe for target 'stage1-bubble' failed make[1]: *** [stage1-bubble] Error 2 make[1]: Leaving directory '/home/wangzhi/桌面/gcc-7.2.0/gcc-7.2.0-build' Makefile:933: recipe for target 'all' failed make: *** [all] Error 2
没关系我们继续搞,存在首次安装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
见过漫长的等待,下面就是见证历史奇迹的时候了。
到这里关于 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 = { 1 }; 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); }
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[] = { 1, 2, 5, 3, 4, 11, 23, 34, 33, 55, 11, 12 }; int i, len = sizeof a / sizeof (*a); sort_insert(a, len); for (i = 0; i < len; ++i) printf("%2d ", a[i]); putchar('\n'); return 0; } #define sort_insert_definition(T) \ void \ sort_insert_##T (T a[], int len) { \ int i, j; \ for (i = 1; i < len; ++i) { \ T key = a[j = i]; \ while (j > 0 && a[j - 1] < key) { \ a[j] = a[j - 1]; \ --j; \ } \ a[j] = key; \ } \ } sort_insert_definition(int) sort_insert_definition(float) sort_insert_definition(double)
最终输出结果如下
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 0; }
其实本质等同于, 真的有点鸡肋
#if __STDC_VERSION__ >= 201112L # error "It is c11 version" #endif
5、安全版本的几个函数
gets_s()取代了gets(),原因是后者这个I/O函数的实际缓冲区大小不确定,
以至于发生常见的缓冲区溢出攻击,类似的函数还有其它的。
_Success_(return != 0) _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_(6) 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_(6) 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ß水🍌" setlocale(LC_ALL, "en_US.utf8"); len = strlen(str); printf("Processing %zu bytes: [ ", len); for (i = 0; i < len; ++i) { // 强转是画龙点睛之笔 printf("0x%x ", (unsigned char)str[i]); } printf("]\n"); size_t rc; char16_t c16; mbstate_t state = { 0 }; const char * ptr = str, * end = str + len; /* // // mbrtoc16 : UTF-8 转换为 UTF-16 // _Pc16 - 指向将写入产生的 16-bit 宽字符的位置的指针 // _S - 指向用作输入的多字节字符串的指针 // _N - 能检验的字节数上的限制 // _Ps - 指向转译多字节字符串时所用转换状态对象的指针 // return : // 若从 s 转换的字符是空字符(并存储于 *pc16 ,若它非空),则为 0 。 // 成功从 s 转换的多字节字符的字节数 [1...n] 。 // 若下个 char16_t 组成多 char16_t 字符,并已写入 *pc16 ,则为 -3 。此情况下不从输入处理字节。 // 若接下来 n 个字符组成不完整,但到此还合法的多字节字符,则为 -2 。不写入 *pc16 。 // 若出现编码错误则为 -1 。不写入 *pc16 ,存储值 EILSEQ 于 errno ,且 *ps 状态未指定。 // size_t __cdecl mbrtoc16(char16_t * _Pc16, const char * _S, size_t _N, mbstate_t * _Ps); */ while ((rc = mbrtoc16(&c16, ptr, end - ptr + 1, &state))) { printf("Next UTF-16 char: 0x%x obtained from ", c16); if (rc == (size_t)-3) printf("earlier surrogate pair\n"); else if (rc <= ((size_t)-1) / 2) { printf("%zu bytes [ ", rc); for (i = 0; i < rc; ++i) printf("0x%x ", (unsigned char)ptr[i]); printf("]\n"); ptr += rc; } else break; } return 0; }
最终输出结果
11、quick_exit()
又一种终止程序的方式,当exit()失败时用以终止程序。对于退出操作, 目前有这几种
abort -> quick_exit -> exit , abort 直接退出什么都不管. exit 会先执行 atexit 注册的操作,
随后各种额外处理, 刷新缓冲区, 刷新文件描述符. quick_exit 处理流程非常简单, 先执行 at_quick_exit
注册函数随后交给 _exit 直接退出, 但是如果 at_quick_exit 中调用了 exit, 则行为是未定义.
12、复数宏,浮点数宏。
新的标准添加了创建复数宏, 主要是相对 C99 的, 例如 complex.h 中多了些复数操作
相关的宏
#define complex _Complex /* Narrowest imaginary unit. This depends on the floating-point evaluation method. XXX This probably has to go into a gcc related file. */ #define _Complex_I (__extension__ 1.0iF) /* Another more descriptive name is `I'. XXX Once we have the imaginary support switch this to _Imaginary_I. */ #undef I #define I _Complex_I #if defined __USE_ISOC11 && __GNUC_PREREQ (4, 7) /* Macros to expand into expression of specified complex type. */ # define CMPLX(x, y) __builtin_complex ((double) (x), (double) (y)) # define CMPLXF(x, y) __builtin_complex ((float) (x), (float) (y)) # define CMPLXL(x, y) __builtin_complex ((long double) (x), (long double) (y)) #endif
对于浮点数处理宏多了一些 (More macros for querying the characteristics of floating point types,
concerning subnormal floating point numbers and the number of decimal digits the type is able to store)
上面是摘自 C11 标准中文字, 我们简单的以 GCC 对于 float.h 中一些实现
#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L /* Versions of DECIMAL_DIG for each floating-point type. */ #undef FLT_DECIMAL_DIG #undef DBL_DECIMAL_DIG #undef LDBL_DECIMAL_DIG #define FLT_DECIMAL_DIG __FLT_DECIMAL_DIG__ #define DBL_DECIMAL_DIG __DBL_DECIMAL_DIG__ #define LDBL_DECIMAL_DIG __DECIMAL_DIG__ /* Whether types support subnormal numbers. */ #undef FLT_HAS_SUBNORM #undef DBL_HAS_SUBNORM #undef LDBL_HAS_SUBNORM #define FLT_HAS_SUBNORM __FLT_HAS_DENORM__ #define DBL_HAS_SUBNORM __DBL_HAS_DENORM__ #define LDBL_HAS_SUBNORM __LDBL_HAS_DENORM__ /* Minimum positive values, including subnormals. */ #undef FLT_TRUE_MIN #undef DBL_TRUE_MIN #undef LDBL_TRUE_MIN #define FLT_TRUE_MIN __FLT_DENORM_MIN__ #define DBL_TRUE_MIN __DBL_DENORM_MIN__ #define LDBL_TRUE_MIN __LDBL_DENORM_MIN__ #endif /* C11 */
13、time.h新增timespec结构体,时间单位为纳秒,原来的timeval结构体时间单位为毫秒。
关于这个特别爽, 特别是引入了 timespec_get 谁用谁知道.
#ifndef _CRT_NO_TIME_T struct timespec { time_t tv_sec; // Seconds - >= 0 long tv_nsec; // Nanoseconds - [0, 999999999] }; #endif // The number of clock ticks per second #define CLOCKS_PER_SEC ((clock_t)1000) #define TIME_UTC 1 _Check_return_ static __inline int __CRTDECL timespec_get( _Out_ struct timespec* const _Ts, _In_ int const _Base ) { return _timespec64_get((struct _timespec64*)_Ts, _Base); }
不妨写个 demo
#include <stdio.h> #include <time.h> // // struct timespec heoo // int main(void) { struct tm tm; struct timespec ts; timespec_get(&ts, TIME_UTC); printf("time_t tv_sec = %lld, long tv_nsec = %d.\n", (long long)ts.tv_sec, ts.tv_nsec); // 线程不安全, 单纯为了测试 tm = *localtime(&ts.tv_sec); printf("now %s", asctime(&tm)); return 0; }
最终结果
后记 - 一切刚刚开始
关于 C11标准研究部分就到这里了. 说真的, 新手别入坑. 欢迎指针错误, 最后引述一个大佬的话