C语言中的字节对齐以及其相关处理
首先,我們來了解下一些基本原理:
一、什么是字節對齊
一個基本類型的變量在內存中占用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).結構體整體的有效對齊值必須為其最寬基本類型成員大小的整數倍;或者說是,結構體整體的大小為結構體中最寬基本類型成員大小的整數倍,如有需要,編譯器會在最末一個成員之后加上填充字節;換句話說是,結構體整體的有效對齊值按照結構體中最寬基本類型成員的大小和指定對齊值中較小的那個值進行;
特別注意:如果指定對齊值大于自身對齊值,則指定對齊值無效;
?
然后,我們來看一下C編譯器對字節對齊的相關處理?
在缺省情況下,C編譯器為每一個變量或是數據單元按其自然對界條件分配空間。?
在結構中,編譯器為結構的每個成員按其自然對界(alignment)條件分配空間。各個成員按照它們被聲明的順序在內存中順序存儲(成員之間可能有插入的空字節),第一個成員的地址和整個結構的地址相同。?
C編譯器缺省的結構成員自然對界條件為“N字節對齊”,N即該成員數據類型的長度。如int型成員的自然對界條件為4字節對齊,而double類型的結構成員的自然對界條件為8字節對齊。若該成員的起始偏移不位于該成員的“默認自然對界條件”上,則在前一個節面后面添加適當個數的空字節。?
C編譯器缺省的結構整體的自然對界條件為:該結構所有成員中要求的最大自然對界條件。若結構體各成員長度之和不為“結構整體自然對界條件的整數倍,則在最后一個成員后填充空字節。
例子1(分析結構各成員的默認字節對界條界條件和結構整體的默認字節對界條件):
struct Test { char x1; // 成員x1為char型(其起始地址必須1字節對界),其偏移地址為0 char x2; // 成員x2為char型(其起始地址必須1字節對界,其偏移地址為1 float x3; // 成員x3為float型(其起始地址必須4字節對界),編譯器在x2和x3之間填充了兩個空字節,其偏移地址為4 char x4; // 成員x4為char型(其起始地址必須1字節對界),其偏移地址為8 };因為Test結構體中,最大的成員為flaot x3,因些此結構體的自然對界條件為4字節對齊。則結構體長度就為12字節,內存布局為1100 1111 1000。
例子2:
#include <stdio.h> //#pragma pack(2) typedef struct {int aa1; //4個字節對齊 1111char bb1;//1個字節對齊 1short cc1;//2個字節對齊 011char dd1; //1個字節對齊 1 } testlength1; int length1 = sizeof(testlength1); //4個字節對齊,占用字節1111 1011 1000,length = 12 typedef struct {char bb2;//1個字節對齊 1int aa2; //4個字節對齊 01111short cc2;//2個字節對齊 11char dd2; //1個字節對齊 1 } testlength2; int length2 = sizeof(testlength2); //4個字節對齊,占用字節1000 1111 1110,length = 12 typedef struct {char bb3; //1個字節對齊 1char dd3; //1個字節對齊 1int aa3; //4個字節對齊 001111short cc23//2個字節對齊 11 } testlength3; int length3 = sizeof(testlength3); //4個字節對齊,占用字節1100 1111 1100,length = 12 typedef struct {char bb4; //1個字節對齊 1char dd4; //1個字節對齊 1short cc4;//2個字節對齊 11int aa4; //4個字節對齊 1111 } testlength4; int length4 = sizeof(testlength4); //4個字節對齊,占用字節1111 1111,length = 8int main(void) {printf("length1 = %d.\n",length1);printf("length2 = %d.\n",length2);printf("length3 = %d.\n",length3);printf("length4 = %d.\n",length4);return 0; }改變缺省的對界條件(指定對界)
· 使用偽指令#pragma pack (n),C編譯器將按照n個字節對齊。
· 使用偽指令#pragma pack (),取消自定義字節對齊方式。
這時,對齊規則為:
1、數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset為0的地方,以后每個數據成員的對齊按照#pragma pack指定的數值和這個數據成員自身長度中,比較小的那個進行。
2、結構(或聯合)的整體對齊規則:在數據成員完成各自對齊之后,結構(或聯合)本身也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大數據成員長度中,比較小的那個進行。
結合1、2推斷:當#pragma pack的n值等于或超過所有數據成員長度的時候,這個n值的大小將不產生任何效果。
?
因此,當使用偽指令#pragma pack (2)時,Test結構體的大小為8,內存布局為11 11 11 10。
需要注意一點,當結構體中包含一個子結構體時,子結構中的成員按照#pragma pack指定的數值和子結構最大數據成員長度中,比較小的那個進行進行對齊。
例3:
#pragma pack(8) struct s1{ short a; long b; };struct s2{ char c; s1 d; long long e; }; #pragma pack()sizeof(s2)的結果為24。S1的內存布局為1100 1111,S2的內存布局為1000 1100 1111 0000 1111 1111。
例4:
#include <stdio.h> #pragma pack(2) typedef struct {int aa1; //2個字節對齊 1111char bb1;//1個字節對齊 1short cc1;//2個字節對齊 011char dd1; //1個字節對齊 1 } testlength1; int length1 = sizeof(testlength1); //2個字節對齊,占用字節11 11 10 11 10,length = 10 typedef struct {char bb2;//1個字節對齊 1int aa2; //2個字節對齊 01111short cc2;//2個字節對齊 11char dd2; //1個字節對齊 1 } testlength2; int length2 = sizeof(testlength2); //2個字節對齊,占用字節10 11 11 11 10,length = 10 typedef struct {char bb3; //1個字節對齊 1char dd3; //1個字節對齊 1int aa3; //2個字節對齊 11 11short cc23//2個字節對齊 11 } testlength3; int length3 = sizeof(testlength3); //2個字節對齊,占用字節11 11 11 11,length = 8 typedef struct {char bb4; //1個字節對齊 1char dd4; //1個字節對齊 1short cc4;//2個字節對齊 11int aa4; //2個字節對齊 11 11 } testlength4; int length4 = sizeof(testlength4); //2個字節對齊,占用字節11 11 11 11,length = 8int main(void) {printf("length1 = %d.\n",length1);printf("length2 = %d.\n",length2);printf("length3 = %d.\n",length3);printf("length4 = %d.\n",length4);return 0; }另外,還有如下的一種方式:
· __attribute((aligned (n))),讓所作用的結構成員對齊在n字節自然邊界上。如果結構中有成員的長度大于n,則按照最大成員的長度來對齊。
· __attribute__ ((packed)),取消結構在編譯過程中的優化對齊,按照實際占用字節數進行對齊。
以上的n = 1, 2, 4, 8, 16... 第一種方式較為常見。
?
轉載于:https://www.cnblogs.com/xd-elegant/p/4153463.html
總結
以上是生活随笔為你收集整理的C语言中的字节对齐以及其相关处理的全部內容,希望文章能夠幫你解決所遇到的問題。