簡體   English   中英

為什么在類中使用時 uint64_t 比 2 個 uint32_t 需要更多內存? 以及如何防止這種情況?

[英]Why does an uint64_t needs more memory than 2 uint32_t's when used in a class? And how to prevent this?

我以下面的代碼為例。

#include <iostream>

struct class1
{

    uint8_t     a;
    uint8_t     b;
    uint16_t    c;
    uint32_t    d;
    uint32_t    e;

    uint32_t    f;
    uint32_t    g;
};

struct class2
{

    uint8_t     a;
    uint8_t     b;
    uint16_t    c;
    uint32_t    d;
    uint32_t    e;

    uint64_t    f;
};

int main(){
    std::cout << sizeof(class1) << std::endl;
    std::cout << sizeof(class2) << std::endl;
    std::cout << sizeof(uint64_t) << std::endl;
    std::cout << sizeof(uint32_t) << std::endl;
}

印刷

20
24
8
4

所以很容易看出一個 uint64_t 和兩個 uint32_t 一樣大,為什么類 2 會有 4 個額外的字節,如果它們是相同的,除了用兩個 uint32_t 替換一個 uint64_t 之外。

正如有人指出的那樣,這是由於padding

為了防止這種情況,您可以使用

#pragma pack(1)

class ... {

};
#pragma pack(pop)

它告訴您的編譯器不要對齊 8 個字節,而是對齊 1 個字節。 pop 命令將其關閉(這非常重要,因為如果您在標題中執行此操作並且有人包含您的標題,則可能會發生非常奇怪的錯誤)

對齊規則(在 x86 和 x86_64 上)通常是對齊變量的 size

換句話說,32 位變量在 4 個字節上對齊,64 位變量在 8 個字節上對齊,等等。

f的偏移量為 12,因此在uint32_t f情況下不需要填充,但是當fuint64_t ,添加 4 個字節的填充以使f對齊 8 個字節。

因此,最好將數據成員從大到小排序 那么就不需要填充或包裝(除非可能在結構的末尾)。

為什么在類中使用時 uint64_t 比 2 個 uint32_t 需要更多內存?

原因是由於對齊要求而填充。

在大多數 64 位架構上,uint8_t 的對齊要求為 1,uint16_t 的對齊要求為 2,uint32_t 的對齊要求為 4,uint64_t 的對齊要求為 8。編譯器必須確保結構中的所有成員都正確對齊並且結構的大小是其整體對齊要求的倍數。 此外,編譯器不允許重新排序成員。

所以你的結構最終布局如下

struct class1
{
    
    uint8_t     a; //offset 0
    uint8_t     b; //offset 1
    uint16_t    c; //offset 2
    uint32_t    d; //offset 4
    uint32_t    e; //offset 8
    
    uint32_t    f; //offset 12
    uint32_t    g; //offset 16
}; //overall alignment requirement 4, overall size 20.

struct class2
{
    
    uint8_t     a; //offset 0
    uint8_t     b; //offset 1
    uint16_t    c; //offset 2
    uint32_t    d; //offset 4
    uint32_t    e; //offset 8
    // 4 bytes of padding because f has an alignment requirement of 8
    uint64_t    f; //offset 16
}; //overall alignment requirement 8, overall size 24

以及如何防止這種情況?

不幸的是,沒有好的通用解決方案。

有時可以通過重新排序字段來減少填充量,但這對您的情況沒有幫助。 它只是在結構中移動填充。 具有需要 8 字節對齊的字段的結構的大小始終是 8 的倍數。 因此,無論您重新排列字段多少,您的結構的大小始終至少為 24。

您可以使用特定於編譯器的功能,例如#pragma pack__attribute((packed))來強制編譯器比正常對齊要求所允許的更緊密地打包結構。 然而,除了限制可移植性之外,這在獲取成員地址或綁定對成員的引用時會產生問題。 結果指針或引用可能不滿足對齊要求,因此使用起來可能不安全。

不同的編譯器處理這個問題的方式各不相同。 來自一些在 Godbolt 上玩耍的人。

  • g++ 9 到 11 將拒絕將引用綁定到打包成員並在獲取地址時發出警告。
  • clang 4 到 11 將在獲取地址時發出警告,但會靜默綁定一個引用並將該引用傳遞到編譯單元邊界。
  • Clang 3.9 及更早版本將獲取地址並靜默綁定引用。
  • g++ 8 及更早版本和 clang 3.9 及更早版本(直到 Godbolt 上最舊的版本)也將拒絕綁定引用,但會在沒有警告的情況下獲取地址。
  • icc 將綁定一個指針或獲取地址,在任何一種情況下都不會產生任何警告(盡管公平的英特爾處理器支持硬件中的未對齊訪問)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM