RFC-0045:零大小的空結構體 | |
---|---|
狀態 | 已遭拒 |
區域 |
|
說明 | 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++ 的各種警告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
成員並不會造成太大影響。如果結構體稍後變更為非空,包含的結構體可以以來源相容的方式 3 發出先前為空的結構體成員 options
。
使用者可能會想要執行的合理操作之一,就是取得空結構體的位址,也就是 &(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/