struct的記憶體對齊詳細講解

2021-06-26 05:16:13 字數 3347 閱讀 2936

在c語言中,結構是一種復合資料型別,其構成元素既可以是基本資料型別(如int、long、float等)的變數,也可以是一些復合資料型別(如陣列、結構、聯合等)的資料單元。在結構中,編譯器為結構的每個成員按其自然對界(alignment)條件分配空間各個成員按照它們被宣告的順序在記憶體中順序儲存,第乙個成員的位址和整個結構的位址相同。例如,下面的結構各成員空間分配情況:

struct test

;結構的第乙個成員x1為char型別,其自然對界為1,其偏移位址為0,佔據了第1個位元組。第二個成員x2為short型別,其自然對界為2,起始位址必須2位元組對界,因此,編譯器在x2和x1之間填充了乙個空位元組。結構的第三個成員x3和第四個成員x4恰好落在其自然對界位址上,在它們前面不需要額外的填充位元組。在test結構中,成員x3要求4位元組對界,是該結構所有成員中要求的最大對界單元,因而test結構的自然對界條件為4位元組,編譯器在成員x4後面填充了3個空位元組。整個結構所佔據空間為12位元組。

更改c編譯器的預設位元組對齊方式

在預設情況下,c編譯器為每乙個變數或是資料單元按其自然對界條件分配空間。一般地,可以通過下面的方法來改變預設的對界條件:

· 使用偽指令#pragma pack (n),c編譯器將按照n個位元組對齊。

· 使用偽指令#pragma pack (),取消自定義位元組對齊方式。

另外,還有如下的一種方式:

· __attribute((aligned (n))),讓所作用的結構成員對齊在n位元組自然邊界上。如果結構中有成員的長度大於n,則按照最大成員的長度來對齊。

· __attribute__ ((packed)),取消結構在編譯過程中的優化對齊,按照實際占用位元組數進行對齊。

以上的n = 1, 2, 4, 8, 16... 第一種方式較為常見。

應用例項

在網路協議程式設計中,經常會處理不同協議的資料報文。一種方法是通過指標偏移的方法來得到各種資訊,但這樣做不僅程式設計複雜,而且一旦協議有變化,程式修改起來也比較麻煩。在了解了編譯器對結構空間的分配原則之後,我們完全可以利用這一特性定義自己的協議結構,通過訪問結構的成員來獲取各種資訊。這樣做,不僅簡化了程式設計,而且即使協議發生變化,我們也只需修改協議結構的定義即可,其它程式無需修改,省時省力。下面以tcp協議首部為例,說明如何定義協議結構。其協議結構定義如下:

#pragma pack(1) // 按照1位元組方式進行對齊

struct tcpheader 

; #pragma pack() // 取消1位元組對齊方式

下面有一道在 csdn論壇 上討論火熱的題:

#pragma pack(8)

struct s1;

struct s2;

#pragma pack()

問1.sizeof(s2) = ?

2.s2的c後面空了幾個位元組接著是d?

結果如下:

sizeof(s2)結果為24.

成員對齊有乙個重要的條件,即每個成員分別對齊.即每個成員按自己的方式對齊.

也就是說上面雖然指定了按8位元組對齊,但並不是所有的成員都是以8位元組對齊.其對齊的規則是,每個成員按其型別的對齊引數(通常是這個型別的大小)和指定對齊引數(這裡是8位元組)中較小的乙個對齊.並且結構的長度必須為所用過的所有對齊引數的整數倍,不夠就補空位元組.

s1中,成員a是1位元組預設按1位元組對齊,指定對齊引數為8,這兩個值中取1,a按1位元組對齊;成員b是4個位元組,預設是按4位元組對齊,這時就按4位元組對齊,所以sizeof(s1)應該為8;

s2 中,c和s1中的a一樣,按1位元組對齊,而d 是個結構,它是8個位元組,它按什麼對齊呢?對於結構來說,它的預設對齊方式就是它的所有成員使用的對齊引數中最大的乙個,s1的就是4.所以,成員d就是按4位元組對齊.成員e是8個位元組,它是預設按8位元組對齊,和指定的一樣,所以它對到8位元組的邊界上,這時,已經使用了12個位元組了,所以又新增了4個位元組的空,從第16個位元組開始放置成員e.這時,長度為24,已經可以被8(成員e按8位元組對齊)整除.這樣,一共使用了24個位元組.

a    b

s1的記憶體布局:11**,1111,

c    s1.a s1.b     d

s2的記憶體布局:1***,11**,1111,****11111111

這裡有三點很重要:

1.每個成員分別按自己的方式對齊,並能最小化長度

2.複雜型別(如結構)的預設對齊方式是它最長的成員的對齊方式,這樣在成員是複雜型別時,可以最小化長度

3.對齊後的長度必須是成員中最大的對齊引數的整數倍,這樣在處理陣列時可以保證每一項都邊界對齊

補充一下,對於陣列,對齊方式為元素的對齊方式。比如:

char a[3];這種,它的對齊方式和分別寫3個char是一樣的.也就是說它還是按1個位元組對齊.

如果寫: typedef char array3[3];

array3這種型別的對齊方式還是按1個位元組對齊,而不是按它的長度.

不論型別是什麼,對齊的邊界一定是1,2,4,8,16,32,64....中的乙個.

struct s1

;struct s2

;struct s3

;struct s4

;cout<union的對齊方式:為成員中最大的對齊方式,長度為按照這個對齊方式調整最長成員得到的長度。

union u

;union u2

;union u3

;cout<

結論:復合資料型別,如union,struct,class的對齊方式為成員中對齊方式最大的成員的對齊方式。

順便提一下cpu對界問題,32的c++採用8位對界來提高執行速度,所以編譯器會盡量把資料放在它的對界上以提高記憶體命中率。對界是可以更改的,使用#pragmapack(x)巨集可以改變編譯器的對界方式,預設是8。c++固有型別的對界取編譯器對界方式與自身大小中較小的乙個。例如,指定編譯器按2對界,int型別的大小是4,則int的對界為2和4中較小的2。在預設的對界方式下,因為幾乎所有的資料型別都不大於預設的對界方式8(除了 longdouble),所以所有的固有型別的對界方式可以認為就是型別自身的大小。 www. 更改一下上面的程式:

#pragmapack(2)

union u2

;union u3

;#pragmapack(8)

cout

結論:c++固有型別的對界取編譯器對界方式與自身大小中較小的乙個。

struct記憶體對齊

出於速度和空間的考量,編譯器在實現過程中均會採用對struct內的變數進行記憶體對齊的操作,雖然會有一定的空間浪費,卻可以減少在讀取資料時候的讀取操作。先看下面的例子 struct a int main struct b int main struct b int main struct a str...

struct記憶體對齊

關於c 中的struct記憶體對齊,應該也是初學者比較疑惑的乙個知識點,但是搞清楚之後會發現非常簡單,這裡解釋一下struct記憶體到底怎麼對齊。主要記住以下兩點 1.各成員變數存放的起始位址相對於結構的起始位址的偏移量必須為該變數的型別所占用的位元組數的倍數 2.整個struct的記憶體大小需為s...

struct 記憶體對齊

struct 是一種復合資料型別,其構成元素既可以是基本資料型別 如 int long float 等 的變數,也可以是 一些復合資料型別 如 array struct union 等 的資料單元。對於結構體,編譯器會自動進行成員變數的對齊,以提高運算效率。預設情況下,編譯器為結構體的每個成員按其自...