[英]bit field padding in C
繼續我在C中的實驗,我想看看位字段是如何放在內存中的。 我正在使用英特爾64位機器。 這是我的一段代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
int main(int argc, char**argv){
struct box_props
{
unsigned int opaque : 1;
unsigned int fill_color : 3;
unsigned int : 4;
unsigned int show_border : 1;
unsigned int border_color : 3;
unsigned int border_style : 2;
unsigned int : 2;
};
struct box_props s;
memset(&s, 0, 32);
s.opaque = 1;
s.fill_color = 7;
s.show_border = 1;
s.border_color = 7;
s.border_style = 3;
int i;
printf("sizeof box_porps: %d sizeof unsigned int: %d\n", sizeof(struct box_props), sizeof(unsigned int));
char *ptr = (char *)&s;
for (i=0; i < sizeof(struct box_props); i++){
printf("%x = %x\n", ptr + i, *(ptr + i));
}
return 0;
這是一個輸出:
sizeof box_porps: 4 sizeof unsigned int: 4
5be6e2f0 = f
5be6e2f1 = 3f
5be6e2f2 = 0
5be6e2f3 = 0
這里有一個問題:為什么struct box_props
大小為4
- 它不能只是2
個字節? 在這種情況下如何填充? 我有點(nomen omen)與它混淆。
提前獲取所有答案
即使在這種情況下總需求僅為2字節(1 + 3 + 4 + 1 + 3 + 2 + 2),所使用的數據類型( unsigned int
)的大小為4字節。 所以分配的內存也是4字節。 如果只想分配2個字節,請使用unsigned short
作為數據類型並再次運行程序。
從ISO C標准:
實現可以分配足夠大的任何可尋址存儲單元來保持位字段。 (以及之后)結構或聯合的末尾可能有未命名的填充。
因此,不需要總是為結構選擇盡可能小的內存塊。 由於32位字可能是編譯器的原生大小,因此它就是它所選擇的。
位域在內存中的位置不僅取決於編譯器如何決定分配結構中的各個字段,還取決於運行它的機器的字節序。 讓我們一個接一個。 可以通過指定字段的大小(如@DDD)指出,也可以通過另一種機制來控制編譯器中字段的分配。 您可以告訴編譯器pack
您的結構,或者讓它更適合編譯器可能希望針對您正在編譯的機器體系結構進行優化的方式。 使用packed
type屬性指定packed
。 因此,如果將結構指定為:
struct __attribute__ ((__packed__)) box_props {
...
}
你可能會在內存中看到不同的布局。 請注意,通過檢查結構組件,您將看不到布局的不同 - 它可能會更改內存中的布局。 在與其他需要特定位置的特定位的IO設備進行通信時,打包結構至關重要。
位字段結構的第二個問題是它們的布局依賴於字節序。 內存中結構的布局(或任何數據)取決於您是在big-endian(POWER)還是little-endian(例如x86)機器上運行。 一些系統(例如,嵌入式PowerPC系統是雙端的)。
通常,位字段使得端口代碼變得非常困難,因為您在內存中存儲數據時非常困難。
希望這可以幫助!
由於某些原因我無法理解,C標准的實現者決定指定一個數字類型以及一個位域應該分配足以容納該數字類型的空間,除非前一個字段是一個位域,由同一類型分配,留下足夠的空間來處理下一個字段。
對於您的特定示例,在具有16位無符號短路的機器上,您應該將位域中的聲明更改為無符號短路。 碰巧的是,unsigned char也可以工作,並產生相同的結果,但情況並非總是如此。 如果最佳打包的位域跨越char邊界而不是短邊界,則將位域聲明為unsigned char
將需要填充以避免這種跨越。
雖然有些處理器可以毫不費力地為跨越存儲單元邊界的位域生成代碼,但是現在的C標准將禁止以這種方式打包它們(再次,由於我無法理解的原因)。 例如,在具有典型8/16/32/64位數據類型的機器上,編譯器不允許程序員指定包含8個三字節字段的3字節結構,因為字段必須跨越字節邊界。 我可以理解規范不要求編譯器處理跨越邊界的字段,或要求以某種特定方式布置位域(如果可以指定特定位域應該使用位4,我會認為它們更有用)一些位置的7,但現在的標准似乎給兩個世界最壞的。
在任何情況下,有效使用位域的唯一方法是確定存儲單元邊界的位置,並適當地選擇位域的類型。
PS - 值得注意的是,雖然我記得編譯器過去常常禁止包含位域的結構的volatile
聲明(因為編寫位域時的操作順序可能沒有很好地定義),在新規則下語義可以很好地定義(I不知道規范是否真的需要它們。 例如,給定:
typedef struct {
uint64_t b0:8,b1:8,b2:8,b3:8, b4:8,b5:8,b6:8,b7:8;
uint64_t b8:8,b9:8,bA:8,bB:8, bC:8,bD:8,bE:8,bF:8;
} FOO;
extern volatile FOO bar;
聲明bar.b3 = 123;
將讀取從第一64位bar
,然后寫的前64位bar
與更新后的值。 如果bar
不是易失性的,編譯器可能會用簡單的8位寫入替換該序列,但是bar
可能類似於硬件寄存器,只能用32位或64位塊寫入。
如果我有我的druthers,就可以使用以下方法定義位域:
typedef struct {
uint32_t {
baudRate:13=0, dataFormat:3,
enableRxStartInt: 1=28, enableRxDoneInt: 1, enableTxReadyInt: 1, enableTxEmptyInt: 1;};
};
} UART_CONTROL;
指示baudRate是從位0(LSB)開始的13位,dataFormat是在baudRate之后開始的3位,enableRxStartInt是位28,等等。這樣的語法將允許以便攜方式寫入許多類型的數據打包和解包,以及允許許多I / O寄存器操作以編譯器無關的方式完成(盡管這樣的代碼顯然是特定於硬件的)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.