| RFC-0045:零大小的空結構體 | |
|---|---|
| 狀態 | 已遭拒 |
| 區域 |
|
| 說明 | 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++ 的各種警告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/