struct與union位元組對齊問題

2021-07-05 01:30:51 字數 4081 閱讀 8357

c 語言的一大優勢就是對記憶體空間的控制,當然,在物件導向語言的壓力下,程式設計師更喜歡輕鬆的語言,不喜歡自己還要顧慮記憶體空間。

可是,c 語言仍然有很強的生命力,尤其是在作業系統、嵌入式系統這兩方面,因為要直接操作硬體,c語言就顯現出自己強大的體制、機制、邏輯優勢。

c語言對記憶體控制,有乙個始終困擾初學者的問題:位元組對齊!

看一段程式:

[cpp]view plain

copy

struct

stexample  

; //

sizeof

( char

) == 1  

sizeof

( short

) == 2  

sizeof

( int

) == 4  

/sizeof

( struct

stexample ) == 8  

stexample結構體的大小是8 byte. 看起來符合預期。

可是下面這個例子:

[cpp]view plain

copy

struct

stexample  

; //

sizeof

( char

) == 1  

sizeof

( short

) == 2  

sizeof

( int

) == 4  

/sizeof

( struct

stexample ) == 12  

//?

stexample結構體的大小就變成了12 byte. 為什麼元素少了,反而占用空間會多出來4 byte? /

先讓我們看四個重要的基本概念:

1.資料型別自身的對齊值:

: 對於char型資料,其自身對齊值為1,對於short型為2,對於int,float,double型別,其自身對齊值為4,單位位元組。

2.結構體或者類的自身對齊值:

:其成員中自身對齊值最大的那個值。

3.指定對齊值:

:#pragma pack (value)時的指定對齊值value。

4.資料成員、結構體和類的有效對齊值:

:自身對齊值和指定對齊值中小的那個值。 /

有效對齊值n是最終用來決定資料存放位址方式的值,最重要。有效對齊n,就是 表示「對齊在n上」,也就是說該資料的"存放起始位址%n=0".

而資料結構中的資料變數都是按定義的先後順序來排放的。第乙個資料變數的起始位址就是資料結構的起始位址。結構體的成員變數要對齊排放。

例子分析:

[cpp]view plain

copy

struct

stexample  

; //

sizeof

( char

) == 1  

sizeof

( short

) == 2  

sizeof

( int

) == 4  

/sizeof

( struct

stexample ) == 12  

//?

假設stexample從位址空間0x0000開始排放。該例子中沒有定義指定對齊值,在筆者環境下,該值預設為4。三個成員的儲存位置如圖:

1. 第乙個成員變數a的自身對齊值是1,比指定或者預設指定 對齊值4小,所以其有效對齊值為1,所以其存放位址0x0000符合0x0000%1=0.

2. 第二個成員變數b,其自身對齊值為4,所以有效對齊值也為4, 所以只能存放在起始位址為0x0004到0x0007這四個連續的位元組空間中,複核0x0004%4=0,且緊靠第乙個變數。

3. 第三個變數c,自身對齊值為 2,所以有效對齊值也是2,可以存放在0x0008到0x0009這兩個位元組空間中,符合0x0008%2=0。

所以從0x0000到0x0009存放的 都是stexample內容。再看資料結構stexample的自身對齊值為其變數中最大對齊值(這裡是b)所以就是4,所以結構體的有效對齊值也是4。根據結構體圓整的要求, 0x0009到0x0000=10位元組,(10+2)%4=0。所以0x0000a到0x000b也為結構體stexample所占用。故stexample從0x0000到0x000b 共有12個位元組,sizeof( struct stexample )=12.

其實如果就這乙個就來說它已將滿足位元組對齊了, 因為它的起始位址是0,因此肯定是對齊的,之所以在後面補充2個位元組,是因為編譯器為了實現結構陣列的訪問效率。

試想如果我們定義了乙個結構體stexample的陣列,那麼第乙個結構起始位址是0沒有問題,但是第二個結構呢?按照陣列的定義,陣列中所有元素都是緊挨著的,如果我們不把結構的大小補充為4的整數倍,那麼下乙個結構的起始位址將是0x0000a,這顯然不能滿足結構的位址對齊了,因此我們要把結構補充成有效對齊大小的整數倍.

其實諸如:對於char型資料,其自身對齊值為1,short型別為2,int,float,double型別,其自身對齊值為4,這些已有型別的自身對齊值也是基於陣列考慮的,只是因為這些型別的長度已知了,所以他們的自身對齊值也就已知了.

分析下面例子:

[cpp]view plain

copy

#pragma pack (2) /*指定按2位元組對齊*/

struct

stexample  

;  #pragma pack () /*取消指定對齊,恢復預設對齊*/

///sizeof

( struct

stexample ) == 8   

1. 第 乙個變數a的自身對齊值為1,指定對齊值為2,所以,其有效對齊值為1,假設stexample 從0x0000開始,那麼a存放在0x0000,符合0x0000%1= 0;

2. 第二個變數b,自身對齊值為4,指定對齊值為2,所以有效對齊值為2,所以順序存放在0x0002、0x0003、0x0004、0x0005四個連續 位元組中,符合0x0002%2=0。

3. 第三個變數c的自身對齊值為2,所以有效對齊值為2,順序存放在0x0006、0x0007中,符合 0x0006%2=0。

所以從0x0000到0x00007共八字節存放的是stexample 的變數。又因為stexample 的自身對齊值為4,所以stexample 的有效對齊值為2。又8%2=0, stexample  只占用0x0000到0x0007的八個位元組。所以sizeof( struct stexample ) == 8.

//struct 結構體的巢狀結構也是類似的對齊方式。

看下面的例子:

[c-sharp]view plain

copy

struct

stexample  

short

c;  

};  

//sizeof

( struct

stexample ) == 16   

記憶體中的位元組對齊如下圖所示:

struct b 結構體的自身對齊值是4(由成員cc決定的),所以儲存位址必須是4的整數倍。

struct stexample 結構體的自身對齊值也是4(由成員struct b決定),所以最後兩個位元組用來補齊(即0x000e 和 0x0010)。

union 共用體的位元組對齊情況類似,共用體的自身對齊值決定於成員的最大自身對齊值。

位元組對齊,在一般情況下,在編寫上層應用程式時一般是不用顧慮的。

但是有兩種情況要特別小心,一是涉及到硬體memory操作,一是涉及到網路報文傳輸。

對網路報文定義結構體時,位元組不對齊的話就會造成大錯。有兩種方法解決:

1. 可以使用pack(1)宣告為1位元組對齊。但是操作效率會下降,而且有些嵌入式系統的編譯器支援不夠好。

2. 可以將網路報文結構體內的成員變數,定義時最大使用short型,可以使用char型,但要保持偶數位元組對齊。(一般標準的網路報文結構就是偶數位元組對齊的)。遇到需要int型的變數,可以定義乙個小共用體typedef union union_int ; 用它來代替int在報文結構體中使用,只是程式中注意點就行了。

**

union 與struct的空間計算

總體上遵循兩個原則 1 整體空間是 占用空間最大的成員 的型別 所佔位元組數的整倍數 2 資料對齊原則 記憶體按結構成員的先後順序排列,當排到該成員變數時,其前面已擺放的空間大小必須是該成員型別大小的整倍數,如果不夠則補齊,以此向後類推。注意 陣列按照單個變數乙個乙個的擺放,而不是看成整體。如果成員...

union 與struct的空間計算

一 x86 總體上遵循兩個原則 說明 假定結構體是從位址0開始依次存放各個變數的 struct s1 student 詳細解釋 sizeof 用法彙總 為什麼會有這樣的規定呢?這一定與處理器的字長有關 處理器一次訪問資料的寬度 和編譯器對結構體變數的處理有關。不幸的是,本人對x86架構不甚熟悉,只能...

union內嵌struct用法

union內嵌struct用法 眾所周知,union為聯合體,struct為結構體。下面根據例項談談用法 include include void main half number strcpy number.i,abcda printf c c n number.half.first,number...