C 語言 Coding Style 規範

我的編程風格

Posted on 2024-05-25

稍微整理了一下我自己習慣的 C 語言 Coding style。這些規則只是我自己的喜好。

簡述

  1. 所有的左右花括號 { } 都不換行。
  2. 縮排使用 4 個空白,而非 Tab 字元。
  3. const 全域常數、#define 巨集常數和巨集這三者使用大寫蛇形(SCREAMING_SNAKE_CASE),其餘全部使用蛇形(snake_case)命名風格。
  4. 全域範圍命名偏好用更多文字描述,區域範圍的命名偏好簡短。
  5. 使用常見的縮寫,但不能打破命名風格。
 1const int LOOP_TIMEOUT = 1000;
 2char tx_buffer[128] = {0};
 3
 4typedef struct {
 5    int age;
 6    const int id_number;
 7} human_info_t;
 8
 9void do_something(int my_param) {
10    int local_var = my_param;
11}
12
13int main(void) {
14    do_something(0xF3);
15
16    if (foo > 0) {
17        bar = 1;
18    } else {
19        bar = 0;
20    }
21
22    while (1) {  // Main loop
23        bar++;
24    }
25}

A. 定義

提及這些關鍵字時會以粗體標示。

  1. 強度:以下特定關鍵字按照 RFC2119 中的描述解釋。
    • 必須:MUST
    • 不允許:MUST NOT
    • 應該:SHOULD
    • 不應該:SHOULD NOT
    • 可以:MAY
  2. 命名風格:皆爲必須
    • 蛇型(Snake case):snake_casethe_name
    • 大寫蛇型(Screaming snake case):SCREAMING_SNAKE_CASETHE_NAME
  3. 命名偏好:皆爲應該
    • 具描述性:代表用更多文字精確描述。
    • 簡潔有力:代表盡可能簡短。

B. 通則

  1. 縮排必須使用 4 個空白(Space),不允許Tab 字元。(建議設定爲軟 Tab,即按 Tab 出空白)
  2. 任何左/右花括號 { }必須置於同一行,並縮進其內容一次,即 LLVM 或(類)K&R Coding style。
  3. 任何名稱都…
    1. 不允許加上單/雙底線(___)的前/後綴。
    2. 不允許使用阿拉伯數字作爲開頭。
    3. 可以使用常見的縮寫,常見代表這個縮寫(在此專業領域內)同時滿足:不會造成歧義、一目瞭然、足夠通用。但是需要注意不允許打破命名風格規則。
  4. 16 進制數的數值部分的英文必須爲大寫,如:0x3A0xFF8D
  5. 描述陣列的名稱應該使用複數形、集合名詞或容器等可以表示多個的名詞。
  6. 在需要區分的場合,指標可以加上 _p 後綴。
  7. 應該盡可能使用 const 常數取代巨集常數 #define
  8. 應該盡可能使用 inline 取代巨集 #define
  9. 如果呼叫的函數有非 void 回傳值,但你不需要用到它時,應該使用 (void) 明確地捨棄它。
  10. 檔案名稱必須使用蛇形,副檔名必須爲小寫。
  11. 檔案末行必須有一個換行符號。
  12. 特別對於嵌入式應用,應該使用明確大小和有無號的型別,例如 uint8_t
 1int main(void) {
 2    char rx_buf[32] = {0};
 3
 4    if (cnt > 0xF3) {
 5        // Do somethings
 6    } else if (cnt < 0) {
 7        // Do somethings
 8    } else {
 9        // Do somethings
10    }
11
12    while (1) {
13        (void)my_function(cnt);
14    }
15}
16
17int my_function(int param) {
18    return param;
19}

縮寫例如:

  • 使用 len 代表 length。此縮寫在 C 語言中足夠常見
  • 使用 buf 代表 buffer。此縮寫在 C 語言中足夠常見
  • 使用 isr 代表 interrupt_service_routine。此縮寫在嵌入式領域中足夠常見

縮寫名稱不允許打破命名風格。例如:

  • 使用ip_address 而非 IP_address
  • 使用 uart_send 而非 UART_send

C. 函數(Function)

  1. 函數名稱必須使用蛇型風格,命名應該偏好具描述性
  2. 參數(Parameter)
    1. 名稱必須使用蛇形風格,命名應該偏好簡潔有力,爲名詞。
    2. 若指標形式的參數不會或不應該在函數內變更,必須加上 const
    3. 無參數的函數也必須要補上 void
    4. 所有的參數必須要全部都在同一行或都在不同行,不允許有些在同行、有些獨立一行。
 1// OK
 2void timer_setup(void);
 3int module_is_enabled(void);
 4void uart1_send(const char* data, size_t len);
 5void foobar(int long_param) {
 6    int foo = long_param;
 7}
 8
 9void loooooooooooooooooooooooooooooooooong_function(
10    int param1, int param2, int param3
11) {
12    int foo = 0;
13}
14
15void loooooooooooooooooooooooooooooooooong_function(
16    int param1,
17    int param2,
18    int looooooooooooooooong_param3
19) {
20    int foo = 0;
21}
22
23// Wrong
24void timer_setup();           // 遺失 `void`
25void loooooooooooooooooooooooooooooooooong_function(
26    int param1, int param2,   // 只要有一個參數換行,那所有的參數都需要獨立一行
27    int looooooooooooooooong_param3
28) {
29    int foo = 0;
30}

D. 變數(Variable)

  1. 全域範圍
    1. 一般全域變數名稱必須使用蛇形風格,命名應該偏好具描述性,爲名詞。
    2. const 全域常數名稱必須使用大寫蛇形風格,命名應該偏好具描述性,爲名詞。
  2. 區域變數名稱必須使用蛇形風格,命名應該偏好簡潔有力,爲名詞。
 1// OK
 2int uart1_rx_index = 0;
 3int analog_channels[5];
 4const int UART1_BAUDRATE = 9600;
 5static const int UART1_TIMEOUT = 10000;
 6void func(void) {
 7    int tmp = 0;
 8    static int cnt;
 9    char rx_buf[16];
10    for (int i = 0; i < 100; i++);
11}
12
13// Wrong
14int rx_i = 0;            // 太短
15int UART1_rx_index = 0;  // 縮寫不能打破命名風格規則,即使是首字母縮寫
16int analog_channel[5];   // 在陣列使用複數形、集合名稱或容器
17void func(void) {
18    static int CNT;      // `static` 不會改變命名風格
19}

E. 資料型別(Data Type)

  1. 列舉(Enum)、結構(Struct)與聯合(Union)
    1. 本身的名稱必須使用蛇型風格,命名應該偏好具描述性,爲名詞。
    2. 成員的名稱必須使用蛇形風格,命名應該偏好簡潔有力,爲名詞。
    3. 多行形式下,最後一個成員必須加上尾隨逗號(Trailing comma)。
  2. 使用 typedef 定義的…
    1. 型別名稱要加上 _t 後綴。
    2. 函數指標名稱要加上 _fn 後綴。
  3. Enum 的成員名稱可以加上某些統一的前綴,以避免與其它 Enum 成員重複。
  4. Struct 的成員…
    1. 如果其值不會或不應該改變的話,必須加上 const
    2. 視情況使用 bit-field。
 1// OK
 2struct human_info {
 3    int age;
 4    int id;
 5};
 6
 7typedef enum {
 8    key_release = 0,
 9    key_debounce,
10    key_accept,
11} key_stage_t;
12
13union control_reg {
14    struct {
15        int bit1: 1;
16        int bit2: 1;
17        int bit34: 2;
18        const int unused: 7;
19    } bits;
20    int byte;
21};
22
23typedef int (*event_callback_fn)(int long_param);

F. 流程控制

  1. switch-case 語法的 casedefault 不允許縮排。其內容必須要縮排。
  2. switch-case 語法的最後一個 casedefault必須要加上 break,即使它在語法上是可以省略的。
  3. 如果要在 casedefault 中加入花括號,且該區塊末的 break 必須放在其外。
  4. 總是必須明確地加上 default,即使它的內容爲空。
 1void function(int param) {
 2    int foo;
 3
 4    switch (param) {
 5    case 0:
 6        foo = 100;
 7        break;
 8    case 1:
 9    case 2:
10        foo = 250;
11        break;
12    case -1: {
13        foo = -99;
14    } break;
15    default:
16        break;
17    }
18}

G. 預處理器(Preprocessor)

  1. 巨集常數(Macro constants,以 #define 定義的值)
    1. 名稱必須使用大寫蛇形風格,命名應該偏好具描述性,爲名詞。
    2. 必須使用括號 () 包圍,以避免展開錯誤。
    3. 可以加上強制轉型(Casting)作爲型別標記。
  2. 巨集(Macro)
    1. 本身的名稱必須使用大寫蛇形風格,命名應該偏好具描述性
    2. 參數的名稱必須使用蛇形風格,命名應該偏好簡潔有力(更甚至使用單一字母)。
    3. 所有參數皆必須使用括號 () 包圍,以避免展開錯誤。
    4. 複雜(內有邏輯處理)的巨集應該使用 do-while(0) 包圍,確保讓編譯器將其解釋爲一個獨立的 Block。
    5. 可以加上強制轉型(Casting)作爲型別標記。
 1// OK
 2#define UART_RX_PIN   (GPIO5)
 3#define UART_BAUDRATE ((uint16_t)19200)
 4
 5#define ADD2(a, b)          ((a) + (b))
 6#define FOO_BAR(foo_bar)    (foo_bar++)
 7
 8// Wrong
 9#define UART_RX_PIN  GPIO5           // 遺失括號包圍
10#define ADD2(a, b)          (a + b)  // 參數遺失括號包圍
  1. #endif不應該加上用於標註其對應的開頭的註解。
  2. 所有 .h 標頭檔皆必須有引用保護(Include Guard)。
    1. 若使用的編譯器支援 #pragma once 則優先使用,否則使用預處理器達成,其格式爲:
1/// @file my_file.h
2
3#ifndef MY_FILE_H
4#define MY_FILE_H
5
6// Your code
7
8#endif
  1. 視情況而定,可以加入 C++ 檢查。格式爲:
 1/// @file my_file.h
 2
 3#ifndef MY_FILE_H
 4#define MY_FILE_H
 5
 6#include "something.h"
 7
 8#ifdef __cplusplus
 9extern "C" {
10#endif
11
12// Your code
13
14#ifdef __cplusplus
15}
16#endif
17
18#endif

H. 註解

  1. 所有註解句子的第一個字必須要大寫,除非句子的開頭不是英文。
  2. 一般註解必須使用單行註解形式,即雙斜線,即使它會寫成多行的形式。行末不允許加句號,即使行中有其它標點符號。
    1. 例外:帶有特殊功能的註解必須使用多行註解形式,例如 /* clang-format off */
  3. 文件註解必須使用 Doxygen 的三斜線 /// 註解形式。如果是描述性的句子,各行末必須加句號。
    1. 例外:URL 網址或檔案名等特殊字段行末不允許加句號,以避免感染閱讀或複製。
  4. 對於檔案的 Doxygen 格式必須遵守以下規則與順序:
    1. 使用 @file <FILENAME> 標記此檔案的名稱。
    2. 使用 @brief <TEXT> 簡述此檔案。
    3. 使用 @attention <TEXT> 描述特別需要注意的事情。
    4. 使用 @author <NAME> 標記作者。可以在作者的名字後面加上以角括號 < > 包圍的 Email。
    5. 使用 @copyright <LICENSE> 標記此檔案的授權許可。<LICENSE> 應該使用 SPDX-License-Identifier: <SPDX_ID> 這樣的格式,除非此授權不在 SPDX 內。或是嵌入授權許可的所有內容。
    6. 使用 @note <TEXT> 寫其它說明,尤其是段落性質的筆記。
  5. 對於函數的 Doxygen 格式必須遵守以下規則與順序:
    1. 使用 @brief <TEXT> 簡述此函數。
    2. 使用 @note <TEXT> 寫其它說明,尤其是段落性質的筆記。
    3. 使用 @param <NAME> <TEXT> 描述各個參數。可以使用下述帶資料方向標記的版本。
    4. 使用 @param[<DIR>] <NAME> <TEXT> 描述帶有方向的各個參數。[<DIR>] 必須[in][out][in, out]
    5. 使用 @return <TEXT> 描述函數的回傳值。
  6. 對於全域變數、常數的 Doxygen 格式必須遵守以下規則與順序:
    1. 使用 @brief <TEXT> 簡述此變數或常數。
    2. 使用 @note <TEXT> 寫其它說明,尤其是段落性質的筆記。
  7. 如果是段落性質的 Doxygen 文字,換行後必須要保持縮排。
  8. Doxygen 文字可以使用 Markdown 語法.
  9. 不允許 結構化註解,例如:// ########## //
 1/// @file foobar.c
 2/// @brief Awesome code.
 3/// @author ZiTe <honmonoh@gmail.com>
 4/// @copyright SPDX-License-Identifier: MIT
 5
 6/// @brief Comment, comment and comment.
 7uint8_t foobar = 0;
 8
 9/// @brief Do something, doc comment end with a period.
10///        Url without period: https://github.com/git/git
11///
12/// @param data A pointer to the data.
13/// @param len The length of data.
14/// @return 0 if successful, otherwise an error code.
15uint16_t do_something(const uint8_t *data, uint16_t len) {
16    // Multi line comment
17    // foo, bar
18    uint8_t val = *data;  // Without period
19    return 0;             // Success
20}
21
22/// @brief Copies the values of `len` bytes from `src` to `des`.
23///
24/// @param[out] des Pointer to the destination array.
25/// @param[in] src Pointer to the source of data.
26/// @param[in] len Number of bytes to copy.
27void array_copy(uint8_t *des, const uint8_t *src, uint16_t len) {
28}

I. 空白

  1. 如果註解前有程式碼,必須間隔 2 個空白。
  2. 函數(包含宣告、定義、呼叫)和 sizeof 運算子的左圓括號前不允許插入空白,其餘都必須在前有 1 個空白。
  3. 右圓括號前不允許插入空白。
  4. 若逗號 , 或分號 ;不允許插入空白,若其後還有其它內容,其後必須有 1 個空白。除了三元運算子以外的冒號 : 也套用此規則。
  5. 除了下述的運算子與其它數值或變數間必須有 1 個空白、以及 sizeof 運算子套用函數規則外,其餘運算子與數值或變數之間不允許有空白。
    1. 三元條件 ? :
    2. 各種賦值:=, +=, -=, *=, /=, %=, <<=, =>>, &=, |=, ^=
    3. 邏輯 AND &&,邏輯 OR ||
    4. 位元 AND &,位元 OR |,位元 XOR ^
    5. 各種關係:==, !=, <, <=, >, >=
    6. 左移 <<,右移 >>
    7. 基本運算的:+, -, *, /, %
 1void func(uint8_t p1, uint8_t p2);
 2
 3int main(void) {
 4    func(1, 2);
 5    uint8_t len = sizeof(foobar);
 6    uint8_t a = 1 + 2;
 7    uint8_t b = 1 + (1 * 10) % (len / (a * 3.14));
 8    ++a;
 9    if (a);
10    for (uint8_t i = 0; i < 10; i++);
11    while (1);  // Main loop
12}

附錄

Q&A

爲何所有的花括號 {} 都不換行?爲何不是 Allman 風格?

因爲:C 語言用 K&R Coding Style 的最大理由不是省行數

爲何要使用 (void) 捨棄非 void 的函數回傳值?

爲了明確表達你知道這個函數有回傳值,但是你不需要它,因爲很多情況下函數的回傳值會是錯誤代碼之類的。

這是來自 Rust 的啓發。 Casting function returns to void

爲什麼函數的參數只能有全部同行和全部不同行兩種?

  1. 保持規則單純。
  2. 參數寫成多行的情況表示此函數的參數有一定的複雜性(無論是數量還是名稱),各自獨立一行更方便閱讀和修改。

這是來自 Rust 的啓發。

使用縮寫不會影響閱讀和判斷嗎?

所以你只能使用常見、不會造成歧義和一目瞭然的縮寫。

C is a Spartan language, and your naming conventions should follow suit. Unlike Modula-2 and Pascal programmers, C programmers do not use cute names like ThisVariableIsATemporaryCounter. A C programmer would call that variable tmp, which is much easier to write, and not the least more difficult to understand. – From Linux kernel coding style

爲何 switch-case 的最後一個案例也要加 break

主要有兩個理由:1. 保持一致,2. 如果哪天修改了這些案例,明確的 break 是一種保護。

讓我節錄一些看法:

Refactorability. If all your branches end with break or return, you can reorder them without changing the meaning. This makes it less likely for such a reordering to introduce a regression. – From @tdammers Break on default case in switch

As a matter of good form, put a break after the last case (the default here) even though it’s logically unnecessary. Some day when another case gets added at the end, this bit of defensive programming will save you. – From The C Programming Language, 2/e, Should we break the default case in switch statement?

爲何使用花括號的 switch-casebreak 要在其外?

絕大多數的情況下 case 都會以 break 結束,以避免執行了預期外的程式。某些較複雜的案例會使用花括號包圍,將 break 放在其外可以更明確地確認你沒有遺漏結尾的 break

爲何 typedef 要加上後綴?

加上 _t 後綴是來自標準庫的習慣,實際上大多數的專案也延續了此規則,而且這樣可以明確知道這個是一種型別,而非變數等。

指標函數加上 _fn 後綴是對於 _t 行爲的擴充。

爲何要爲指標加上後綴?

因爲指標代表的是該數值的記憶體位置,而不是該數值本身。例如「台北101」和「台北市信義區信義路五段7號」,雖然後者可以指向前者,但它們在概念上終究不是完全一樣的。

當然,這個後綴是可選的,請根據實際需求選擇。

爲何 const 只在全域才實施大寫蛇形,而區域變數、成員和參數不實施?

const 全域常數實施大寫蛇形是爲了替換/取代/兼容 #define 的巨集常數。使用 #define 定義值的名稱使用大寫蛇形是多數 C 專案的共同習慣,所以作爲替換的 const 全域常數也使用相同的規則(此規則甚至在其它語言也適用)。區域變數、成員和參數不實施是因爲它們本來就沒有「替換 #define」的這層意義與功能在。

另外,爲了鼓勵使用者爲本來就不會也不該改變的值使用不可變變數(Immutable variable),const 區域變數、成員和參數不實施大寫蛇形,而是遵照原始規則,可以讓使用者不會爲了「避免程式碼出現一堆難看的全大寫」而進一步避免使用 const。所以如果你確定這個區域變數、成員或參數的值不會也不該改變,請考慮加上 const。這是來自 Rust 的一個啓發。

you should use const wherever possible but for maintainability reasons & preventing yourself from doing stupid mistakes. – From Should I use const for local variables for better code optimization?

[106] const 變數有助理解程式碼並協助編譯器優化

爲何要用 const 全域常數取代 #define

實際上 const#define 的處理方式不同。#define 是預處理器會使用「替換」的方式處理,const 是表明此變數不可變(在編譯期檢查)。

使用 const#define 對編譯出來的程式大小或效能並不會有差異,所以性能不是區分何時該使用這兩者的考量。

const 的好處是:

  1. 明確型別,編譯器會對它進行型別檢查(有寫過動態型別語言的使用者應該很清楚這件事的好處),而 #define 只是單純的替換文字。
  2. const 可以用 static 限制存取範圍,有助於封裝(Encapsulation),而 #define 基本上是跨檔案的。
  3. 在一些 IDE 中,Debug 模式下是看不到 #define 的實際值的,而 const 可以。

當然,如果有特殊需求(例如刻意不想進行型別檢查)或充分理由時,還是可以使用 #define

What is the difference between #define and const?

爲何要用 inline 取代 #define 巨集?

基本上和「爲何要用 const 全域常數替換或取代 #define?」的理由類似。

#define 的特性單純是不會進行型別檢查。大多數情況下,我們總是喜歡型別檢查,這會把錯誤顯式的帶到編譯期而非執行期。也同樣的,如果你有刻意不想進行型別檢查(例如想要參數不限型別)的充分理由或需求的話,使用巨集。

在可讀性上巨集也差很多。多行的 #define 要在行末加上 \,複雜的甚至還要使用 do-while(0) 包圍才可以確保編譯器將其解釋爲一個 Block。而 inline 函數就只是比一般的函數前面多一個修飾詞而已。當然,如果這個巨集足夠單純的話,使用巨集無可厚非,但是還是要注意巨集的非預期行爲。

巨集可能在展開後出現非預期行爲。想像一下如何用巨集實現一個 MAX(a, b) 函數,它會比較兩個參數並回傳較大的那個。感覺很簡單?

1#define MAX(a, b) (a) > (b) ? (a) : (b)

實際上上面的寫法在某些情況下會有非預期行爲,這是比較實務的寫法(Linux 核心原始程式碼巨集: max, min):

1#define MAX(a, b) ({     \
2    typeof (a) _a = (a); \
3    typeof (b) _b = (b); \
4    _a > _b ? _a : _b;   \
5})

Inline functions vs Preprocessor macros

爲何標頭檔的 C++ 檢查不是必須

簡單來說,我認爲這違反 YAGNI(You Aren’t Gonna Need It,你不會需要它)原則。我認爲它可能是一種對於未來情況的假設。

如果你知道或認爲這個程式就是不會被 C++ 使用,只會在純 C 中的話,那加入 C++ 檢查就是多餘且干擾的。我參與的專案中有很多都是嵌入式系統,它們基本上永遠都只會是純 C,完全沒有必要讓 C++ 檢查爲強制必須的,你自行判斷是否該加入。但是我認爲這個判定可以寬鬆一點,即只要稍微有一點會在 C++ 中使用的可能,那就可以加。

爲何 #endif 後不加對應開頭的註解?

#endif 後面加對應的 #if/#ifdef/#ifndef 名稱的註解是很多 C 專案的習慣,例如:

 1#ifndef MY_CODE_H
 2#define MY_CODE_H
 3
 4#ifdef __cplusplus
 5extern "C" {
 6#endif /* __cplusplus */
 7
 8#ifdef __cplusplus
 9}
10#endif /* __cplusplus */
11
12#endif /* MY_CODE_H */

但是,對於使用 #ifndef 作引入保護的標頭檔,誰不知道最後一個 #endif 對應的是引入保護?又或者是 C++ 檢查的部分,它們各自也只有3行而已,一看就知道對應的開頭是哪個。而且如果中間有一個 #elif 的話呢?

那如果是其它預處理器判斷式呢?我認爲,如果那些判斷式寫的很複雜或很多行(尤其無法在一個螢幕上顯示)的話,可以考慮加上註解。但同時也要優先考慮是否真的有必要使用這麼複雜或長的預處理器判斷式?

就如同 Linux Kernel 風格的縮排使用 8 個空白,如果有人抱怨「用 8 個空白的話一下就縮排到最右邊了!」,那他們會回應:「不要讓你的程式碼使用超過 3 層縮排就不會有這個問題。如果你的程式超過 3 層,那代表它不夠簡潔,需要重新檢視」

如果你有 N 個預處理器 Flag,那這個程式就有 2^N 種變化性(或著說狀態),這可能是難以掌握的。而且這是不是預處理或是檔案層級的單一職責問題?

自動格式化

推薦使用 clang-format 工具來自動完成程式碼格式化(已整合在 VS Code C/C++ 套件中),這是 LLVM 專案下的開源工具。可以在 Clang-format configurator v2 網站測試並預覽格式化設定。

參考設定檔 .clang-format

 1---
 2# clang-format v18.1.3
 3
 4BasedOnStyle: LLVM
 5Language: Cpp
 6TabWidth: 4
 7IndentWidth: 4
 8AccessModifierOffset: -4
 9ColumnLimit: 0
10UseTab: Never
11SortIncludes: Never
12AlignAfterOpenBracket: BlockIndent
13AlignArrayOfStructures: Left
14AlignConsecutiveMacros:
15  Enabled: true
16  AcrossComments: true
17AlignEscapedNewlines: Left
18AlignOperands: AlignAfterOperator
19AllowShortBlocksOnASingleLine: Always
20AllowShortFunctionsOnASingleLine: None
21AllowShortIfStatementsOnASingleLine: AllIfsAndElse
22AllowShortLoopsOnASingleLine: true
23BinPackArguments: false
24BinPackParameters: false
25BitFieldColonSpacing: After
26BreakBeforeBraces: Custom
27BraceWrapping:
28  AfterCaseLabel: false
29  AfterClass: false
30  AfterControlStatement: Never
31  AfterEnum: false
32  AfterFunction: false
33  AfterNamespace: false
34  AfterObjCDeclaration: false
35  AfterStruct: false
36  AfterUnion: false
37  AfterExternBlock: true  # Make `IndentExternBlock: NoIndent` work. https://github.com/llvm/llvm-project/issues/49804
38  BeforeCatch: false
39  BeforeElse: false
40  BeforeLambdaBody: false
41  BeforeWhile: false
42  IndentBraces: false
43  SplitEmptyFunction: true
44  SplitEmptyRecord: true
45  SplitEmptyNamespace: true
46BreakBeforeBinaryOperators: NonAssignment
47CommentPragmas: ''
48IncludeIsMainRegex: ''
49IndentExternBlock: NoIndent
50IndentPPDirectives: BeforeHash
51InsertNewlineAtEOF: true
52InsertTrailingCommas: Wrapped
53KeepEmptyLinesAtTheStartOfBlocks: false
54LineEnding: DeriveLF
55...

如果你使用的是 VS Code,也可以使用 key-value 格式直接設定(推薦設定在 C_Cpp.clang_format_fallbackStyle 內):

1{BasedOnStyle: LLVM, Language: Cpp, TabWidth: 4, IndentWidth: 4, AccessModifierOffset: -4, ColumnLimit: 0, UseTab: Never, SortIncludes: Never, AlignAfterOpenBracket: BlockIndent, AlignArrayOfStructures: Left, AlignConsecutiveMacros: {Enabled: true, AcrossComments: true}, AlignEscapedNewlines: Left, AlignOperands: AlignAfterOperator, AllowShortBlocksOnASingleLine: Always, AllowShortFunctionsOnASingleLine: None, AllowShortIfStatementsOnASingleLine: AllIfsAndElse, AllowShortLoopsOnASingleLine: true, BinPackArguments: false, BinPackParameters: false, BitFieldColonSpacing: After, BreakBeforeBraces: Custom, BraceWrapping: {AfterCaseLabel: false, AfterClass: false, AfterControlStatement: Never, AfterEnum: false, AfterFunction: false, AfterNamespace: false, AfterObjCDeclaration: false, AfterStruct: false, AfterUnion: false, AfterExternBlock: true, BeforeCatch: false, BeforeElse: false, BeforeLambdaBody: false, BeforeWhile: false, IndentBraces: false, SplitEmptyFunction: true, SplitEmptyRecord: true, SplitEmptyNamespace: true}, BreakBeforeBinaryOperators: NonAssignment, CommentPragmas: '', IncludeIsMainRegex: '', IndentExternBlock: NoIndent, IndentPPDirectives: BeforeHash, InsertNewlineAtEOF: true, InsertTrailingCommas: Wrapped, KeepEmptyLinesAtTheStartOfBlocks: false, LineEnding: DeriveLF}

如果你想要另外下載執行檔的話,可以在 LLVM 的 GitHub 下載並安裝。安裝完的預設路徑應該是在 C:\Program Files\LLVM\bin\clang-format.exe

Doxygen

你可以在 VScode 安裝這個 Doxygen Documentation Generator 來協助產生 Doxygen 註解。可以使用以下的設定:

 1{
 2  "doxdocgen.c.commentPrefix": "/// ",
 3  "doxdocgen.c.firstLine": "",
 4  "doxdocgen.c.lastLine": "",
 5  "doxdocgen.c.triggerSequence": "///",
 6  "doxdocgen.file.copyrightTag": [
 7    "@copyright SPDX-License-Identifier: "
 8  ],
 9  "doxdocgen.file.fileOrder": [
10    "file",
11    "brief",
12    "author",
13    "copyright"
14  ],
15  "doxdocgen.generic.authorTag": "@author {author} <{email}>",
16  "doxdocgen.generic.returnTemplate": "@return ",
17}

參考



留言可能不會立即顯示。若過了幾天仍未出現,請 Email 聯繫:)

comments powered by Disqus