1. C99
已經(jīng)是上個世紀的最后一版 C 語言,但由于譚浩強當年沒有跟上,導致我們很多人也沒跟上。
1.1. 語法級
1.1.1. 基本概念
1.1.1.1. 注釋
C99 起支持 C++ 式的雙斜杠單行注釋。
1.1.1.2. 標識符
標識符中可使用由 \u 和 \U 轉(zhuǎn)義的 Unicode 字符。\u 后面為 4 位 16 進制數(shù),\U 后面為 8 位 16 進制數(shù),該 16 進制數(shù)表示一個 Unicode 碼點,如:
int \u6211 = 42;
1.1.2. 預處理
1.1.2.1. 宏
1.1.2.1.1. 宏定義
#define 指令的形參列表尾部可使用變長參數(shù)列表 ...,使用 __VA_ARGS__ 訪問變長參數(shù),如:
#define print1(s, ...) printf(s, 1, __VA_ARGS__)
print1("%d %d %d\n", 2, 3); // printf("%d %d %d\n", 1, 2, 3);
使用 #__VA_ARGS__ 將變長參數(shù)放入一對雙引號中,并用一個逗號與空格隔開(無論代碼如何排版),如:
#define print2(a, b, ...) printf(#__VA_ARGS__, a, b)
print2(1, 2, %d,
%d); // printf("%d, %d", 1, 2);
1.1.2.1.2. 預定義宏
__STDC_VERSION__ 的值為 199901L。
新增宏 __STDC_HOSTED__,當目標環(huán)境為操作系統(tǒng)之下則為 1,目標環(huán)境無操作系統(tǒng)則為 0。
1.1.3. 表達式
1.1.3.1. 字面量
新增整型字面量后綴 ll 或 LL,表示 long long 類型。新增整型字面量后綴 ull 或 Ull 或 uLL 或 ULL,表示 unsigned long long 類型。
新增字符字面量轉(zhuǎn)義符 \u 和 \U。\u 后面為 4 位 16 進制數(shù),\U 后面為 8 位 16 進制數(shù),該 16 進制數(shù)表示一個 Unicode 碼點,如 '\u6211'。
新增復合字面量用以表達數(shù)組、結(jié)構(gòu)體、聯(lián)合體,其形式為 (類型){初始化列表},如:
int i = (int[]){ 1, 2 }[1];
1.1.3.2. 初始化
初始化列表中的項,新增一種可用形式 指派器列表=初始化器。其中指派器列表的形式為數(shù)組的下標表達式 [常量表達式] 或結(jié)構(gòu)體、聯(lián)合體的成員訪問表達式 .標識符。
對于數(shù)組的初始化,可使用指派器列表指定下標,后續(xù)的下標會在此指定值的基礎(chǔ)上依次加 1,如:
int ar[5] = { 1, [4] = 5, [1] = 2, 3 }; // { 1, 2, 3, 0, 5 }
按照初始化列表的順序,后面相同下標的賦值會覆蓋前面,如:
int ar[5] = { [3] = 5, 5, [1] = 2, 3, 4 }; // { 0, 2, 3, 4, 5 }
可用于嵌套數(shù)組,此時指派器列表可以是嵌套的數(shù)組下標表達式,用于指定當前嵌套深度下的數(shù)組下標,如:
int ar[3][3] = { [0] = { 1, [1] = 2, 3 }, { [1] = 5, 6 }, [2][1] = 8, 9 }; // { { 1, 2, 3 }, { 0, 5, 6 }, { 0, 8, 9 } }
對于結(jié)構(gòu)體的初始化,可使用指派器列表指定成員,后續(xù)的成員會依照結(jié)構(gòu)體中成員聲明的順序,后面相同成員的賦值會覆蓋前面,如:
typedef struct S {
int a, b, c, d, e;
} S;
S s = { 1, 1, .e = 5, .b = 2, 3 }; // { 1, 2, 3, 0, 5 }
可用于嵌套結(jié)構(gòu)體,此時指派器列表可以是嵌套的成員訪問表達式,用于指定當前嵌套深度下的成員,如:
typedef struct S {
int a, b, c;
} S;
typedef struct R {
S x, y, z;
} R;
R r = { .x = { 1, .b = 2, 3 }, { .b = 5, 6 }, .z.b = 8, 9 }; // { { 1, 2, 3 }, { 0, 5, 6 }, { 0, 8, 9 } }
1.1.4. 聲明
聲明不再限定于只能出現(xiàn)在塊的開頭。這是 C99 中譚浩強錯過的最重要的一點,以致于到了二十一世紀我們許多人還遵守著這個限制。
1.1.4.1. 限定符
在函數(shù)聲明中,限定符可用于數(shù)組形參的方括號內(nèi),數(shù)組形參會轉(zhuǎn)化為指針形參,限定符轉(zhuǎn)而修飾該指針,如:
int f(int[const]); // int f(int* const)
int g(const int[]); // int g(const int*)
1.1.4.1.1. restrict
新增限定符關(guān)鍵字 restrict 以修飾一個指針類型的左值,提示編譯器在該指針的作用域內(nèi),如果經(jīng)由該指針所訪問的內(nèi)存會被修改,則該內(nèi)存僅經(jīng)由該指針訪問。根據(jù)此限定符,編譯器能作出更大膽的優(yōu)化。
1.1.5. 語句
1.1.5.1. for
for 語句的初始化子句可以是一個聲明,該聲明的作用域為整個循環(huán)體,包括循環(huán)體的條件表達式和迭代表達式,如:
for (int i = 0; i < 42; i++) {
1.1.6. 類型
1.1.6.1. 標量
新增至少 64 位的有符號整型 long long 和無符號整型 unsigned long long。
新增算術(shù)類型關(guān)鍵字 _Bool,該類型的值只能是 0 或 1。通常使用頭文件 stdbool.h 中定義的宏 bool、true、false。所有非零值的標量轉(zhuǎn)化為 _Bool 類型后其值為 1,零值的標量轉(zhuǎn)化為 _Bool 類型后其值為 0,注意這與轉(zhuǎn)化為其他某種整型時的區(qū)別,特別是將其他某種整型用作布爾類型時,如:
#include <stdbool.h>
bool b1 = false; // _Bool b1 = 0;
bool b2 = NULL; // 0
bool b3 = 0.5; // 1
#define Bool unsigned char
Bool b4 = 0.5; // 0
1.1.6.2. 數(shù)組
新增變長數(shù)組。聲明一個數(shù)組時如果長度不是一個常量,則聲明為變長數(shù)組,如:
void f(int i) {
int ar[i];
變長數(shù)組的聲明不能使用初始化列表。變長數(shù)組的長度在運行時確定,內(nèi)存在運行時分配,長度在其生命周期內(nèi)不可變。變長數(shù)組的生命周期只能在函數(shù)塊內(nèi)部,不能作為全局變量、結(jié)構(gòu)體和聯(lián)合體的成員。
變長數(shù)組作為函數(shù)形參時,在函數(shù)原型聲明中,使用 * 作為數(shù)組長度,該數(shù)組同樣會轉(zhuǎn)化為元素類型的指針,如:
void f(int[*]); // void f(int*)
變長組數(shù) T[n] 的 sizeof 操作仍是計算整個數(shù)組的長度,即 sizeof(T) * n,但是在運行時計算,因此不再是常量。
1.1.6.3. 枚舉
允許枚舉聲明中的最后一項的后面出現(xiàn)逗號。
1.1.6.4. 結(jié)構(gòu)體
新增結(jié)構(gòu)體柔性數(shù)組成員。柔性數(shù)組成員只能作為不完整數(shù)組類型而聲明在最后,如:
typedef struct S {
char c;
int ar[];
} S;
S *s = (S*)malloc(sizeof(S) + sizeof(int) * 3);
s->ar[2] = 42;
結(jié)構(gòu)體的初始化列表、sizeof 操作符、賦值操作符會忽略柔性數(shù)組成員。包含柔性數(shù)組成員的結(jié)構(gòu)體不能作為數(shù)組元素和結(jié)構(gòu)體成員。
結(jié)構(gòu)體位域字段可使用 _Bool 類型,寬度只能為 1,如:
struct S {
_Bool b: 1;
};
1.1.7. 函數(shù)
新增函數(shù)域內(nèi)的預定義靜態(tài)局部變量 static const char __func__[],內(nèi)容為當前函數(shù)名。
1.2. 標準庫級
1.2.1. 類型
頭文件
<stdbool.h>
定義了宏 bool、true、false。true 和 false 是整型常量 1 和 0。
頭文件
<stdint.h>
定義了確定長度的整型 typedef int8_t、int16_t、int32_t、int64_t、intptr_t、uint8_t、uint16_t、uint32_t、uint64_t、uintptr_t 等。
1.2.2. 數(shù)值計算
新增大量數(shù)值計算函數(shù)。
2. C11
和 C++ 同年推出的新世紀第一個 C 語言新標準,譚浩強更加跟不上了。
2.1. 語法級
2.1.1. 基本概念
2.1.1.1. 對齊
新增操作符關(guān)鍵字 _Alignof 查詢類型的對齊,返回類型為 size_t,返回值為常量,如:
typedef struct S {
int i;
char c;
} S;
_Alignof(S) // 4;
新增關(guān)鍵字 _Alignas 以聲明對齊,其形式為 _Alignas(整型常量表達式或類型),對齊為整型常量表達式的值或類型的 _Alignof 的值,如:
typedef struct S {
_Alignas(16) char s[42];
};
2.1.2. 預處理
2.1.2.1. 宏
__STDC_VERSION__ 的值為 201112L。
新增宏 __STDC_UTF_16__,當 char16_t 使用 UTF-16 編碼則為 1。
新增宏 __STDC_UTF_32__,當 char32_t 使用 UTF-32 編碼則為 1。
新增宏 __STDC_ANALYZABLE__,當編譯器支持可分析性則為 1。
新增宏 __STDC_LIB_EXT1__,當標準庫包含帶邊界檢查的特定 API 則為 201112L。
新增宏 __STDC_NO_ATOMICS__,當編譯器不支持原子類型且標準庫不包含原子類型 API 則為 1。
新增宏 __STDC_NO_COMPLEX__,當編譯器不支持復數(shù)類型且標準庫不包含復數(shù)類型 API 則為 1。
新增宏 __STDC_NO_VLA__,當編譯器不支持變長數(shù)組則為 1。
2.1.3. 表達式
2.1.3.1. 字面量
新增字符字面量前綴 u,字符類型為 char16_t,通常是 UTF-16 編碼。如 u'我' 即 (char16_t)0x6211,若字符的 Unicode 碼點對應的 UTF-16 編碼超出單個編碼單元,則依照編譯器的具體實現(xiàn)。
新增字符字面量前綴 U,字符類型為 char32_t,通常是 UTF-32 編碼。如 U'我' 即 (char32_t)0x00006211,若字符的 Unicode 碼點對應的 UTF-32 編碼超出單個編碼單元(這都能超過?),則依照編譯器的具體實現(xiàn)。
新增字符串字面量前綴 u8,字符類型為 char,UTF-8 編碼。如 u"是我" 即 (char[]){ 0xe6, 0x98, 0xaf, 0xe6, 0x88, 0x91, 0 }。
新增字符串字面量前綴 u,字符類型為 char16_t,通常是 UTF-16 編碼。如 u"是我" 即 (char16_t[]){ 0x662f, 0x6211, 0 }。
新增字符串字面量前綴 U,字符類型為 char32_t,通常是 UTF-32 編碼。如 U"是我" 即 (char32_t[]){ 0x0000662f, 0x00006211, 0 }。
2.1.3.2. 泛型選擇表達式
新增關(guān)鍵字 _Generic 以提供泛型選擇表達式,其形式為 _Generic(控制表達式, 關(guān)聯(lián)列表)。關(guān)聯(lián)列表中的關(guān)聯(lián)項由逗號分隔,關(guān)聯(lián)項的形式為 類型名: 表達式 或 default: 表達式,各表達式不能為逗號表達式??刂票磉_式會經(jīng)歷左值轉(zhuǎn)換(去掉頂層的類型限定符、數(shù)組到指針、函數(shù)到指針)但不會求值,轉(zhuǎn)換后的類型與各關(guān)聯(lián)項中的類型名進行匹配,返回匹配到的關(guān)聯(lián)項中的表達式,若無 default 項且無匹配項則編譯出錯。如:
int i = 42;
j = _Generic(i,
char: i + 1,
int: i + 2,
default: i + 3
); // 44
2.1.3.3. 靜態(tài)斷言
新增關(guān)鍵字 _Static_assert 以提供靜態(tài)斷言,其形式為 _Static_assert(整型常量表達式, 字符串字面量),靜態(tài)斷言在編譯時進行,當整型常量表達式的值為 0 則發(fā)生編譯錯誤,字符串字面量會出現(xiàn)在錯誤信息中。
2.1.4. 聲明
2.1.4.1. 存儲期說明符
新增關(guān)鍵字 _Thread_local 以聲明變量的存儲期為新增的線程存儲期。線程存儲期為該線程的整個執(zhí)行過程,變量在線程啟動時初始化。不能用于函數(shù)聲明。用于在塊作用域中聲明時,必須與 static 或 extern 存儲期組合。
2.1.4.2. 原子類型
新增關(guān)鍵字 _Atomic 以聲明變量為原子類型。
不能用于數(shù)組和函數(shù)聲明,如:
typedef int pair[2];
// _Atomic pair p;
但可聲明元素為原子類型的數(shù)組,如:
_Atomic int p[2];
2.1.5. 類型
2.1.5.1. 標量
新增 16 位和 32 位無符號字符類型 char16_t 和 char32_t,通常是 typedef。
2.2. 標準庫級
2.2.1. 線程
新增線程支持庫
頭文件
<threads.h>
新增線程、線程局部存儲和同步原語操作函數(shù)。
頭文件
<stdatomic.h>
新增原子操作函數(shù)。