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 forsuitable m4... configure: error: No usable m4 in $PATH or /usr/5bin (see config.log for reasons).
不用怕,那就繼續安裝 m4
m4:ftp://ftp.gnu.org/gnu/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標準研究部分就到這裏了. 說真的, 新手別入坑. 歡迎指針錯誤, 最後引述一個大佬的話
C11 標準特性研究