C 程式庫可讀性評分量表

本文件說明編寫 C 程式庫的經驗法則和規則 Fuchsia SDK 中發布的版本。

會為 C++ 程式庫編寫不同的文件。雖然 C++ 是 對 C 的延伸,而且對本文件、 編寫 C++ 程式庫的模式與 C 截然不同

本文件大部分都與 介面上。這不是完整的 C 格式指南,且 幾乎不會說明 C 來源檔案的內容也不是 說明文件評分量表 (不過公開介面 )。

部分 C 程式庫的外部限制與這些限制相牴觸 不過,編寫這類演算法並不容易 因為我們無法寫出所有可能的規則舉例來說,C 標準程式庫本身不會遵循 這些規則本文仍須遵守本文件,

目標

ABI 穩定性

部分配備穩定 ABI 的 Fuchsia 介面將以 C 形式發布 程式庫這份文件的目標之一是協助 Fuchsia 輕鬆上手 編寫及維護穩定的 ABI因此,我們 最好不要使用 C 語言提供的某些功能 可能會產生令人意外或複雜影響 存取 API我們也禁止非標準編譯器擴充功能 無法假設第三方使用任何特定編譯器 以下列舉幾個 DDK 的例外狀況。

資源管理

本文件的組成部分說明資源管理的最佳做法 。包括資源、Zircon 控點和任何其他類型的 資源。

標準化

我們也希望對 Fuchsia C 採用合理一致的標準 程式庫特別是命名配置。Out 參數 排序也是標準化的另一個例子

FFI 適用性

外部函式介面 (FFI) 會耗費一些心力 瀏覽。許多非 C 語言支援 C 介面。 這些 FFI 系統的複雜程度差異不大, 升級至精密的 libclang 工具。有一些 我們在製定這些決策時考量了 FFI 相容性的考量因素。

語言版本

C

Fuchsia C 程式庫是根據 C11 標準編寫 (使用 支援多種例外狀況 (例如 Unix 信號支援) 尤其與我們的 C 程式庫 ABI 密切相關)。C99 法規遵循並不是 目標。

特別是 Fuchsia C 程式碼可以使用 <threads.h> 和 C11 標準程式庫的 <stdatomic.h> 標頭,以及 _Thread_local和對齊語言功能。

執行緒本機應該使用 thread_local 拼寫 <threads.h>,而非內建的 _Thread_local。同樣地 偏好來自<stdalign.h>alignasalignof,而非 _Alignas_Alignof

請注意,編譯器支援的標記可能會變更 再也不是件繁重乏味的工作舉例來說,GCC 的 -m96bit-long-double 旗標會修改 長雙精度浮點數的大小我們假定您並沒有使用這類標記。

最後,我們的 IDK 結合了外部定義的介面和 Fuchsia 專屬 。在這些情況下,我們允許某些暴力內容。舉例來說: libc 定義了 thrd_get_zx_handle 等函式,以及 dlopen_vmo。這些名稱並非完全遵循 規則: 程式庫的名稱不是前置字串。這麼做 比較容易將這些名稱與其他函式搭配使用,例如 thrd_currentdlopen,因此允許例外狀況。

C++

雖然 C++ 不是 C 確切的超集,但我們仍會設計 C 程式庫 可以從 C++ 存取Fuchsia C 標頭應與 C++11、C++14 和 C++17 標準。尤其是函式 宣告必須為 extern "C",如下所述。

請勿在單一標頭中混用 C 和 C++ 介面。 建立獨立的 cpp 子目錄,並將 C++ 介面放置在其位置 自己的標頭

程式庫版面配置和命名

Fuchsia C 程式庫有名稱。這個名稱會決定其中包含路徑 (如程式庫命名文件所述) 和 ID 程式庫中的各個選項

在本文件中,程式庫一律會命名為 tag,且在各種情況下 稱為 tagTAGTagkTag,代表 特殊的詞典慣例tag 必須是單一 ID 不要加上底線代碼的格式為全小寫的 規則運算式 [a-z][a-z0-9]*。只要使用較短的標記取代標記 程式庫名稱版本,例如 zx 而不是 zircon

標頭 foo.h 的 include 路徑,如程式庫所述 命名文件應為 lib/tag/foo.h

標題版面配置

C 程式庫中的單一標頭包含幾種內容。

  • 著作權橫幅
  • 標頭防護
  • 檔案納入項目清單
  • 外部 C 防衛
  • 常數宣告
  • 外部符號宣告
    • 包含外部函式宣告
  • 靜態內嵌函式
  • 巨集定義

防潮

在標頭中使用 #ifndef guard。如下所示:

#ifndef SOMETHING_MUMBLE_H_
#define SOMETHING_MUMBLE_H_

// code
// code
// code

#endif // SOMETHING_MUMBLE_H_

該定義的確切形式如下:

  • 以標準 include 路徑指向標頭
  • 將所有的「.」、「/」和「-」取代為「_」
  • 將所有字母轉換為大寫
  • 加上結尾的 _

例如,位於 SDK lib/tag/object_bits.h 中的標頭 應包含標頭保護 LIB_TAG_OBJECT_BITS_H_

包容

並在標題中加入實際使用的內容。尤其是任何公開標頭 包含在來源檔案之中。

程式庫可以依附 C 標準程式庫標頭。

有些程式庫也可能依附一部分 POSIX 標頭。等於 是否適合待後續的 libc API 審查。

常數定義

程式庫中的大部分常數都是編譯時間常數, 透過 #define。您還可以使用唯讀變數,透過宣告 extern const TYPE NAME;,因為有時如果需要 表示常數 (尤其是某些形式的 FFI)。這個區段 說明如何在標頭中提供編譯時間常數。

編譯時間常數有多種類型。

  • 單一整數常數
  • 列舉整數常數
  • 浮點常數

單一整數常數

單一整數常數在程式庫 TAG 中有一些 NAME, 定義如下所示

#define TAG_NAME EXPR

其中 EXPR 為下列任一格式 (uint32_t)

  • ((uint32_t)23)
  • ((uint32_t)0x23)
  • ((uint32_t)(EXPR | EXPR | ...))

列舉整數常數

已知程式庫中有一組名為 NAME 的列舉常數 TAG,相關編譯時間常數組合包含下列部分。

首先, typedef 用於提供類型名稱、大小和 簽署。typedef 必須是明確大小的整數 類型。例如,如果您使用 uint32_t

typedef uint32_t tag_name_t;

每個常數都會有以下形式:

#define TAG_NAME_... EXPR

其中 EXPR 是少數編譯時間整數的類型之一 常數 (一律以括號括住):

  • ((tag_name_t)23)
  • ((tag_name_t)0x23)
  • ((tag_name_t)(TAG_NAME_FOO | TAG_NAME_BAR | ...))

請勿加入值的數量 (難以維護) 常數集會不斷成長

浮點常數

浮點常數與單一整數常數類似, 只是使用其他機制來描述型別浮點值 常數結尾必須是 fF;雙常數沒有後置字串; 長雙常數的結尾必須是 lL。十六進位版本 可以使用浮點常數。

// A float constant
#define TAG_FREQUENCY_LOW 1.0f

// A double constant
#define TAG_FREQUENCY_MEDIUM 2.0

// A long double constant
#define TAG_FREQUENCY_HIGH 4.0L

函式宣告

函式宣告的名稱皆應以 tag_... 開頭。

函式宣告應置於 extern "C" 守衛中。這些 正透過 __BEGIN_CDECLS 和 來自 compiler.h__END_CDECLS 巨集。

函式參數

您必須為函式參數命名,例如:

// Disallowed: missing parameter name
zx_status_t tag_frob_vmo(zx_handle_t, size_t num_bytes);

// Allowed: all parameters named
zx_status_t tag_frob_vmo(zx_handle_t vmo, size_t num_bytes);

因此應清楚說明要使用哪些參數,以及哪些參數 借用。避免使用用戶端可能擁有的 呼叫函式之後的資源如果無法這麼做,請考慮 函式名稱或其 參數。例如:

zx_status_t tag_frobinate_subtle(zx_handle_t foo);
zx_status_t tag_frobinate_if_frobable(zx_handle_t foo);
zx_status_t tag_try_frobinate(zx_handle_t foo);
zx_status_t tag_frobinate(zx_handle_t maybe_consumed_foo);

按照慣例,參數會在函式簽章中最後存在,且 名稱應為 out_*

血管函式

應避免為如 printf 以外的所有項目避免使用 Variadic 函式 函式。這些函式應記錄自己的格式字串 與 compiler.h__PRINTFLIKE 屬性簽訂合約。

靜態內嵌函式

允許使用靜態內嵌函式,且最好 類似函式的巨集僅限內嵌 (也不是 static) C 函式有複雜的連結規則和極少用例。

類型

建議明確大小的整數類型 (例如 int32_t) 不含明確大小的類型 (例如 intunsigned long int)。一個 參照 POSIX 檔案描述元時,int 將不受限制。 以及 C 或 POSIX 標頭中的 size_t 類型定義。

在可能的情況下,介面中提及的指標類型應參照 特定類型的物件這包括不透明結構的指標。void* 是 可接受,用來參照原始記憶體,以及如果介面為 不透明的使用者 Cookie 或背景資料

不透明/煽情露骨類型

定義不透明結構時,建議使用 void*。不透明 結構體應宣告如下:

typedef struct tag_thing tag_thing_t;

公開的結構應宣告如下:

typedef struct tag_thing {
} tag_thing_t;

保留欄位

結構中的所有保留欄位都應記錄為用途 。

本文件日後將提供指引 說明 C 介面中的字串參數。

匿名類型

不允許使用頂層匿名類型。匿名建築 聯集可以在其他結構內,或在函式內使用 主體,因為之後不屬於頂層命名空間。適用對象 例項,以下範例包含允許的匿名聯集。

typedef struct tag_message {
    tag_message_type_t type;
    union {
        message_foo_t foo;
        message_bar_t bar;
    };
} tag_message_t;

函式 typedefs

允許使用函式類型的 Typedef。

使用 zx_status_t 的函式不應超載傳回值 成功和正成功值函式不應超載 傳回值的 zx_status_t,其中含有不包含的其他值 如 zircon/errors.h 中所述。

狀態退貨

偏好使用 zx_status_t 做為傳回值,以說明與下列項目相關的錯誤: Zircon 原始物件和 I/O。

資源管理

程式庫可在多種資源中投放流量。記憶體與 Zircon 是許多通用資源的例子 程式庫程式庫也可以定義自己的資源 需要管理的生命週期

所有資源的擁有權應明確定義。轉乘 資源應在函式名稱中明確表示例如: createtake 負責轉移擁有權的函式。

程式庫應保持緊密的記憶體。下列函式分配的記憶體 tag_thing_create 應透過 tag_thing_destroy 或部分 而非透過 free

程式庫不應公開全域變數。而是改為提供 運用函式操控該狀態具有 process-global 的程式庫 狀態必須是動態連結,而非靜態連結。常見模式是 可將程式庫分割為無狀態的靜態部分 以及含有全域狀態的小型動態程式庫

特別是 errno 介面,其為全域執行緒本機 「全域」) 應避免使用新的程式碼。

連結

程式庫中的預設符號顯示設定應設為隱藏。使用 匯出符號的許可清單,或明確顯示 要匯出的符號註解。

C 程式庫不得匯出 C++ 符號。

演化

淘汰

已淘汰的函式應標示 __DEPRECATED 屬性 來自 compiler.h。觀眾也應該要加上說明註解 建議改成做什麼,以及如何追蹤淘汰情形。

不允許或令人反感的用語功能

本節說明的語言功能不得或不應 用在介面中的 C 語言程式碼 然後阻止它們

列舉

C 列舉已遭禁止。他們從 ABI 的角度來看而有條不紊。

  • 用來代表列舉類型常數的整數大小為 會影響編譯器 (和編譯器標記)。
  • 列舉的帶正性相當脆弱,因為將負值 列舉會變更基礎類型。

比特田

C 的 Bitfields 遭禁止。它們從 ABI 的角度來看而易用。 有許多不直覺的銳利邊緣

請注意,這適用於 C 語言功能,不適用於 會顯示位元旗標C 位元功能如下所示:

typedef struct tag_some_flags {
    // Four bits for the frob state.
    uint8_t frob : 4;
    // Two bits for the grob state.
    uint8_t grob : 2;
} tag_some_flags_t;

我們建議以編譯時間整數的形式公開位元標記 常數。

空白參數清單

C 允許函式 with_empty_parameter_lists()functions_that_take(void) 不同。第一個意思是「以 第二種則是「從零開始」 參數。由於空白參數清單有危險,我們會予以禁止。

彈性陣列成員

這個 C99 功能可讓您宣告不完整的陣列, 結構中最後一個成員,其中包含多個參數。例如:

typedef struct foo_buffer {
    size_t length;
    void* elements[];
} foo_buffer_t;

不過,如果是 DDK 結構,則在下列情況中,可以使用這個模式: 參照符合此 Header-plus-payload 的外部版面配置 。

宣告 0 大小陣列成員的 GCC 同樣是

模組對應

這些是 Clang 擴充功能的一部分,旨在嘗試以類似 C 語言的方式 其中許多與標頭導向編譯有關的問題紫紅色時 我們很有可能未來會投資這類工具 我們目前不支援這些格式

編譯器擴充功能

從定義上來說,無法跨工具鍊遷移。

這尤其包括封裝的屬性或碎片, 因為 DDK 的例外狀況

DDK 結構通常反映了不相符的外部版面配置 執行 ABI 測試舉例來說,它可能會參照 較不合乎語言規定可以透過以下程式碼形式表示: 例如 pragma pack