如何設計結構體

之前我寫過一篇《如何設計一個 C++ 的類》,今天這裏繼續聊聊如何設計結構體,注意本文不介紹在 C++ 中結構體和類具體有什麼區別,本文所說的結構體是指只有數據字段不帶任何函數的那種結構體。

當創建結構體的實例時,結構體的數據成員會按其聲明的順序連續存儲。然而,這個聲明的順序也是有學問的,順序不同結構體的大小可能有很大差別,數據成員的訪問性能也可能會有很大區別!

這裏涉及一個概念:內存對齊。關於內存對齊我之前寫過一篇文章:《內存對齊》,這裏不深入討論,只是簡單介紹一下。

大多數編譯器會對齊數據成員,會以四捨五入地址方式來優化數據的訪問,如下表所示。

這種內存對齊可能會在成員大小混合的結構體中產生未使用字節的空洞。

例如:

struct S {
    short int a; // 2字節
    // 6個空洞
    double b; // 8
    int d; // 4
    // 4個空洞
};
S ArrayOfStructures[100];

這裏,在 a 和 b 之間有 6 個未使用的字節,因爲 b 必須從一個能被 8 整除的地址開始。

最後還有 4 個未使用的字節空洞。這樣做的原因是,數組中 S 的下一個實例必須從一個能被 8 整除的地址開始,以便將其 b 成員以 8 對齊。

然而,如果改變一下結構體中數據成員聲明的順序,通過將最小的成員放在最後,未使用的字節數可以減少到 2:

struct S {
    double b; // 8
    int d; // 4
    short int a; // 2
    // 2個空洞
};
S ArrayOfStructures[100];

這種重新排序使結構體變小了 8 個字節,那整個數組則變小了 800 個字節。

在此特性上,類和結構體相同。通過重新排序數據成員,結構體對象和類對象通常可以變得更小。如果類至少有一個虛成員函數,則在第一個數據成員之前或最後一個成員之後會有一個指向虛函數表的指針。該指針在 32 位系統中爲 4 字節,在 64 位系統中爲 8 字節。

如果不確定結構體或它的每個成員有多大,可以使用 sizeof 操作符進行一些測試。sizeof 操作符返回的值包括對象末尾的任何未使用的字節(內存對齊後的字節數)。

還有一個知識點:

如果數據成員相對於結構體或類開頭的偏移量小於 128,則訪問數據成員的代碼會更加緊湊,因爲該偏移量可以使用 8 位有符號的數字來表示。如果相對於結構體或類的開頭的偏移量是 128 字節或更多,那麼偏移量必須表示爲一個 32 位數字 (指令集在 8 位到 32 位之間沒有偏移量)。例如:

struct S {
    int a[100]; // 400
    int b; // 4
    int read() { return b; }
};

b 成員的偏移量是 400。任何通過指針或成員函數訪問 b 字段的代碼都需要將偏移量編碼爲 32 位數字。如果交換 a 和 b,則兩者都可以通過編碼爲 8 位有符號數字的偏移量來訪問,或者根本不需要偏移量。

這會使代碼更緊湊,方便更有效地使用代碼緩存。因此,建議在結構或類聲明中,大數組和其他大對象排在最後,最常用的數據成員排在前面。如果不能在前 128 個字節內包含所有數據成員,則將最常用的成員放在前 128 個字節中。

通過上面兩個小知識點可以使得將結構體設計的更小,訪問數據成員的速度更快,但是這有時往往會犧牲一些可讀性,比如這種結構體:

struct S {
    int deskA;
    double deskB;
    bool deskC;
    int chairA;
    double chairB;
    bool chairC;
};

可能這樣修改後結構體會更小:

struct S {
    int deskA;
    int chairA;
    double deskB;
    double chairB;
    bool deskC;
    bool chairC;
};

但是我們一般情況下貌似希望同類的字段放在一起,這樣代碼可讀性更高一些,易於讀懂代碼。至於這種結構體具體需不需要重新排序,那就需要大家自己權衡啦。

小總結:

打完收工。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/FbaGdRgUFmfXYY52NxbbbA