C語言中的位元組對齊

2021-06-21 08:08:46 字數 3730 閱讀 1188

一、什麼是位元組對齊

乙個基本型別的變數在記憶體中占用n個位元組,則該變數的起始位址必須能夠被n整除,即: 存放起始位址 % n = 0,那麼,就成該變數是位元組對齊的;對於結構體、聯合體而言,這個n取其所有基本型別的成員中占用空間位元組數最大的那個;

記憶體空間是以位元組為基本單位進行劃分的,從理論上講,似乎對任何型別的變數的訪問都可以從任何位址處開始,但實際情況是在訪問特定型別變數的時候經常是從特定的記憶體位址處開始訪問,這就需要各種型別的資料只能按照一定的規則在空間上排列,而不是順序的乙個接乙個地排放;究其原因,是為了使不同架構的cpu可以提高訪問記憶體的速度,就規定了對於特定型別的資料只能從特定的記憶體位置處開始訪問;所以,各種型別的資料只能按照相應的規則在記憶體空間上排放,而不能順序地、連續地、乙個乙個地排放;這就是記憶體對齊;

二、為什麼需要位元組對齊

由於各種硬體平台對儲存空間的處理上有很大的不同;一些平台對某些特定型別的資料只能從某個特定記憶體位址處開始訪問;比如:有些架構的cpu在訪問乙個沒有進行對齊的變數的時候會發生錯誤,那麼在這種架構下程式設計時就必須保證位元組對齊;其它平台可能沒有這種情況,但最常見的是,如果不按照適合其平台要求對資料進行對齊,會在訪問效率上帶來損失;比如,有些平台每次讀取資料都是從偶位址處開始,如果乙個int(假設為32位系統)型資料從偶位址處開始存放,那麼只需要乙個讀指令週期就可以完全讀出這個32bit的int型資料,相反,如果這個32bit的int型資料是從奇位址處開始存放,那麼就需要兩個讀指令週期才能完全讀出這個32bit的int資料,並且還需要對這兩次讀出的結果的高低位元組進行重新拼湊才能得到正確的32bit資料;這個時候,cpu的讀取效率明顯下降;

三、位元組對齊規則

預處理指令#pragma pack(align_value)用於指定對齊值,而預處理指令#pragma pack()用於取消上次設定的對齊值,恢復預設對齊值;

位元組對齊是針對基本型別變數的;基本型別變數有:char、unsigned char、short、unsigned short、int、unsigned int、long、unsigned long、long long、unsigned long long、float、double,等等;所以,對於結構體的對齊也只能按照其成員變數中的基本型別來對齊了;

有四個概念需要理解:

a、資料型別自身的對齊值:

是指對該資料型別使用sizeof()操作符進行操作所得到的大小(單位,位元組);比如,對於[unsigned] char型別的資料,其自身對齊值為1位元組;對於[unsigned] short型別的資料,其自身對齊值是2位元組;對於[unsigned] int、[unsigned] long、[unsigned] long long、float、double等資料型別,其自身對齊值是4位元組;

b、結構體、聯合體、類的自身對齊值:

是指其所有基本型別的成員中,自身對齊值最大的那個值;如果這些復合型別中有巢狀型別或復合型別的變數,則需要把這些巢狀的型別或復合型別的變數拆解成基本型別的成員之後再對齊;

c、指定對齊值:

是指使用預處理指令#pragma pack(align_value)指定的對齊值align_value;

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

是指其自身對齊值和指定對齊值中較小的那個值;

其中,有效對齊值是最終用來決定資料存放位址方式的值,最重要;設定有效對齊值為n,就表示"對齊在n位元組上",也就是說,該資料的"存放起始位址%n=0";

因此,每個型別的資料的有效對齊值就是其自身對齊值(通常是這個型別的大小)和指定對齊值(不指定則取預設值)中較小的那個值,並且結構體自身對齊值是其所有成員中自身對齊值最大的那個值;

位元組對齊的細節與編譯器的實現有關,但一般來說,結構體需要滿足以下幾個準則:

1).從結構體外部來看,結構體變數的首位址能夠被其最寬基本成員的大小整除;從結構體內部來看,它的第乙個資料成員的位址相對於整個結構體首位址的偏移量為0,也就是說,結構體的第乙個資料成員存放在偏移量為0的地方;

2).結構體中的每個資料成員的有效對齊值都取其自身對齊值和指定對齊值中的較小的那個對齊值;或者說是,結構體中的每個資料成員相對於結構體首位址的偏移量都是該資料成員大小和指定對齊值中較小的那個值(或有效對齊值)的整數倍,如有需要,編譯器會在資料成員之間加上填充位元組;

3).如果結構體中還有巢狀的結構體或結構體變數,那麼就要把這些巢狀進去的結構體或結構體變數拆成基本型別成員,並取其最長的基本型別成員的對齊方式;

4).結構體整體的有效對齊值必須為其最寬基本型別成員大小的整數倍;或者說是,結構體整體的大小為結構體中最寬基本型別成員大小的整數倍,如有需要,編譯器會在最末乙個成員之後加上填充位元組;換句話說是,結構體整體的有效對齊值按照結構體中最寬基本型別成員的大小和指定對齊值中較小的那個值進行;

注意:如果指定對齊值大於自身對齊值,則指定對齊值無效;

例1:不帶巢狀的

#pragma pack(4) //指定按照4位元組對齊

struct testa

;#pragma pack() //取消4位元組對齊,恢復預設對齊值;

因此,整個結構體占用的有效位元組為9個位元組;由於結構體整體的對齊值和大小是其最寬基本型別成員大小的整數倍,即:按照最寬基本型別成員大小和指定對齊值中較小的值對齊的;因為結構體最寬基本型別成員的大小是4位元組,其有效對齊值也是4位元組,而9位元組按照4位元組圓整的結果是12位元組,所以,sizeof(testa)=12;

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

struct testa

;#pragma pack() //取消4位元組對齊,恢復預設對齊值;

可以看出,只是改變了一下結構體之間的對齊方式,從4位元組對齊改為2位元組對齊;結果就不一樣了;

整個結構體所占用的有效位元組數仍然是9位元組,但是結構體整體的大小就變了,按照最寬基本型別成員大小和指定對齊值中較小的值對齊;結構體中最寬基本型別成員的大小事4位元組,而指定對齊值是2位元組對齊,取最小值2,所以,最後,整個結構體的大小就是9位元組按照2位元組圓整(取2的整數倍),於是,sizeof(testa)=10;

例2:帶巢狀的

#pragma pack(2)

struct a 

; struct b 

; #pragma pack()

結構體a占用的有效位元組數是6位元組;結構體a整體的大小要取其最寬基本型別成員大小和指定對齊值中較小的那個值的整數倍,最寬基本型別成員大小為4位元組,指定對齊值為2位元組,所以,6取較小的值2位元組的整數倍為6位元組;最終,結構體a的大小為:sizeof(a)=6;

結構體b占用的有效位元組數是9位元組;結構體b整體的大小要取其最寬基本型別成員大小和指定對齊值中較小的那個值的整數倍,最寬基本型別成員大小為6位元組,指定對齊值為2位元組,所以,9取較小的值2位元組的整數倍為10位元組;最終,結構體b的大小為:sizeof(b)=10;

四、總結

設定對齊方式有兩種方法;

第一種方法:

#pragma pack(n),指定按照n位元組對齊;

#pragma pack(),取消自定義的對齊值;

第二種方法:

__attribute__((aligned(n))):讓所作用的結構體成員對齊在n位元組自然邊界上;如果結構體中有成員的長度大於n,則按照最大成員的長度來對齊;即:按照機器能允許該類的的最大長度來對齊;這個恰好與#pragma pack(n)指令的相反;

__attribute__((packed)):取消結構體在編譯過程中的優化對齊,按照實際占用位元組數進行對齊,等價於指令#pragma pack(1),即,按照1位元組對齊;

其中,n=1,2,4,8,16,......等;

第一種方法比較常見

C語言中的位元組對齊

一 什麼是位元組對齊 乙個基本型別的變數在記憶體中占用n個位元組,則該變數的起始位址必須能夠被n整除,即 存放起始位址 n 0,那麼,就成該變數是位元組對齊的 對於結構體 聯合體而言,這個n取其所有基本型別的成員中占用空間位元組數最大的那個 記憶體空間是以位元組為基本單位進行劃分的,從理論上講,似乎...

c語言中的位元組對齊

一 什麼是位元組對齊?計算機中的資料在儲存時並不是按順序儲存,因為在訪問特定的資料型別時通常從特定的記憶體位址開始,所以資料在儲存時特定的型別儲存時從特定的位址開始,比如我們所說的int型別的對齊數是4,意思是儲存這個int型別的變數的位址 4 0,這就是對齊數為4的意思。二 為什麼要位元組對齊?位...

關於c語言中的位元組對齊padding問題

最近的工作中,發現有乙個很奇怪的問題,就是兩個結構體,裡面的字段的值完全是一樣的,但是用memcpy就是不返回0。大致是下面的 輸出是 aa bb bb cc 為什麼設的值都是一樣的,但是memcmp就是返回不一樣呢。memcpy還算是乙個比較常用的函式,特別是在c語言中比較乙個有較多字段的結構體,...