RFC-0045:零大小的空结构体

RFC-0045:大小为零的空结构体
状态已拒绝
领域
  • FIDL
说明

RFC-0056(“空结构体”)支持定义空结构体,从而改进了语言工效学设计。空结构体不携带任何内容,但它们目前在传输格式中占用一个字节,以便兼容所有 FIDL 语言实现。这会占用不必要的空间,由于 FIDL 在许多上下文中采用 8 字节对齐方式,空间通常会变得更糟。

作者
提交日期(年-月-日)2018-12-26
审核日期(年-月-日)2019-05-29

遭拒原因

由于工作优先级较高,此 RFC 已被作者拒绝,并且 影响很小 如果本视频中的主意 未来产生的影响。

因此,某些部分不完整。

摘要

RFC-0056(“Empty Structs”)通过 定义空结构体。 空结构体不携带任何内容,但它们目前在 线上格式与所有 FIDL 语言实现兼容。 这会占用不必要的空间,由于 FIDL 8 字节对齐。

此 RFC 以 RFC-0056 为基础而构建,增强了空结构体以占据零字节 电线。

设计初衷

RFC-0056 用于标识空结构体的用例:

  • 按计划未来使用
  • 表示为联盟中的一种选项
  • 作为命令 图案

除了一个不含任何信息的对象普遍不公平之外 占用非零的电线空间, 空结构体对于未来潜在的 FIDL 工作非常重要,例如 代数数据类型泛型

size-one 空结构体设计也会使其他所有 FIDL 目标语言 支付特定于 C++ 的费用(如需了解详情,请参阅下文的设计部分) 详情)。 其他语言通常可以表示包含零字节的空结构体。

设计

两种设计:任一种选择。我们得选择。

我更喜欢设计 1,使用零长度的数组来表示空的 结构体。 这种做法对于用户来说没有那么惊讶 应用场景。 但缺点是,我们需要使用 C 扩展名,这可能是不可接受的。

设计 2 可以完成,但当设计 空结构体会用作 FIDL 方法的参数。 我很想听一听这方面的一些想法和反馈。

如果编译器支持零长度数组,我们还可以假设设计 1, 如果不是,则设计 2我挺喜欢的。

设计 1:零长度数组

此 RFC 提议使用长度为零的数组来表示一个空结构体。 这是 FIDL 目标所支持且普遍支持的扩展 C 和C++ 编译器。

对于 C 和 C 的简化示例,C++:

// FIDL: struct Empty {};

// define a zero-length array type
typedef int FIDLEmptyStructInternal[0];

typedef struct {
  FIDLEmptyStructInternal reserved;
} Empty;

上述代码段针对 C 和sizeof(Empty) == 0C++。 实际上,为绑定生成的代码应该会关闭各种警告 适用于 C 和 C++1GitHub Gist显示了 生成的 C 和C++ 绑定。

设计 2:完全省略发出空结构体

必须将 FIDL 结构体转换为 C 和 C++ 中的等效结构体。 空结构体是一种特殊情况,因为 C 和 C++ 处理它们的方式不同 空结构体:

  • C 使空结构体的大小未定义2。 许多编译器(例如,gccclang)来定义 是 零大小
  • C++ 定义了空结构体,大小为 1。 “空基数 类”优化为 大多数编译器使用,在某些特定环境下 情况。

为了解决此问题,RFC-0056 建议生成具有单个 uint8 成员来表示一个空结构体,这在 C 语言中是一致的 和 C++。

在三种不同的上下文中,空结构体会出现:

  • 位于包含结构体内,
  • 或位于包含性联合或表中的内部
  • 作为“顶级”通过成为 FIDL 接口的参数来实现 struct 方法。

这三种上下文可以单独处理。

在容器结构体内部

父结构体内的空结构体可以包含 已省略空结构体。 例如,以下 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;
};

而不必生成“FooOptions”空结构成员 C/C++ 绑定:

// 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;
};

我仍然会:

  1. 具有明确定义的内存布局,该布局将包含 uint32 枚举标记。
  2. 发出表示序数和相应访问器方法的枚举, 以便客户端代码可以创建和检查此类联合。

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 {};

任何为空结构体的方法参数可以是:

  1. 方法签名中省略此参数(推荐),
  2. 在 C 或 C++ 中规范化为单个空结构体单例类型(例如, 不直接映射到零字节线的 fidl::EmptyStruct) 格式,或者
  3. 并采用不直接 编码/解码为线缆格式。

变更

  • 无需更改 FIDL 源语言。
  • FIDL 传输格式和文档将会更改, 结构体在线路上占用了 0 个字节,而不是 1 个字节。
  • fidlc”无需任何更改。
  • 每种语言后端(C、C++、Rust、Go、Dart)都需要更新为 反映本节中讨论的绑定更改。 此操作应作为硬过渡完成,以便跨层 ABI 兼容性。

实施策略

实现类似于 RFC-0056, 需要拆分为多个 CL:

  • 用于更新针对所有语言生成的绑定的 CL,而不更新 跨语言兼容性测试
  • 硬转换集成 CL,确保滚轮成功。
  • 更新跨语言。
  • 更新。

无需为此 RFC 更改 FIDL 源语言。

缺点、替代方案和未知问题

请注意:

  • 它在 C 和 C++ 中都是一致的,
  • 这两种语言具有不同的大小实现方式
  • 从概念上讲,C++ 要求
  • 零长度数组
  • C++ [[无唯一地址]]

先验技术和参考资料

https://herbsutter.com/2009/09/02/when-is-a-zero-length-array-okay/


  1. -Wzero-length-array,其中 -Wc++-compat 为 C 和 -Wextern-c-compat 。 您可以使用普遍支持的 #pragma 限定警告的范围 诊断 push/ignored/pop 编译器指令,以便警告应用 空结构体代码。

  2. C99、6.7.2.1:[...] 如果 struct-declaration-list 不包含已命名的 成员,则行为是未定义的。

  3. 请注意,对结构体的大多数更改都是破坏 ABI 的更改。