| RFC-0045:零大小的空结构体 | |
|---|---|
| 状态 | 已拒绝 |
| 区域 |
|
| 说明 | RFC-0056(“空结构体”)通过允许定义空结构体来改进语言工效学。空结构体不包含任何内容,但它们目前在网络格式中占用一个字节,以便与所有 FIDL 语言实现兼容。这会占用不必要的空间,并且在许多情况下,由于 FIDL 的 8 字节对齐,这种情况通常会变得更糟。 |
| 作者 | |
| 提交日期(年-月-日) | 2018-12-26 |
| 审核日期(年-月-日) | 2019-05-29 |
拒绝理由
此 RFC 已被作者拒绝,原因是其优先级较高且影响较小。 如果此 RFC 中的想法在未来产生更大的影响,我们可以稍后重新审视此 RFC。
因此,某些部分不完整。
摘要
RFC-0056(“空结构体”)通过 允许定义空结构体来改进语言工效学。 空结构体不包含任何内容,但它们目前在网络格式中占用一个字节,以便与所有 FIDL 语言实现兼容。 这会占用不必要的空间,并且在许多情况下,由于 FIDL 的 8 字节对齐,这种情况通常会变得更糟。
此 RFC 以 RFC-0056 为基础,通过增强空结构体使其在网络上占用零字节。
设计初衷
RFC-0056 确定了空结构体的用例:
- 计划在未来使用,
- 作为联合中的一个选项,
- 作为命令 模式的一部分,
除了在网络上占用非零空间的对象携带零信息的一般不雅之外,空结构体的有效实现对于潜在的未来 FIDL 工作(例如代数数据类型或泛型)可能非常重要。
大小为 1 的空结构体设计还会让每个其他 FIDL 目标语言 付出 C++ 特有的代价(如需了解详情,请参阅下面的“设计”部分)。 其他语言通常可以用零字节表示空结构体。
设计
两种设计:一种选择。我们必须选择一种。
我更喜欢设计 1,即使用零长度数组来表示空结构体。 对于用户来说,这不太令人意外,并且在所有用例中都是统一且一致的。缺点是我们需要 C 扩展,这可能无法接受。
可以采用设计 2,但当空结构体用作 FIDL 方法的形参时,工效学方面可能会令人意外。我很想听听有关这方面的想法和反馈。
如果编译器支持零长度数组,我们也可以采用设计 1;如果不支持,则采用设计 2。我有点喜欢这种方式。
设计 1:零长度数组
此 RFC 建议使用零长度数组来表示空结构体。 这是 FIDL 的目标 C 和 C++ 编译器支持的常用扩展。
对于 C 和 C++,简化示例看起来类似:
// FIDL: struct Empty {};
// define a zero-length array type
typedef int FIDLEmptyStructInternal[0];
typedef struct {
FIDLEmptyStructInternal reserved;
} Empty;
上面的代码段断言 sizeof(Empty) == 0 对于 C 和 C++ 都是如此。
实际上,为绑定生成的代码应关闭各种警告
对于 C 和 C++1;Github Gist 展示了更完整的
C 和 C++ 绑定生成示例。
设计 2:完全省略发出空结构体
FIDL 结构体必须转换为 C 和 C++ 中的等效结构体。空结构体是一种特殊情况,因为 C 和 C++ 在处理空结构体的方式上有所不同:
- C 将空结构体的大小留作未定义2.
因此,许多编译器(例如
gcc和clang)将空 结构体定义为 零大小。 - C++ 将空结构体定义为大小为 1。 大多数编译器都采用“空基类 ”优化,以便在某些情况下将其优化为 0。
作为一种解决方法,RFC-0056 建议生成一个包含单个 uint8 成员的结构体来表示空结构体,这在 C 和 C++ 中是一致的。
空结构体出现在三种不同的上下文中:
- 在包含结构体中,
- 在包含联合或表中,或者
- 作为“顶级”结构体,即作为 FIDL 接口方法的形参。
这三种上下文可以单独处理。
在包含结构体中
包含结构体中的空结构体可以省略空结构体的成员。 例如,此 FIDL 结构体:
// FIDL
struct FooOptions {
// There is currently no information here but
// we have preserved the plumbing for now.
};
struct Foo {
uint32 before;
FooOptions options;
uint32 after;
};
可以简单地省略在 C/C++ 绑定中生成“FooOptions”空结构体成员:
// generated C or C++ code
struct Foo {
uint32_t before;
// "FooOptions options" is missing here
uint32_t after;
};
然后,序列化的 FIDL 网络格式与 C/C++ 内存布局兼容,并且可以直接转换为任一格式或从任一格式转换。
由于空结构体不包含任何信息,因此无法访问 .options 成员的影响很小。
如果结构体稍后更改为非空,则包含结构体可以
以源代码兼容的方式发出以前为空的结构体成员 options 3。
人们可能希望执行的一项合理操作是获取空结构体的
地址,即 &(foo.options),但此更改后将无法再执行此
操作。
我们认为,对于一致的跨语言零大小空结构体,这是一个可以接受的权衡。
TODO(apang):Go、Rust、Dart。
在包含表或联合中
表或(静态或可扩展)联合具有序号(“标记”),用于指示表/联合携带的信息。在这种情况下,空结构体“携带信息”,因为它的存在表示信息,即使空结构体本身不携带任何信息也是如此。
因此,表或联合仍会发出序号,以便客户端代码可以检查该序号以确定表/联合中包含哪些信息。但是,空结构体本身将无法访问。 例如,空结构体的联合:
// FIDL
struct Option1 {};
struct Option2 {};
union {
// an "empty" union!
Option1 o1;
Option2 o2;
};
仍然会:
- 具有明确定义的内存布局,其中将包含单个
uint32枚举标记。 - 发出表示序号和相应访问器方法的枚举,以便客户端代码可以创建和检查此类联合。
TODO(apang):添加 C/C++ 绑定示例。
表类似:空结构体作为表字段的存在表示信息。 对于联合,可以采用相同的方法:为序号和客户端代码发出枚举,但省略对空结构体的访问。
TODO(apang):Go、Rust、Dart。
作为 FIDL 方法形参
在现有用例中,空结构体用作 FIDL 方法形参,例如在 fuchsia.ui.viewsv1 中:
interface ViewContainerListener {
// |ViewInfo| is an empty struct
OnChildAttached(uint32 child_key, ViewInfo child_view_info) -> ();
};
struct ViewInfo {};
任何作为空结构体的方法形参都可以:
- 从方法签名中省略(推荐),
- 在 C 或 C++ 中规范化为单个空结构体单例类型(例如
fidl::EmptyStruct),该类型不会直接映射到零字节网络格式,或者 - 按原样发出,其语言表示形式不会直接编码/解码为网络格式。
变更
- 无需更改 FIDL 源语言。
- FIDL 网络格式和文档将会更改,以便空结构体在网络上占用 0 字节,而不是 1 字节。
fidlc无需更改。- 每个语言后端(C、C++、Rust、Go、Dart)都需要更新,以反映本部分讨论的绑定更改。 这应作为硬转换完成,以便保留跨层 ABI 兼容性。
实现策略
实现将与 RFC-0056 类似, 并且需要拆分为多个 CL:
- CL 用于更新所有语言的生成绑定,而不更新跨语言兼容性测试。
- 硬转换集成 CL,以确保滚轮成功。
- 更新跨语言。
- 更新。
对于此 RFC,无需更改 FIDL 源语言。
缺点、替代方案和未知因素
请注意:
- 这在 C 和 C++ 中是一致的
- 这两种语言具有不同的尺寸实现
- C++ 在名义上要求
- 零长度数组
- C++ [[no_unique_address]]
在先技术和参考资料
https://herbsutter.com/2009/09/02/when-is-a-zero-length-array-okay/