RFC-0045:零大小的空白結構

RFC-0045:零大小的空結構體
狀態已遭拒
區域
  • FIDL
說明

RFC-0056 (「空結構體」) 可定義空結構體,進而改善語言人體工學。空結構體不含任何內容,但目前在線格式中會佔用一個位元組,以便與所有 FIDL 語言實作相容。這會使用不必要的空間,而這通常會因 FIDL 在許多情境中的八位元對齊而變得更糟。

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

拒絕理由

由於此 RFC 優先順序較低且影響力不大,因此已遭作者拒絕。如果這次的想法日後對使用者造成更大的影響,我們會在日後重新討論這個問題。

因此,部分部分不完整。

摘要

RFC-0056 (「空結構體」) 可定義空結構體,進而改善語言的使用體驗。空結構體不含任何內容,但目前在線格格式中會佔用一個位元組,以便與所有 FIDL 語言實作相容。這會使用不必要的空間,而這通常會因 FIDL 在許多情境中的八位元對齊而變得更糟。

這份 RFC 以 RFC-0056 為基礎,強化空結構體,讓其在網路上占用零位元組。

提振精神

RFC-0056 指出空結構體的用途:

  • 預計日後使用,
  • 做為聯集中的選項,
  • 做為指令模式的一部分,

除了物件沒有任何資訊,卻占用非零量網路空間的一般不雅觀性之外,空結構體的有效實作對未來可能的 FIDL 工作 (例如代數資料類型泛型) 而言也相當重要。

大小為 1 的空結構設計也會讓其他 FIDL 目標語言支付 C++ 專屬的成本 (詳情請參閱下方的「設計」)。其他語言通常可以用零位元組表示空 struct。

設計

兩種設計:一個選擇。我們必須做出選擇。

我偏好設計 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。實際上,產生的繫結程式碼應關閉 C 和 C++ 的各種警告1Github Gist 會顯示產生的 C 和 C++ 繫結的完整範例。

設計 #2:完全省略空白結構體的產生

FIDL 結構體必須轉換為 C 和 C++ 中的等同結構體。空結構體屬於特殊情況,因為 C 和 C++ 處理空結構體的方式不同:

因應這個問題,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 成員並不會造成太大影響。如果結構體稍後變更為非空,包含的結構體可以以來源相容的方式 3 發出先前為空的結構體成員 options

使用者可能會想要執行的合理操作之一,就是取得空結構體的位址,也就是 &(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 (-Wc++-compat 適用於 C,-Wextern-c-compat 適用於 C++)。警告範圍可使用一般支援的 #pragma 診斷推送/忽略/彈出編譯器指示,讓警告只套用於空結構體程式碼。 

  2. C99,6.7.2.1:[...] 如果 struct-declaration-list 不包含任何命名成員,則行為未定義。 

  3. 請注意,結構體的大部分變更都會造成 ABI 變更。