转载:https://www.cnblogs.com/life2refuel/p/7604655.html
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标准研究部分就到这里了. 说真的, 新手别入坑. 欢迎指针错误, 最后引述一个大佬的话
图片说明