C11 標準新特性

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章