本文档介绍了编写 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>
中的 alignas
和 alignof
,而非
_Alignas
和 _Alignof
。
请注意,编译器支持可能会改变
代码。例如,GCC 有一个 -m96bit-long-double
标志,用于将
和长双精度的大小一样大我们假定不使用此类标志。
最后,我们的一些库(如 Fuchsia 的 C 标准库)
IDK 包含外部定义的接口和 Fuchsia 专属接口
。在这些情况下,我们允许一些实用主义。例如,
libc 定义了 thrd_get_zx_handle
和
dlopen_vmo
。这些名称并未严格遵守
库的名称不是前缀。否则
会降低名称与其他函数之间的距离
thrd_current
和 dlopen
,因此允许例外情况。
C++
虽然 C++ 并不完全是 C 的超集,但我们仍然会设计 C 库
可通过 C++ 使用。Fuchsia C 头文件应与
C++11、C++14 和 C++17 标准。具体来说,函数
必须为 extern "C"
,如下所述。
C 和 C++ 接口不应混合在一个头文件中。相反,
创建一个单独的 cpp
子目录,并将 C++ 接口放在其
标头。
库布局和命名
Fuchsia C 库已经有了名称。此名称决定其包含路径 (如库命名文档中所述)以及标识符 资源库内的资源
在本文档中,该库始终命名为 tag
,其名称各不相同,
(称为 tag
、TAG
、Tag
或 kTag
),以反映
特定的词法惯例。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 | ...))
不要包含值的计数,因为该值很难维护,因为 该常量集会增大。
浮点常量
浮点常量与单个整数常量类似,
只是会使用不同的机制来描述类型。浮点数
常量必须以 f
或 F
结尾;双常量没有后缀;
长双精度常量必须以 l
或 L
结尾。的十六进制版本
允许使用浮点数常量。
// 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
),以便
大小未指定的类型(例如 int
或 unsigned 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。
资源管理
库可以在多种资源中传送。内存和锆石 标识名是许多资源中的通用资源 库。库还可以通过 生命周期管理。
所有资源的所有权都应该明确。转入
函数名称中应明确显示资源。例如:
create
和 take
表示转让所有权的函数。
库应该占用内存。由类似函数分配的内存
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。