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

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

RFC-0056(“空结构体”)允许定义空结构体,改进了语言工效学设计。空结构体没有任何内容,但目前会在传输格式中占用一个字节,以便与所有 FIDL 语言实现兼容。这会占用不必要的空间,由于 FIDL 在许多上下文中采用 8 字节对齐,通常会导致性能更差。

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

遭拒的理由

由于工作优先级较高且影响较小,此 RFC 已被其作者拒绝。如果以后的想法最终能产生更大的影响,我们可以稍后重新审视这一点。

因此,该部分内容并不完整。

总结

RFC-0056(“空结构体”)可以定义空结构体,从而改进了语言工效学设计。空结构体没有任何内容,但它们目前占用一个有线格式字节,以便在所有 FIDL 语言实现中兼容。这会占用不必要的空间,由于 FIDL 在许多上下文中采用八字节对齐方式,通常会导致性能更差。

此 RFC 基于 RFC-0056 构建,通过增强空结构体,使其在线占用零字节。

设计初衷

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

  • planned future use,
  • 作为一个选项
  • 作为命令模式的一部分

除了携带零信息且在网络上占用非零空间的一般不规则性之外,高效实现空结构体对于未来潜在的 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;

上面的代码段同时针对 C 和 C++ 向 sizeof(Empty) == 0 断言了 sizeof(Empty) == 0。在实际情况中,为绑定生成的代码应关闭针对 C 和 C++ 的各种警告1GitHub Gist 展示了生成的 C 和 C++ 绑定的更完整示例。

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

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

  • C 未定义空结构体的大小2。 许多编译器(例如,gccclang)将一个空结构体定义为大小为零
  • 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 成员几乎不会产生任何后果。如果结构体之后变为非空,包含的结构体可以以与源兼容的方式发出以前空的结构体成员 options3

人们可能希望采取一种合理的操作,即获取一个空结构体的地址(即 &(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++ [[no_unique_address]]

早期技术和参考资料

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


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

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

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