C 库可读性评分准则

本文档介绍了编写 C 库的启发法和规则 发布在 Fuchsia SDK 中的。

系统会为 C++ 库编写另一个文档。虽然 C++ 是 几乎是 C 语言的扩展,且对本文档有一定影响, 编写 C++ 库的模式与编写 C 的模式截然不同。

本文档的大部分内容都与 C 头文件中的接口。这不是完整的 C 语言风格指南, 关于 C 源文件的内容。它也不是 文档评分准则(尽管公共接口应该 记录)。

一些 C 库具有与这些库相冲突的外部约束条件, 规则。例如,C 标准库本身并不遵循 这些规则在适用的情况下,仍应遵循本文档。

目标

ABI 稳定性

一些具有稳定 ABI 的 Fuchsia 接口将以 C 格式发布 库。本文档的目标之一是让 Fuchsia 能够 开发者可编写和维护稳定的 ABI。相应地, 不建议使用 可能会对应用的 ABI 产生出乎意料或复杂的影响 界面。我们还禁止非标准编译器扩展 不能假设第三方使用任何特定的编译器, 但在下文所述的 DDK 中有一些例外情况。

资源管理

本文档的某些部分介绍了资源管理的最佳做法 语言函数。这包括资源、Zircon 句柄以及任何其他类型的 资源。

标准化

我们还希望对紫红色 C 采用合理统一的标准 库。在命名方案中尤其如此。Out 参数 排序是另一个标准化示例。

外国金融机构友好程度

相当关注外部函数接口 (FFI) 友好程度。许多非 C 语言都支持 C 接口。通过 这些 FFI 系统的复杂程度差异很大,从基本层面 迁移到基于 libclang 的复杂工具。一些 在这些决策中,

语言版本

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 标志,用于将 和长双精度的大小一样大我们假定不使用此类标志。

最后,我们的一些库(如 Fuchsia 的 C 标准库) IDK 包含外部定义的接口和 Fuchsia 专属接口 。在这些情况下,我们允许一些实用主义。例如, libc 定义了 thrd_get_zx_handledlopen_vmo。这些名称并未严格遵守 库的名称不是前缀。否则 会降低名称与其他函数之间的距离 thrd_currentdlopen,因此允许例外情况。

C++

虽然 C++ 并不完全是 C 的超集,但我们仍然会设计 C 库 可通过 C++ 使用。Fuchsia C 头文件应与 C++11、C++14 和 C++17 标准。具体来说,函数 必须为 extern "C",如下所述。

C 和 C++ 接口不应混合在一个头文件中。相反, 创建一个单独的 cpp 子目录,并将 C++ 接口放在其 标头。

库布局和命名

Fuchsia C 库已经有了名称。此名称决定其包含路径 (如库命名文档中所述)以及标识符 资源库内的资源

在本文档中,该库始终命名为 tag,其名称各不相同, (称为 tagTAGTagkTag),以反映 特定的词法惯例。tag 应该是单个标识符 不加下划线。代码的全小写形式由 正则表达式 [a-z][a-z0-9]*。可将标签替换为较短的 库名称的版本,例如 zx,而不是 zircon

标头 foo.h 的包含路径,如 库所述 命名文档,应为 lib/tag/foo.h

标题布局

C 库中的单个头文件包含几种类型的内容。

  • 版权横幅
  • 头球后卫
  • 包含的文件列表
  • 外部 C 后卫
  • 常量声明
  • 外部符号声明 <ph type="x-smartling-placeholder">
      </ph>
    • 包括外部函数声明
  • 静态内联函数
  • 宏定义

头球防护

在标头中使用 #ifndef 防护程序。如下所示:

#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 应为明确大小的整数 类型。例如,如果使用 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 参数位于函数签名的最后,而 应命名为 out_*

可变函数

应避免在除 printf 类之外的任何内容中都使用可变函数 函数。这些函数应记录其格式字符串 与 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;

函数类型定义符

允许使用函数类型的类型定义符。

函数不应在启用 zx_status_t 的情况下使返回值过载 值为正的成功值。函数不应过载 带有 zx_status_t 的返回值,但其中包含不包含 zircon/errors.h 中说明。

状态返回

首选 zx_status_t 作为返回值,用于描述与 Zircon 基元以及 I/O。

资源管理

库可以在多种资源中传送。内存和锆石 标识名是许多资源中的通用资源 库。库还可以通过 生命周期管理。

所有资源的所有权都应该明确。转入 函数名称中应明确显示资源。例如: createtake 表示转让所有权的函数。

库应该占用内存。由类似函数分配的内存 tag_thing_create 应通过 tag_thing_destroy 或某些 API 释放 而不是通过 free

库不应公开全局变量。而应提供 函数来操控该状态。具有 process-global 的库 状态必须是动态关联,而不是静态关联。一种常见的模式是 将库拆分为无状态静态部分, 代码以及保存全局状态的小型动态库。

特别是,errno 接口(这是一个全局线程局部 全局),而不是在新代码中。

关联

库中的默认符号可见性应处于隐藏状态。使用 导出符号的许可名单,或显式可见性 要导出的符号上的注释。

C 库不得导出 C++ 符号。

进化

弃用

已弃用的函数应使用 __DEPRECATED 属性进行标记 从 compiler.h 开始。还应该对它们进行评论并附上说明 以及一个跟踪废弃情况的 bug。

禁止或不建议使用的语言功能

本部分介绍了不允许或不应该使用的语言功能 Fuchsia 的 C 库接口中用到的,以及 决定着禁止这些行为的根本原因。

枚举

禁止使用 C 枚举。从 ABI 的角度来看,它们非常脆弱。

  • 用于表示枚举类型的常量的整数大小为 取决于编译器(和编译器标记)。
  • 枚举的符号性很脆弱,因为如果将负值添加到 枚举可以更改基础类型。

位字段

C 的位字段已被禁止。从 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 结构可以使用此格式: 引用了适合此标头加载荷的外部布局 模式。

声明大小为 0 的数组成员的类似 GCC 扩展为 同样禁止。

模块映射

这些是针对类似 C 语言的 Clang 扩展的一部分,旨在解决 许多头文件驱动型编译的问题虽然紫红色 工具链团队将来很有可能在这方面进行投资, 目前不支持它们。

编译器扩展

根据定义,这些是不可跨工具链移植的。

尤其是打包的属性或 pragma,其中含有 DDK 例外情况。

DDK 结构通常反映的是外部布局, 系统 ABI。例如,它可能表示 未达到语言要求的一致性。这可以通过 编译器扩展,例如 pragma pack。