記憶體中的位元組對齊

2021-08-18 14:35:36 字數 3445 閱讀 3745

一、什麼是位元組對齊,為什麼要對齊?

現代計算機中記憶體空間都是按照byte劃分的,從理論上講似乎對任何型別的變數的訪問可以從任何位址開始,但實際情況是在訪問特定型別變數的時候經常在特定的記憶體位址訪問,這就需要各種型別資料按照一定的規則在空間上排列,而不是順序的乙個接乙個的排放,這就是對齊。

對齊的作用和原因:各個硬體平台對儲存空間的處理上有很大的不同。一些平台對某些特定型別的資料只能從某些特定位址開始訪問。比如有些架構的cpu在訪問乙個沒有進行對齊的變數的時候會發生錯誤,那麼在這種架構下程式設計必須保證位元組對齊.其他平台可能沒有這種情況,但是最常見的是如果不按照適合其平台要求對資料存放進行對齊,會在訪問效率上帶來損失。比如有些平台每次讀都是從偶位址開始,如果乙個int型(假設為32位系統)如果存放在偶位址開始的地方,那麼乙個讀週期就可以讀出這32bit,而如果存放在奇位址開始的地方,就需要2個讀週期,並對兩次讀出的結果的高低位元組進行拼湊才能得到該32bit資料。顯然在讀取效率上下降很多。

二、.位元組對齊的原則

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

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

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

2.結構體或者類的自身對齊值:其成員中自身對齊值最大的那個值。

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

4.資料成員、結構體和類的有效對齊值:自身對齊值和指定對齊值中小的那個值。

有效對齊值n決定資料存放的首位址,有效對齊n,是說存放該資料的起始位址%n=0".即是n的倍數。而資料結構中的資料變數都是按定義的先後順序來排放的。第乙個資料變數的起始位址就是資料結構的起始位址。結構體的成員變數要對齊排放,結構體本身也要根據自身的有效對齊值圓整(就是結構體成員變數占用總長度是結構體有效對齊值的整數倍,

win32平台下的微軟 編譯器(cl.exe for 80×86)的對齊策略:

1) 結構體變數的首位址是其有效對齊值的整數倍;

2) 結構體的每個成員相對於結構體首位址的偏移量(offset)是成員的有效對齊值的整數倍,如有需要編譯器會在成員之間加上填充位元組(internal adding);

備註:為結構體的乙個成員開闢空間之前,編譯器首先檢查預開闢空間的首位址相對於結構體首位址的偏移是否是本成員的有效對齊值得整數倍,若是,則存放本成員,反之,則在本成員和上乙個成員之間填充一定的位元組,以達到整數倍的要求,也就是將預開闢空間的首位址後移幾個位元組。

3) 結構體的總大小為結構體的有效對齊值的整數倍,如有需要,編譯器會在最末乙個成員之後加上填充位元組(trailing padding)。

在設計結構體的時候,一般會遵照乙個習慣,就是把占用空間小的型別排在前面,占用空間大的型別排在後面,這樣可以相對節約一些對齊空間。

示例:struct b

char b;

int a;

short c;

假設b從位址空間0x0000開始排放。該例子中沒有定義指定對齊值,在筆者環境下,該值預設為4。第乙個成員變數b的自身對齊值是1,比指定或者預設指定對齊值4小,所以其有效對齊值為1,所以其存放位址0x0000符合0x0000%1=0.第二個成員變數a,其自身對齊值為4,所以有效對齊值也為4,所以只能存放在起始位址為0x0004到0x0007這四個連續的位元組空間中,符合0x0004%4=0,且緊靠第乙個變數。第三個變數c,自身對齊值為2,所以有效對齊值也是2,可以存放在0x0008到0x0009這兩個位元組空間中,符合0x0008%2=0。所以從0x0000到0x0009存放的都是b內容。再看資料結構b的自身對齊值為其變數中最大對齊值(這裡是b)所以就是4,所以結構體的有效對齊值也是4。根據結構體圓整的要求,0x0009到0x0000=10位元組,(10+2)%4=0。所以0x0000a到0x000b也為結構體b所占用。故b從0x0000到0x000b共有12個位元組,sizeof(struct b)=12;其實如果就這乙個就來說它已將滿足位元組對齊了,因為它的起始位址是0,因此肯定是對齊的,之所以在後面補充2個位元組,是因為編譯器為了實現結構陣列的訪問效率,試想如果我們定義了乙個結構b的陣列,那麼第乙個結構起始位址是0沒有問題,但是第二個結構呢?按照陣列的定義,陣列中所有元素都是緊挨著的,如果我們不把結構的大小補充為4的整數倍,那麼下乙個結構的起始位址將是0x0000a,這顯然不能滿足結構的位址對齊了,因此我們要把結構補充成有效對齊大小的整數倍.其實諸如:對於char型資料,其自身對齊值為1,對於short型為2,對於int,float,double型別,其自身對齊值為4,這些已有型別的自身對齊值也是基於陣列考慮的,只是因為這些型別的長度已知了,所以他們的自身對齊值也就已知了.

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

struct c

char b;

int a;

short c;

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

sizeof(struct c)=8;

三、含有位域字段的結構體的對齊方式

結構體中含位域字段。位域成員不能單獨被取sizeof值。c99規定int、unsigned int和bool可以作為位域型別,但編譯器幾乎都對此作了擴充套件,允許其它型別型別的存在。

使用位域的主要目的是壓縮儲存,其大致規則為:

1) 如果相鄰位域字段的型別相同,且其位寬之和小於型別的sizeof大小,則後面的字段將緊鄰前乙個字段儲存,直到不能容納為止;

2) 如果相鄰位域字段的型別相同,但其位寬之和大於型別的sizeof大小,則後面的字段將從新的儲存單元開始,其偏移量為其型別大小的整數倍;

3) 如果相鄰的位域字段的型別不同,則各編譯器的具體實現有差異,vc6採取不壓縮方式,dev-c++採取壓縮方式;

4) 如果位域字段之間穿插著非位域字段,則不進行壓縮;

5) 整個結構體的總大小為最其有效對齊值的整數倍。

例1:struct a;

a        b             c

a的記憶體布局:111,    1111 *,   11111 * * *

位域型別為char,第1個位元組僅能容納下f1和f2,所以f2被壓縮到第1個位元組中,而f3只能從下乙個位元組開始。因此sizeof(a)的結果為2。

例2:struct b;

由於相鄰位域型別不同,在vc6中其sizeof為6,在dev-c++中為2。

例3:struct c;

非位域字段穿插在其中,不會產生壓縮,在vc6和dev-c++中得到的大小均為3。

四、如何修改編譯器的預設對齊值?

1、在編碼時,可以這樣動態修改

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

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

2、  在vs2008中,可以這樣修改:

假設新建的專案為c-test。

選擇專案----c-test屬性,如下圖所示

選擇**生成—結構體成員對齊

c 中的記憶體位元組對齊

最近遊戲專案開發中遇到一件麻煩事,客戶端unity開發用的c 但需要呼叫c 中的函式進行訊息加密,呼叫dll中的介面時,c 中的結構體需要傳入到c 中進行操作,但是由於c 中的訊息頭定義的比較苟且,沒有提供乙個跟c 中一一對應的結構體,導致加密後再解密出錯,最後發現是記憶體位元組對齊的問題,在c 訊...

記憶體位元組對齊

一 什麼是對齊,以及為什麼要對齊 1.現代計算機中記憶體空間都是按照byte劃分的,從理論上講似乎對任何型別的變數的訪問可以從任何位址開始,但實際情況是在訪問特定變數的時候經常在特定的記憶體位址訪問,這就需要各型別資料按照一定的規則在空間上排列,而不是順序的乙個接乙個的排放,這就是對齊。2.對齊的作...

記憶體位元組對齊

之前對記憶體位元組序知道一些,但是一直沒有系統的學習過,導致有時候還是說不清楚的,今天在網上查到一些自己,學習了下,並驗證了,特意給大家分享下,希望對大家能有幫助 總的來說就三條原則 在沒有 pragma pack巨集的時候 1 資料成員對齊規則,在結構體 struct 中,第乙個資料成員從0開始,...