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
1
2
3
4
5
6
7
8
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
default-aligned addr:   0x17d6010
1024-byte aligned addr: 0x17d6400
1
2
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.");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
输出:

Preparing to stop...
1
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");
}
1
2
3
4
5
6
7
8
9
10
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 个字符到缓冲区后没有遇到换行符或文件尾。 
1
2
3
4
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);       
    }   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
如果不使用匿名结构体,则上述例子对应的代码如下:

#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);       

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
对比上述两个例子可以看出:

使用匿名结构体,结构体对象 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; // 同上
1
2
3
原子类型的对象是仅有的免除数据竞争的对象,即它们可以被两个线程共时修改,或先被一个修改再被另一个读取。.

每个原子对象都拥有关联于其自身的修改顺序,即对该对象的完整修改顺序。若从某个线程的视角来看,对于某原子对象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 求得其值。 
1
2
3
4
一些原子运算亦是同步操作:它们可以拥有附加的释放语义、获取语义,或顺序一致语义。见 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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
可能的输出:

The atomic counter is 10000
The non-atomic counter is 8644
1
2
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(" }");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
可能的输出:

"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  }
1
2
3
4
5
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
输出:

pushed second
pushed first
1
2
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));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
输出:

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
1
2
3
4
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
1
2
3
4
5
6
7
8
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 
};
1
2
3
4
5
6
7
8
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
执行结果:

$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
————————————————
版权声明:本文为CSDN博主「MachineChen」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012611878/article/details/79090793

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值