C11標準是C語言標準的第三版(2011年由ISO/IEC發佈),前一個標準版本是C99標準。相比C99,C11有哪些變化呢
1、 對齊處理
alignof(T)返回T的對齊方式,aligned_alloc()以指定字節和對齊方式分配內存,頭文件<stdalign.h>
定義了這些內容。
alignof( 類型標識 )
返回 std::size_t 類型值。
返回由類型標識所指示的類型的任何實例所要求的對齊字節數,該類型可以爲完整類型、數組類型或者引用類型。
若類型爲引用類型,則運算符返回被引用類型的對齊;若類型爲數組類型,則返回元素類型的對齊要求。
sizeof和alignof對於一般的數據類型返回值是相同的,但是對於下面情況特別:
struct Foo {
int a;
float b;
char c;
};
alignof(Foo) //值爲4,對齊長度
sizeof(Foo) //結構體的總大小:12
void *aligned_alloc( size_t alignment, size_t size );
分配 size 字節未初始化的存儲空間,按照 alignment 指定對齊。 size 參數必須是 alignment 的整數倍。
aligned_alloc 是線程安全的:它表現得如同只訪問通過其參數可見的內存區域,而非任何靜態存儲。
令 free 或 realloc 歸還一塊內存區域的先前調用,同步於令 aligned_alloc 分配相同或部分相同的內存區域的調用。此同步出現於任何解分配函數所做的內存訪問之後,和任何通過 aligned_alloc 所做的內存訪問之前。所有操作每塊特定內存區域的分配及解分配函數擁有單獨全序。
傳遞不是 alignment 整數倍的 size ,或傳遞實現不支持的 alignment ,會令函數失敗並返回空指針(出版的 C11 指定此爲未定義行爲,這已經爲 DR 460 所更正)
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p1 = malloc(10*sizeof *p1);
printf("default-aligned addr: %p\n", (void*)p1);
free(p1);
int *p2 = aligned_alloc(1024, 10*sizeof *p2);
printf("1024-byte aligned addr: %p\n", (void*)p2);
free(p2);
}
default-aligned addr: 0x17d6010
1024-byte aligned addr: 0x17d6400
2、 _Noreturn
_Noreturn是個函數修飾符,位置在函數返回類型的前面,聲明函數無返回值,有點類似於gcc的attribute((noreturn)),後者在聲明語句尾部。
_Noreturn function_declaration
_Noreturn 關鍵詞出現於函數聲明中,指定函數不會由於執行到 return 語句或抵達函數體結尾而返回(可通過執行 longjmp 返回)。若聲明 _Noreturn 的函數返回,則行爲未定義。若編譯器能檢測此錯誤,則推薦編譯器診斷。
_Noreturn 指定符可以在同一函數聲明中出現多於一次,行爲與只出現一次相同。
此指定符通常通過便利宏 noreturn 使用,該宏於頭文件 stdnoreturn.h 提供。
示例:
#include <stdlib.h>
#include <stdio.h>
#include <stdnoreturn.h>
// 在 i <= 0 時導致未定義行爲
// 在 i > 0 時退出
noreturn void stop_now(int i) // 或 _Noreturn void stop_now(int i)
{
if (i > 0) exit(i);
}
int main(void)
{
puts("Preparing to stop...");
stop_now(2);
puts("This code is never executed.");
}
輸出:
Preparing to stop...
3、 _Generic
_Generic支持輕量級範型編程,可以把一組具有不同類型而卻有相同功能的函數抽象爲一個接口。
_Generic ( controlling-expression , association-list )
其中 association-list 是逗號分隔的關聯列表,每個關聯擁有語法:
type-name : expression
default : expression
_Generic. 這個關鍵字類似與switch語法 :_Generic( ‘a’, char: 1, int: 2, long: 3, default: 0) 輸出爲2 (字符在C中爲整型).
4、 _Static_assert()
_Static_assert(),靜態斷言,在編譯時刻進行,斷言表達式必須是在編譯時期可以計算的表達式,而普通的assert()在運行時刻斷言。
_Static_assert ( 表達式 , 消息 ) (C11 起)
表達式 - 任何整數常量表達式
消息 - 任何字符串字面量
示例:
#include <assert.h>
int main(void)
{
// 測試數學是否正常工作
static_assert(2 + 2 == 4, "Whoa dude!"); // 或 _Static_assert(...
// 這會在編譯時產生錯誤。
static_assert(sizeof(int) < sizeof(char),
"this program requires that int is less than char");
}
5、安全版本的幾個函數
gets_s()取代了gets(),原因是後者這個I/O函數的實際緩衝區大小不確定,以至於發生常見的緩衝區溢出攻擊,類似的函數還有其它的。
char *gets( char *str );
從 stdin 讀入 str 所指向的字符數組,直到發現換行符或出現文件尾。在讀入數組的最後一個字符後立即寫入空字符。換行符被捨棄,但不會存儲於緩衝區中。
char *gets_s( char *str, rsize_t n );
從 stdin 讀取字符直到發現換行符或出現文件尾。至多寫入 n-1 個字符到 str 所指向的數組,並始終寫入空終止字符(除非 str 是空指針)。若發現換行符,則忽略它並且不將它計入寫入緩衝區的字符數。
在運行時檢測下列錯誤,並調用當前安裝的制約處理函數:
n 爲零
n 大於 RSIZE_MAX
str 是空指針
在存儲 n-1 個字符到緩衝區後沒有遇到換行符或文件尾。
6、 fopen()新模式
fopen()增加了新的創建、打開模式“x”,在文件鎖中比較常用。
以x結尾的模式爲獨佔模式,文件已存在或者無法創建(一般是路徑不正確)都會導致fopen失敗.文件以操作系統支持的獨佔模式打開.[C11]
7、 匿名結構體、聯合體。
在 C 語言中,可以在結構體中聲明某個聯合體(或結構體)而不用指出它的名字,如此之後就可以像使用結構體成員一樣直接使用其中聯合體(或結構體)的成員。
示例:
#include <stdio.h>
struct person
{
char *name;
char gender;
int age;
int weight;
struct
{
int area_code;
long phone_number;
};
};
int main(void)
{
struct person jim = {"jim", 'F', 28, 65, {21, 58545566}};
printf("%d\n", jim.area_code);
}
如果不使用匿名結構體,則上述例子對應的代碼如下:
#include <stdio.h>
struct phone
{
int area_code;
long phone_number;
};
struct person
{
char *name;
char gender;
int age;
int weight;
struct phone office;
};
int main(void)
{
struct person jim = {"jim", 'F', 28, 65, {21, 58545566}};
printf("%d\n", jim.office.area_code);
}
對比上述兩個例子可以看出:
使用匿名結構體,結構體對象 jim 可以通過 jim.area_code 直接訪問匿名結構體成員變量 area_code,代碼相對比較簡潔
反之則必須通過 jim.office.area_code 來訪問結構體成員變量
8、 多線程
頭文件<threads.h>
定義了創建和管理線程的函數,新的存儲類修飾符_Thread_local限定了變量不能在多線程之間共享。
_Thread_local 指示線程存儲期。它不能用於函數聲明。若將它用在對象聲明上,則它必須在同一對象的每次聲明上都存在。若將它用在塊作用域聲明上,則必須與 static 或 extern 之一組合以決定鏈接。
線程存儲期。存儲期是創建對象的線程的整個執行過程,在啓動線程時初始化存儲於對象的值。每個線程擁有其自身的相異對象。若執行訪問此對象的表達式的線程,不是執行其初始化的線程,則行爲是實現定義的。所有聲明爲 _Thread_local 的對象擁有此存儲期。
9、 _Atomic類型修飾符和頭文件<stdatomic.h>
。
語法
_Atomic ( type-name ) (1) (C11 起)
_Atomic type-name (2) (C11 起)
用作類型限定符;指代 type-name 的原子版本。在此作用中,它可以與 const 、 volatile 及 restrict 混合使用。儘管不同於其他限定符, type-name 的原子版本可能擁有不同的大小、對齊以及對象表示。
_Atomic const int * p1; // p 是指向 _Atomic const int 的指針
const atomic_int * p2; // 同上
const _Atomic(int) * p3; // 同上
原子類型的對象是僅有的免除數據競爭的對象,即它們可以被兩個線程共時修改,或先被一個修改再被另一個讀取。.
每個原子對象都擁有關聯於其自身的修改順序,即對該對象的完整修改順序。若從某個線程的視角來看,對於某原子對象M的修改 A 發生先於同一原子對象 M 的修改 B ,則在 M 的修改順序中 A 的出現先於 B 。
注意即使每個原子對象都有其自身的修改順序,它卻不是全序;不同線程可能會觀測到相異原子對象有相異的修改順序。
對於所有原子運算,保證有四種連貫:
寫寫連貫:若原子對象 M 的修改操作 A 發生先於 M 的修改操作 B ,則 M 的修改順序中 A 出現早於 B 。
讀讀連貫:若原子對象 M 的值計算 A 發生先於 M 的值計算 B ,且從 M 上的副效應X求得 A 值,則 B 所計算得的值要麼是 X 所存儲的值,要麼是 M 上的副效應 Y 所存儲的值,其中 Y 在 M 的修改順序中出現後於 X 。
讀寫連貫:若原子對象 M 的值計算 A 發生先於 M 上的操作 B ,則從 M 上的副效應X求得 A 值,這裏 X 在 M 的修改順序中出現先於 B 。
寫讀連貫:若在原子對象 M 上的副效應 X 發生先於 M 的值計算 B ,則求值 B 從 X,或從在 M 的修改順序中出現後於 X 的副效應 Y 求得其值。
一些原子運算亦是同步操作:它們可以擁有附加的釋放語義、獲取語義,或順序一致語義。見 memory_order 。
內建的自增減運算符和複合賦值運算符是擁有完全序列一致順序(如同用 memory_order_seq_cst )的讀-修改-寫操作。若想要更不嚴格的同步語義,則可以用標準庫函數替代。
原子屬性僅對左值表達式有意義。左值到右值轉換(其模仿從原子區域到CPU寄存器的內存讀取)會把原子性及其他限定符剝去。
示例:
#include <stdio.h>
#include <threads.h>
#include <stdatomic.h>
atomic_int acnt;
int cnt;
int f(void* thr_data)
{
for(int n = 0; n < 1000; ++n) {
++cnt;
++acnt;
// 對於此例,寬鬆內存順序是足夠的,例如
// atomic_fetch_add_explicit(&acnt, 1, memory_order_relaxed);
}
return 0;
}
int main(void)
{
thrd_t thr[10];
for(int n = 0; n < 10; ++n)
thrd_create(&thr[n], f, NULL);
for(int n = 0; n < 10; ++n)
thrd_join(thr[n], NULL);
printf("The atomic counter is %u\n", acnt);
printf("The non-atomic counter is %u\n", cnt);
}
可能的輸出:
The atomic counter is 10000
The non-atomic counter is 8644
10、改進的Unicode支持和頭文件<uchar.h>
。
語法
” s-char-sequence ” (1)
u8 ” s-char-sequence ” (2) (C11 起)
u ” s-char-sequence ” (3) (C11 起)
U ” s-char-sequence ” (4) (C11 起)
L ” s-char-sequence ” (5)
1) 字符串字面量:字面量類型爲 char[] ,用執行字符集從 s-char-sequence 中的下個字符初始化數組中的每個字符。
2) UTF-8 字符串字面量:字面量類型爲 char[] ,用 UTF-8 編碼,從 s-char-sequence 中的下個多字節字符初始化字符數組中的每個字符。
3) 16 位寬字符串字面量:字面量類型爲 char16_t[] ,如同在實現定義的本地環境中通過執行 mbrtoc16 一般初始化數組中的每個 char16_t 元素。
4) 32 位寬字符串字面量:字面量類型爲 char32_t[] ,如同在實現定義的本地環境中通過執行 mbrtoc32 一般初始化數組中的每個 char32_t 元素。
5) 寬字符串字面量:字面量類型爲 wchar_t[] ,如同在實現定義的本地環境中通過執行 mbstowcs 一般初始化數組中的每個 wchar_t 元素。
示例:
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <uchar.h>
#include <locale.h>
int main(void)
{
char s1[] = "a貓?"; // 或 "a\u732B\U0001F34C"
char s2[] = u8"a貓?";
char16_t s3[] = u"a貓?";
char32_t s4[] = U"a貓?";
wchar_t s5[] = L"a貓?";
setlocale(LC_ALL, "en_US.utf8");
printf(" \"%s\" is a char[%zu] holding { ", s1, sizeof s1 / sizeof *s1);
for(size_t n = 0; n < sizeof s1 / sizeof *s1; ++n)
printf("%#x ", +(unsigned char)s1[n]);
puts(" }");
printf("u8\"%s\" is a char[%zu] holding { ", s2, sizeof s2 / sizeof *s2);
for(size_t n = 0; n < sizeof s2 / sizeof *s2; ++n)
printf("%#x ", +(unsigned char)s2[n]);
puts(" }");
printf(" u\"a貓?\" is a char16_t[%zu] holding { ", sizeof s3 / sizeof *s3);
for(size_t n = 0; n < sizeof s3 / sizeof *s3; ++n)
printf("%#x ", s3[n]);
puts(" }");
printf(" U\"a貓?\" is a char32_t[%zu] holding { ", sizeof s4 / sizeof *s4);
for(size_t n = 0; n < sizeof s4 / sizeof *s4; ++n)
printf("%#x ", s4[n]);
puts(" }");
printf(" L\"%ls\" is a wchar_t[%zu] holding { ", s5, sizeof s5 / sizeof *s5);
for(size_t n = 0; n < sizeof s5 / sizeof *s5; ++n)
printf("%#x ", (unsigned)s5[n]);
puts(" }");
}
可能的輸出:
"a貓?" is a char[9] holding { 0x61 0xe7 0x8c 0xab 0xf0 0x9f 0x8d 0x8c 0 }
u8"a貓?" is a char[9] holding { 0x61 0xe7 0x8c 0xab 0xf0 0x9f 0x8d 0x8c 0 }
u"a貓?" is a char16_t[5] holding { 0x61 0x732b 0xd83c 0xdf4c 0 }
U"a貓?" is a char32_t[4] holding { 0x61 0x732b 0x1f34c 0 }
L"a貓?" is a wchar_t[4] holding { 0x61 0x732b 0x1f34c 0 }
11、quick_exit()
又一種終止程序的方式,當exit()失敗時用以終止程序。
quick_exit
C
程序支持工具
定義於頭文件
#include <stdlib.h>
#include <stdio.h>
void f1(void)
{
puts("pushed first");
fflush(stdout);
}
void f2(void)
{
puts("pushed second");
}
int main(void)
{
at_quick_exit(f1);
at_quick_exit(f2);
quick_exit(0);
}
輸出:
pushed second
pushed first
12、複數宏,浮點數宏。
頭文件 <tgmath.h>
包含頭文件 <math.h>
及 <complex.h>
,並定義了幾種泛型宏。這些宏會根據參數類型決定要調用的實際函數。
示例:
#include <stdio.h>
#include <complex.h>
#include <tgmath.h>
int main(void)
{
double complex z1 = I * I; // 虛數單位平方
printf("I * I = %.1f%+.1fi\n", creal(z1), cimag(z1));
double complex z2 = pow(I, 2); // 虛數單位平方
printf("pow(I, 2) = %.1f%+.1fi\n", creal(z2), cimag(z2));
double PI = acos(-1);
double complex z3 = exp(I * PI); // 歐拉公式
printf("exp(I*PI) = %.1f%+.1fi\n", creal(z3), cimag(z3));
double complex z4 = 1+2*I, z5 = 1-2*I; // 共軛
printf("(1+2i)*(1-2i) = %.1f%+.1fi\n", creal(z4*z5), cimag(z4*z5));
}
輸出:
I * I = -1.0+0.0i
pow(I, 2) = -1.0+0.0i
exp(I*PI) = -1.0+0.0i
(1+2i)*(1-2i) = 5.0+0.0i
13、time.h新增timespec結構體,時間單位爲納秒,原來的timeval結構體時間單位爲毫秒。
struct timespec 定義:
typedef long time_t;
#ifndef _TIMESPEC
#define _TIMESPEC
struct timespec {
time_t tv_sec; // seconds
long tv_nsec; // and nanoseconds
};
#endif
struct timespec有兩個成員,一個是秒,一個是納秒, 所以最高精確度是納秒。
一般由函數int clock_gettime(clockid_t, struct timespec *)獲取特定時鐘的時間,常用如下4種時鐘:
CLOCK_REALTIME 統當前時間,從1970年1.1日算起
CLOCK_MONOTONIC 系統的啓動時間,不能被設置
CLOCK_PROCESS_CPUTIME_ID 本進程運行時間
CLOCK_THREAD_CPUTIME_ID 本線程運行時間
struct tm *localtime(const time_t *clock); //線程不安全
struct tm* localtime_r( const time_t* timer, struct tm* result );//線程安全
size_t strftime (char* ptr, size_t maxsize, const char* format,const struct tm* timeptr );
struct timeval 定義:
struct timeval {
time_t tv_sec; // seconds
long tv_usec; // microseconds
};
struct timezone{
int tz_minuteswest; //miniutes west of Greenwich
int tz_dsttime; //type of DST correction
};
struct timeval有兩個成員,一個是秒,一個是微秒, 所以最高精確度是微秒。
一般由函數int gettimeofday(struct timeval *tv, struct timezone *tz)獲取系統的時間
示例:
#include<stdio.h>
#include<time.h>
#include<sys/time.h>
void nowtime_ns()
{
printf("---------------------------struct timespec---------------------------------------\n");
printf("[time(NULL)] : %ld\n", time(NULL));
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
printf("clock_gettime : tv_sec=%ld, tv_nsec=%ld\n", ts.tv_sec, ts.tv_nsec);
struct tm t;
char date_time[64];
strftime(date_time, sizeof(date_time), "%Y-%m-%d %H:%M:%S", localtime_r(&ts.tv_sec, &t));
printf("clock_gettime : date_time=%s, tv_nsec=%ld\n", date_time, ts.tv_nsec);
}
void nowtime_us()
{
printf("---------------------------struct timeval----------------------------------------\n");
printf("[time(NULL)] : %ld\n", time(NULL));
struct timeval us;
gettimeofday(&us,NULL);
printf("gettimeofday: tv_sec=%ld, tv_usec=%ld\n", us.tv_sec, us.tv_usec);
struct tm t;
char date_time[64];
strftime(date_time, sizeof(date_time), "%Y-%m-%d %H:%M:%S", localtime_r(&us.tv_sec, &t));
printf("gettimeofday: date_time=%s, tv_usec=%ld\n", date_time, us.tv_usec);
}
int main(int argc, char* argv[])
{
nowtime_ns();
printf("\n");
nowtime_us();
printf("\n");
return 0;
}
nowtime.cpp
執行結果:
$tt
—————————struct timespec—————————————
[time(NULL)] : 1400233995
clock_gettime : tv_sec=1400233995, tv_nsec=828222000
clock_gettime : date_time=2014-05-16 17:53:15, tv_nsec=828222000
—————————struct timeval—————————————-
[time(NULL)] : 1400233995
gettimeofday: tv_sec=1400233995, tv_usec=828342
gettimeofday: date_time=2014-05-16 17:53:15, tv_usec=828342
PS:有關關鍵字或者接口的參照可以查看以下地址:
http://zh.cppreference.com/w/%E9%A6%96%E9%A1%B5