2014-11-02 111 views
90

我定義這個結構:爲什麼這個結構大小3而不是2?

typedef struct 
{ 
    char A:3; 
    char B:3; 
    char C:3; 
    char D:3; 
    char E:3; 
} col; 

sizeof(col)給我3的輸出,但它不應該是2?如果我只評論一個元素,則sizeof是2.我不明白爲什麼:3位的五個元素等於15位,並且小於2個字節。

定義像這樣的結構是否有「內部大小」?我只需要澄清,因爲從我迄今爲止的語言概念來看,我預計的是2字節的大小,而不是3.

+4

這可能是對齊的優化。如果下一個比特大小不適合實際佔用的空間,它將啓動一個新的字節。 – 2014-11-02 13:52:42

+4

除非您有一些外部約束需要位打包,並且您的平臺爲標準提供了一些額外的保證,否則使用位域幾乎沒有意義。 – 2014-11-02 15:35:36

+3

請注意,對於C來說,使用char不如使用int可移植,http://stackoverflow.com/a/23987436/23118。 – hlovdal 2014-11-02 15:41:52

回答

94

由於您使用的是char作爲字段的基礎類型,因此編譯器會嘗試按字節對位進行分組,並且由於每個字節中的位數不能超過8位,因此每個字節只能存儲兩個字段。

您的結構使用的位的總數是15,所以適合這麼多數據的理想大小將是short

#include <stdio.h> 

typedef struct 
{ 
    char A:3; 
    char B:3; 
    char C:3; 
    char D:3; 
    char E:3; 
} col; 


typedef struct { 
    short A:3; 
    short B:3; 
    short C:3; 
    short D:3; 
    short E:3; 
} col2; 


int main(){ 

    printf("size of col: %lu\n", sizeof(col)); 
    printf("size of col2: %lu\n", sizeof(col2)); 

} 

上面的代碼(爲一個64位平臺等礦)確實會產生2對於第二結構體。對於任何超過short更大,該結構將填補使用類型的不超過一個元素,所以 - 對於同一個平臺 - 該結構將結束與大小四爲int,八項long

+1

建議的結構定義仍然是錯誤的。正確的結構定義將使用'unsigned short'。 – user3629249 2014-11-02 21:21:25

+21

@ user3629249爲什麼無符號短'正確'?如果用戶想要從-4存儲到3,那麼短小是正確的。如果用戶想要存儲從0到7,那麼無符號短小是正確的。原始問題使用簽名類型,但我不能說這是故意的還是意外的。 – 2014-11-03 07:14:46

+2

爲什麼'char'和'short'有什麼區別? – GingerPlusPlus 2014-11-03 13:22:51

78

因爲您不能擁有跨越最小對齊邊界的位數據包字段爲1個字節),因此他們可能會得到擠得像

byte 1 
    A : 3 
    B : 3 
    padding : 2 
byte 2 
    C : 3 
    D : 3 
    padding : 2 
byte 3 
    E : 3 
    padding : 5 

(場的訂單/相同的字節內部填充是不是故意的,它只是給你的想法,因爲編譯器可以奠定下來如何它更喜歡)

16

前兩位字段適合單個char。第三個不適合那個char,需要一個新的。 3 + 3 + 3 = 9不適合8位字符。

所以第一對需要char,第二對需要char,最後一個位域獲得第三個char

9

即使盡管ANSI C標準沒有詳細說明位域是如何打包以提供任何顯着的優勢而不是「編譯器允許打包位域但是它們看起來合適」,但它在很多情況下都禁止編譯器以最有效的方式打包文件。

特別是,如果結構包含位域,編譯器需要將其存儲爲包含一個或多個「正常」存儲類型的匿名字段的結構,然後將每個這樣的字段邏輯地細分爲其組成的位域部分。因此,給定:

unsigned char foo1: 3; 
unsigned char foo2: 3; 
unsigned char foo3: 3; 
unsigned char foo4: 3; 
unsigned char foo5: 3; 
unsigned char foo6: 3; 
unsigned char foo7: 3; 

如果unsigned char是8位,則編譯器將需要分配該類型的四個字段,和分配兩個位域到所有,但一個(這將是在它自己的char字段) 。如果所有char聲明已被替換爲short,則將有兩個short類型的字段,其中一個將保存五個位域,另一個將保留剩餘的兩個。

在沒有對齊限制的處理器上,通過對前五個字段使用unsigned short,對於最後兩個字段使用unsigned char,可以更有效地佈置數據,以三個字節存儲七個三位字段。雖然應該可以將三個字節的8位三位字段存儲起來,但編譯器只允許在存在可以用作「外部字段」類型的三字節數字類型的情況下。

就個人而言,我認爲位域定義爲基本無用。如果代碼需要使用二進制打包數據,它應該明確定義實際類型的存儲位置,然後使用宏或其他一些手段訪問其中的位。這將是有益如果C支持像語法:

unsigned short f1; 
unsigned char f2; 
union foo1 = f1:0.3; 
union foo2 = f1:3.3; 
union foo3 = f1:6.3; 
union foo4 = f1:9.3; 
union foo5 = f1:12.3; 
union foo6 = f2:0.3; 
union foo7 = f2:3.3; 

這樣的語法,如果允許的話,將有可能使代碼在便攜方式使用位域,而無需字長或字節序方面(foo0會位於f1的三個最低有效位中,但可以存儲在較低或較高地址)。但是,如果沒有這樣的功能,宏可能是唯一可以使用這種操作的便攜式方式。

+2

不同的編譯器會以不同的方式放置位域。我寫了一些關於Visual C++如何與它相關的文檔。它指出了一些令人討厭的陷阱: https://randomascii.wordpress.com/2010/06/06/bit-field-packing-with-visual-c/ – 2014-11-03 07:15:55

+0

那麼你說的是相當於一家商店正常類型並使用位域操作符來完成單個感興趣的變量,並簡化這種機制使用一些宏。我認爲在c/C++中生成的代碼也是這樣做的。使用結構只是爲了「更好」的代碼組織,實際上並不是必要的。 – Raffaello 2014-11-03 15:31:53

15

大多數編譯器允許你控制填充,e.g. using #pragmas。下面是使用GCC 4.8.1一個例子:

#include <stdio.h> 

typedef struct 
{ 
    char A:3; 
    char B:3; 
    char C:3; 
    char D:3; 
    char E:3; 
} col; 

#pragma pack(push, 1) 
typedef struct { 
    char A:3; 
    char B:3; 
    char C:3; 
    char D:3; 
    char E:3; 
} col2; 
#pragma pack(pop) 

int main(){ 
    printf("size of col: %lu\n", sizeof(col)); // 3 
    printf("size of col2: %lu\n", sizeof(col2)); // 2 
} 

注意,編譯器的默認行爲是有原因的,將可能給你更好的性能。

相關問題