2016 年,現代 C 語言的寫法

故名思義就是 C 要怎麼寫才能與時俱進。本文參考自 How to C in 2016 這篇文章。我只挑我這種新手看得懂的部分來講,有興趣的人可以去看原文。

文章開頭就講:

寫 C 的首要原則,就是避免去寫 C。
(The first rule of C is don’t write C if you can avoid it.)

這個部分笑笑就好,以下正經的。

編譯器

C 的版本建議使用 C11,再來是 C99。編譯器建議使用 clang,再來是 gcc。編譯時可加上 -std=c11 選項使用 C11 編譯,例如:

$ clang -std=c11 main.c -o justDoIt

型別

文章提到使用 charintshortlong 或是 unsigned 這種型別寫法不利於程式碼的閱讀,也容易出現跨平台相容性的問題。

建議引用 stdint.h 函式庫,並使用具有帶號(signed)與長度資訊宣告方式:

  • 帶正負號的整數使用 int8_tint16_tint32_tint64_t
  • 不帶號的整數使用 uint8_tuint16_tuint32_tuint64_t
  • 32 與 64 位元長的浮點數使用 floatdouble

除了浮點數外,整體的部分一目瞭然。另外就是請以 uint8_t 取代 char 來宣告不帶號的 8 位元整數。char 保留在對於字元或字串(char *)的宣告上就好。

若不想考慮變數長度的問題,整數的話直接宣告 intmax_tuintmax_t,浮點數則用 double 即可。如此一來程式會以最大的變數長度來執行,即可避免溢位或精度不足的問題。

另外在 stddef.h 中有所有謂的 size_t,可以用來宣告最大的「不」帶號整數。依 C99 的定義,size_t 是個可以做為任何長度的陣列的索引(index)。至於要不要使用它則隨意了。

不使用 intlong…等的例外是,如果所使用的函數輸入值或回傳值為 int(或 long…等) ,則配合其定義即可。

C99 允許在任何程式區塊做宣告

引用文章中的例子。「別」這樣用:

void test(uint8_t input) {
    uint32_t b;

    if (input > 3) {
        return;
    }

    b = input;
}

而是:

void test(uint8_t input) {
    if (input > 3) {
        return;
    }

    uint32_t b = input;
}

(寫過了較新的程式語言的人應該很習慣了 XD)

C99 允許在 for 迴圈宣告計數器

引用文章的例子。「別」這樣用:

    uint32_t i;

    for (i = 0; i < 10; i++)

而是:

    for (uint32_t i = 0; i < 10; i++)

除非你需要把上述的 i 值傳出去(傳出該 for 迴圈的 {...} 外),直接做上述的宣告即美觀又容易控制。

Array 直接做初始化

「別」這樣做:

    uint32_t numbers[64];
    memset(numbers, 0, sizeof(numbers));

而是:

    uint32_t numbers[64] = {0};

Structure 直接做初始化

「別」這樣做:

    struct thing {
        uint64_t index;
        uint32_t counter;
    };

    struct thing localThing;

    void initThing(void) {
        memset(&localThing, 0, sizeof(localThing));
    }

而是:

    struct thing {
        uint64_t index;
        uint32_t counter;
    };

    struct thing localThing = {0};

函數中的輸入型別

要讓函數可以輸入任意數值,別限制他的型別。所以「別」這樣做:

void processAddBytesOverflow(uint8_t *bytes, uint32_t len) {
    for (uint32_t i = 0; i < len; i++) {
        bytes[0] += bytes[i];
    }
}

而是:

void processAddBytesOverflow(void *input, uint32_t len) {
    uint8_t *bytes = input;

    for (uint32_t i = 0; i < len; i++) {
        bytes[0] += bytes[i];
    }
}

將輸入型別設定為 void *,並在內容中重新指派。

回傳型別

若回傳值有「是」/「否」這種需求,建議引入 stdbool.h 函式庫,使用 truefalse 來代替整數型態(例如 int32_t)的 1 或 0。讓程式更容易閱讀。

可以的話,建議創一個 enum 來表示複合型態的回傳型別。例如:

typedef enum growthResult {
    GROWTH_RESULT_SUCCESS = 1, // 成功
    GROWTH_RESULT_FAILURE_GROW_NOT_NECESSARY, // 失敗 1
    GROWTH_RESULT_FAILURE_ALLOCATION_FAILED   // 失敗 2
} growthResult;

寫作習慣

程式沒人看得懂,功能再怎麼強都沒用。文章中建議使用自動格式工具(auto code formatter):clang-format

例如要對目標 main.c 進行轉換,在終端機中敲入:

$ clang-format -style="{BasedOnStyle: llvm, IndentWidth: 4, AllowShortFunctionsOnASingleLine: None, KeepEmptyLinesAtTheStartOfBlocks: false}" "$@" -i main.c

clang-format 會將將程式依建議重新排版。但是每次都這樣敲太麻煩了。可以將 clang-format 指令以 alias 的型式建在 .bash_profie.bash_rc 中:

# .bash_profile or .bash_rc

alias clang-format='clang-format -style="{BasedOnStyle: llvm, IndentWidth: 4, AllowShortFunctionsOnASingleLine: None, KeepEmptyLinesAtTheStartOfBlocks: false}" "$@"'

之後就能直接以 clang-format 進行轉換:

$ clang-format -i main.c

其中若省略掉 -i 是直接在 bash 中預覽結果,而不將轉換結果覆寫掉目標檔案。

其它

不建議使用 malloc,取而代之的是使用 calloc。另外也應盡量避免使用 memset。詳見參考的文章。

結論

最後再重申一次,本文參考自 How to C in 2016 。祝大家寫程式人家看得懂,也看得懂別人寫的程式。

廣告

Posted

in

by

Tags:

Comments

「2016 年,現代 C 語言的寫法」 有一則迴響

  1. 「D男子」的個人頭像
    D男子

    朝聖推

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s