1. 程式人生 > >C11 標準特性研究

C11 標準特性研究

要點 2.0 win cjson 產生 nan query iso 安全

前言 - 需要點開頭

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

    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;
}

最終輸出結果

技術分享

11quick_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標準研究部分就到這裏了. 說真的, 新手別入坑. 歡迎指針錯誤, 最後引述一個大佬的話

技術分享

C11 標準特性研究