2015-04-01 29 views
8

看到網上例如: Ideone example爲什麼在這裏打包結構5而不是4字節的大小?

struct { 
    union { 
    struct { 
     uint32_t messageID : 26; 
     uint8_t priority : 3; 
    } __attribute__ ((packed)); 
    uint32_t rawID : 29; 
    } __attribute__ ((packed)); 
    uint8_t canFlags : 3; 
} __attribute__ ((packed)) idSpecial; 

爲什麼會編譯器報告結構的大小爲5個字節,而不是4嗎?它應該包含32位。

+0

可能重複[C中的結構和聯合,確定大小和訪問成員](http://stackoverflow.com/questions/3380118/structures-and-unions-in-c -determining-size-and-access-members) – 2015-04-01 07:19:41

+0

@DavidTitarenco:我不認爲這個帖子對位域有好處。如果這個問題已經在其他地方得到了回答,那麼我不會感到驚訝。 – 2015-04-01 07:22:17

+4

響應者應該注意,OP使用[非標準'__attribute__((packed))'](https://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html)GCC擴展來避免填充。所以,說編譯器被允許插入填充是不正確的 - 當使用'__attribute __((packed))'時,GCC將組織結構以避免填充(在不支持未對齊的讀取的平臺上使訪問更加昂貴)。 – user4815162342 2015-04-01 07:31:27

回答

5

這是因爲內存對齊:編譯器不會在中間開始canFlags一個字節,它會在下一個字節(可能是*)開始時啓動它。所以你的初始聯合有四個字節,canFlags有一個字節。

如果,例如,你感動canFlags進入聯盟,這將(可能*)有大小4:

typedef struct structTag { 
    union { 
    struct { 
     uint32_t messageID : 26; /* 26bit message id, 67108864 ids */ 
     uint8_t priority : 3; /* priority: MUST BE 0 */ 
    } __attribute__ ((packed)); 
    uint32_t rawID : 29; 
    uint8_t canFlags : 3; /* <==== Moved */ 
    } __attribute__ ((packed)); 
} __attribute__ ((packed)) idSpecial; 

Updated example on ideone。顯然,這種具體的變化可能不是你想要的;我只是證明問題是在字節邊界上開始一個新字段。


*「可能」,因爲最終取決於編譯器。

+0

rawid旁邊的canFlags使它們結合 - 我想這不是OP想要的。 – 2015-04-01 07:32:00

+0

@MatsPetersson:對,OP將不得不重新設計,我只是指出爲什麼他們爲他們當前的設計獲得5而不是4。 – 2015-04-01 07:32:53

+1

根據您的建議,我們將'rawID'和'canFlags'封裝在一個結構中,並在聯合內部的第一個結構的末尾添加了3位虛擬填充,現在整個結構的大小與預期的一樣大4個字節。 – 2015-04-01 07:47:32

0

在一些編譯器中,要「合併」這些位,所有項目必須是相同的類型。所以使它uint32_t你現在有uint8_t - 這似乎不是在編譯器的情況下IdeOne使用'

[不管怎麼樣,它仍然由編譯器如何合併位,所以它是唯一的以絕對保證你的數據存儲爲32位的方法是使用一個單獨的uint32_t並聲明一個類來執行相關的移位操作來控制這個值 - 唯一的保證是你的結構中的ONE元素會有至少與你所要求的位數一樣多]

正如其他人指出的那樣,除了字節邊界之外,你不能啓動一個新的結構。我固定它由具有工會內部的第二結構體,這樣的: http://ideone.com/Mr1gjD

#include <stdint.h> 
#include <stdio.h> 

typedef struct structTag { 
    union { 
    struct { 
     uint32_t messageID : 26; /* 26bit message id, 67108864 ids */ 
     uint8_t priority : 3; /* priority: MUST BE 0 */ 
    } __attribute__ ((packed)); 
    struct { 
     uint32_t rawID : 29; 
     uint8_t canFlags : 3; 
    }; 
    } __attribute__ ((packed)); 
} __attribute__ ((packed)) idSpecial; 

int main() { 
    printf("size: %d", sizeof(idSpecial)); 
    return 0; 
} 
+0

@ T.J.Crowder:同意。儘管我曾經遇到過這種情況,因爲某些原因 - 我認爲這可能是MS編譯器遇到的問題。 – 2015-04-01 07:42:39

6

的問題是,__attribute__((packed))不執行按位填料。它只是保證在struct成員之間沒有填充。您可以嘗試這個更簡單的示例,其中大小也報告爲5:

typedef struct structTag { 
    struct { 
     uint32_t messageID : 26; 
     uint8_t priority : 3; 
    } __attribute__ ((packed)); 
    uint8_t canFlags : 3; 
} __attribute__ ((packed)) idSpecial; 

按位打包僅適用於位域成員。您將需要重新設計您的結構,使其成爲具有bitfields messageID/priority/canFlags的結構體的聯合體,以及具有位域rowID/canFlags的結構體。換句話說,您將需要重複或使用訪問者宏或成員函數。

1

使用數據結構對齊方式在計算機內存中排列和訪問數據。其具有兩個相關的問題

  1. 對齊
  2. 填充

當由計算機執行寫操作時,它通常寫入以4個字節倍數(32位系統)。這種行爲的一個原因是提高績效的目標。所以當你編寫任何數據結構時,首先有1個字節的變量,然後是4個字節的變量數據,它將在第一個1字節數據之後進行填充,以在32位邊界上對齊它。

struct { 
    union { 
    struct { 
     uint32_t messageID : 26; 
     uint8_t priority : 3; 
    } __attribute__ ((packed)); 
    uint32_t rawID : 29; 
    } __attribute__ ((packed)); 
    uint8_t canFlags : 3; 
} __attribute__ ((packed)) idSpecial; 

現在,在上述數據結構中使用的是__attribute__ ((packed))這意味着沒有填充。所以uint32_t是4個字節,但是你說它有26位和3位優先級。現在,因爲您在一個結構中都有兩個變量,所以它將保留32位而不是29位,以便您的第一個結構的信息在邊界上被分配。

現在爲canFlags它將需要另一個字節。因此,這使得5個字節,而不是4.

相關問題