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

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

允許定義空白結構體,RFC-0056 (「Empty Structs」) 能夠改善語言人體工學。空白結構中不含任何內容,但目前在傳輸格式中佔用一個位元組,適用於所有 FIDL 語言實作項目。這會使用不必要的空間,通常是因為 FIDL 在許多情況下採用八位元組對齊方式,因此通常會變得更糟。

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

拒絕原因

由於優先順序較高且影響低,因此這份 RFC 已遭作者拒絕。如果產生的想法日後產生更顯著的影響,可以稍後再回頭查看。

因此,部分部分不完整。

摘要

RFC-0056 (「Empty Structs」) 能夠定義空白結構體,藉此提升語言人體工學的效率。空白結構不含任何內容,但目前在 Wi-Fi 格式中佔用一個位元組,以便在所有 FIDL 語言實作上相容。這會使用不必要的空間,通常是因為 FIDL 在許多情況下採用 8 位元對齊方式,通常會變得更加嚴重。

這個 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++ 中保持一致。

有三種不同的結構定義會顯示空白結構:

  • 位於封裝的 struct 中
  • 所屬聯集或資料表內,或
  • 做為「頂層」結構體,是 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、Dat.

包含資料表或聯集內部

資料表或 (靜態或擴充) 聯集具有序數 (「標記」),用來指出資料表/聯集查詢的資訊。在這種情況下,即使空結構體本身沒有任何資訊,一個空白結構也會「載資訊」它,因為其存在代表資訊。

因此,資料表或聯集仍會發出序數,讓用戶端程式碼可以透過檢查判斷資料表/聯集中的資訊。不過,您將無法存取空白的結構體本身。例如,執行空白結構的聯集:

// FIDL
struct Option1 {};
struct Option2 {};
union {
    // an "empty" union!
    Option1 o1;
    Option2 o2;
};

仍會:

  1. 定義明確的記憶體版面配置,其中包含單一 uint32 列舉標記。
  2. 發出列舉,代表序數和適當存取子方法,以便用戶端程式碼建立及檢查這類聯集。

TODO(apang):包含範例 C/C++ 繫結。

資料表也類似:如果資料表欄位出現空白結構,代表資訊。同樣的,您也可以對聯集進行相同做法,也就是產生序數和用戶端程式碼的列舉,但省略存取空白結構體的存取權。

TODO(apang):Go、Rust、Dat.

做為 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) 和 -Wextern-c-compat (C++) 的 -Wextern-c-compat。您可以透過一般支援的 #pragma 診斷推播/忽略/流行編譯器指令設定警示範圍,確保警告僅適用於空白的結構程式碼。

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

  3. 請注意,大部分的結構變更都是 ABI 破壞性變更。