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 工作也很重要,例如代數資料型別泛型

大小為一的空結構體設計也會導致其他 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。在實務上,繫結產生的程式碼應關閉 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 成員的影響不大。如果結構體稍後變更為非空白,包含的結構體可以來源相容的方式發出先前空白的結構體成員 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++ [[no_unique_address]]

既有技術和參考資料

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


  1. ,其中 C 為 -Wc++-compat,C++ 為 -Wextern-c-compat。警告可使用常見的 #pragma 診斷推送/忽略/彈出編譯器指令設定範圍,因此警告只會套用至空白結構體程式碼。-Wzero-length-array 

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

  3. 請注意,結構體的大部分變更都會導致 ABI 中斷。