C11 标准特性研究

Posted 喜欢兰花山丘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C11 标准特性研究相关的知识,希望对你有一定的参考价值。

前言 - 需要点开头

  C11标准是C语言标准的第三版(2011年由ISO/IEC发布),前一个标准版本是C99标准。

相比C99,C11有哪些变化呢!!所有的测试全部基于能够和标准贴合的特性平台. 但是绝大部

分来源于 GCC. 这里不妨教大家源码安装最新的GCC吧。

 

a. 首先去 GNU GCC官网下载最新的 GCC 源码

  GCC  : https://gcc.gnu.org/

下载最新源码, 安装过程中可能提示下面这句话

configure: error: Building GCC requires GMP 4.2+, MPFR 2.4.0+ and MPC 0.8.0+.

说白了缺少上面 GMP,MPFR,MPC 三个组件。 那么开始下载

  GMP  : ftp://ftp.gnu.org/gnu/gmp/

  MPFR: http://www.mpfr.org/mpfr-current/

  MPC  : ftp://ftp.gnu.org/gnu/mpc/

 

b. 开始挨个解压安装 GMP → MPFR → MPC → GCC

开始执行命令跑起来。

cd gmp-6.1.2/
mkdir gmp-6.1.2-build
cd gmp
-6.1.2-build
..
/configure

 

我们如果出现

checking for suitable m4... configure: error: No usable m4 in $PATH or /usr/5bin (see config.log for reasons).

不用怕,那就继续安装 m4

  m4ftp://ftp.gnu.org/gnu/m4/

cd m4-1.4.18

mkdir m4-1.4.18-build

cd m4-1.4.18-build

../configure

make

sudo make install

 

那继续安装 GMP。

cd ../../gmp-6.1.2 /gmp-6.1.2-build

../configure

make

sudo make install

 

随后就是 MPFR

cd ../../mpfr-3.1.6

mkdir mpfr-3.1.6-build

cd mpfr-3.1.6-build

../configure

make

sudo make install

 

然后就是 MPC

cd ../../mpc-1.0.3

mkdir mpc-1.0.3-build

cd mpc-1.0.3-build

../configure

make

sudo make install

 

最后还是回到我们的 gcc

cd ../../gcc-7.2.0

mkdir gcc-7.2.0-build

cd gcc-7.2.0-build

../configure

 

又是不好意思,提示下面错误信息

configure: error: I suspect your system does not have 32-bit development libraries (libc and headers). 
If you have them, rerun configure with --enable-multilib. If you do not have them,
and want to build a 64-bit-only compiler, rerun configure with –disable-multilib.

继续

../configure --enable-multilib

make

 

不好意思又来了

checking dynamic linker characteristics... configure: error: Link tests are not allowed after GCC_NO_EXECUTABLES.
Makefile:11923: recipe for target \'configure-stage1-zlib\' failed
make[2]: *** [configure-stage1-zlib] Error 1
make[2]: Leaving directory \'/home/wangzhi/桌面/gcc-7.2.0/gcc-7.2.0-build\'
Makefile:23803: recipe for target \'stage1-bubble\' failed
make[1]: *** [stage1-bubble] Error 2
make[1]: Leaving directory \'/home/wangzhi/桌面/gcc-7.2.0/gcc-7.2.0-build\'
Makefile:933: recipe for target \'all\' failed
make: *** [all] Error 2

没关系我们继续搞,存在首次安装GCC不彻底污染问题,清理后继续安装

make distclean

../configure –enable-multilib

make

 

还是不行更换思路, 走插件全安装

sudo apt-get install gawk

sudo apt-get install gcc-multilib
sudo apt-get install binutils
sudo apt-get install lzip

 
make distclean

../configure

make

sudo make install

见过漫长的等待,下面就是见证历史奇迹的时候了。

到这里关于 GCC 升级到最新版本问题以及搞定。

 

正文  -  C11标准特性研究

 

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ß水

以上是关于C11 标准特性研究的主要内容,如果未能解决你的问题,请参考以下文章

GNU C编译器的gnu11和c11

编程界的常青树C语言,这些新特性你了解多少?

C99标准新特性的说明

C11新特性之智能指针

glibc中strtoul的实现是不是与C11标准冲突?

C11性能之道:标准库优化