C语言中的字节对齐以及其相关处理
首先,我們來了解下一些基本原理:
一、什么是字節(jié)對(duì)齊
一個(gè)基本類型的變量在內(nèi)存中占用n個(gè)字節(jié),則該變量的起始地址必須能夠被n整除,即: 存放起始地址 % n = 0,那么,就成該變量是字節(jié)對(duì)齊的;對(duì)于結(jié)構(gòu)體、聯(lián)合體而言,這個(gè)n取其所有基本類型的成員中占用空間字節(jié)數(shù)最大的那個(gè);
內(nèi)存空間是以字節(jié)為基本單位進(jìn)行劃分的,從理論上講,似乎對(duì)任何類型的變量的訪問都可以從任何地址處開始,但實(shí)際情況是在訪問特定類型變量的時(shí)候經(jīng)常是從特定的內(nèi)存地址處開始訪問,這就需要各種類型的數(shù)據(jù)只能按照一定的規(guī)則在空間上排列,而不是順序的一個(gè)接一個(gè)地排放;究其原因,是為了使不同架構(gòu)的CPU可以提高訪問內(nèi)存的速度,就規(guī)定了對(duì)于特定類型的數(shù)據(jù)只能從特定的內(nèi)存位置處開始訪問;所以,各種類型的數(shù)據(jù)只能按照相應(yīng)的規(guī)則在內(nèi)存空間上排放,而不能順序地、連續(xù)地、一個(gè)一個(gè)地排放;這就是內(nèi)存對(duì)齊;
二、為什么需要字節(jié)對(duì)齊
由于各種硬件平臺(tái)對(duì)存儲(chǔ)空間的處理上有很大的不同;一些平臺(tái)對(duì)某些特定類型的數(shù)據(jù)只能從某個(gè)特定內(nèi)存地址處開始訪問;比如:有些架構(gòu)的CPU在訪問一個(gè)沒有進(jìn)行對(duì)齊的變量的時(shí)候會(huì)發(fā)生錯(cuò)誤,那么在這種架構(gòu)下編程時(shí)就必須保證字節(jié)對(duì)齊;其它平臺(tái)可能沒有這種情況,但最常見的是,如果不按照適合其平臺(tái)要求對(duì)數(shù)據(jù)進(jìn)行對(duì)齊,會(huì)在存取效率上帶來損失;比如,有些平臺(tái)每次讀取數(shù)據(jù)都是從偶地址處開始,如果一個(gè)int(假設(shè)為32位系統(tǒng))型數(shù)據(jù)從偶地址處開始存放,那么只需要一個(gè)讀指令周期就可以完全讀出這個(gè)32bit的int型數(shù)據(jù),相反,如果這個(gè)32bit的int型數(shù)據(jù)是從奇地址處開始存放,那么就需要兩個(gè)讀指令周期才能完全讀出這個(gè)32bit的int數(shù)據(jù),并且還需要對(duì)這兩次讀出的結(jié)果的高低字節(jié)進(jìn)行重新拼湊才能得到正確的32bit數(shù)據(jù);這個(gè)時(shí)候,CPU的讀取效率明顯下降;
三、字節(jié)對(duì)齊規(guī)則
預(yù)處理指令#pragma pack(align_value)用于指定對(duì)齊值,而預(yù)處理指令#pragma pack()用于取消上次設(shè)定的對(duì)齊值,恢復(fù)默認(rèn)對(duì)齊值;
字節(jié)對(duì)齊是針對(duì)基本類型變量的;基本類型變量有:char、unsigned char、short、unsigned short、int、unsigned int、long、unsigned long、long long、unsigned long long、float、double,等等;所以,對(duì)于結(jié)構(gòu)體的對(duì)齊也只能按照其成員變量中的基本類型來對(duì)齊了;
有四個(gè)概念需要理解:
A、數(shù)據(jù)類型自身的對(duì)齊值:
?? 是指對(duì)該數(shù)據(jù)類型使用sizeof()操作符進(jìn)行操作所得到的大小(單位,字節(jié));比如,對(duì)于[unsigned] char類型的數(shù)據(jù),其自身對(duì)齊值為1字節(jié);對(duì)于[unsigned] short類型的數(shù)據(jù),其自身對(duì)齊值是2字節(jié);對(duì)于[unsigned] int、[unsigned] long、[unsigned] long long、float、double等數(shù)據(jù)類型,其自身對(duì)齊值是4字節(jié);
B、結(jié)構(gòu)體、聯(lián)合體、類的自身對(duì)齊值:
?? 是指其所有基本類型的成員中,自身對(duì)齊值最大的那個(gè)值;如果這些復(fù)合類型中有嵌套類型或復(fù)合類型的變量,則需要把這些嵌套的類型或復(fù)合類型的變量拆解成基本類型的成員之后再對(duì)齊;
C、指定對(duì)齊值:
?? 是指使用預(yù)處理指令#pragma pack(align_value)指定的對(duì)齊值align_value;
D、數(shù)據(jù)成員、結(jié)構(gòu)體和類的有效對(duì)齊值:
?? 是指其自身對(duì)齊值和指定對(duì)齊值中較小的那個(gè)值;
其中,有效對(duì)齊值是最終用來決定數(shù)據(jù)存放地址方式的值,最重要;設(shè)定有效對(duì)齊值為N,就表示"對(duì)齊在N字節(jié)上",也就是說,該數(shù)據(jù)的"存放起始地址%N=0";
因此,每個(gè)類型的數(shù)據(jù)的有效對(duì)齊值就是其自身對(duì)齊值(通常是這個(gè)類型的大小)和指定對(duì)齊值(不指定則取默認(rèn)值)中較小的那個(gè)值,并且結(jié)構(gòu)體自身對(duì)齊值是其所有成員中自身對(duì)齊值最大的那個(gè)值;
字節(jié)對(duì)齊的細(xì)節(jié)與編譯器的實(shí)現(xiàn)有關(guān),但一般來說,結(jié)構(gòu)體需要滿足以下幾個(gè)準(zhǔn)則:
1).從結(jié)構(gòu)體外部來看,結(jié)構(gòu)體變量的首地址能夠被其最寬基本成員的大小整除;從結(jié)構(gòu)體內(nèi)部來看,它的第一個(gè)數(shù)據(jù)成員的地址相對(duì)于整個(gè)結(jié)構(gòu)體首地址的偏移量為0,也就是說,結(jié)構(gòu)體的第一個(gè)數(shù)據(jù)成員存放在偏移量為0的地方;
2).結(jié)構(gòu)體中的每個(gè)數(shù)據(jù)成員的有效對(duì)齊值都取其自身對(duì)齊值和指定對(duì)齊值中的較小的那個(gè)對(duì)齊值;或者說是,結(jié)構(gòu)體中的每個(gè)數(shù)據(jù)成員相對(duì)于結(jié)構(gòu)體首地址的偏移量都是該數(shù)據(jù)成員大小和指定對(duì)齊值中較小的那個(gè)值(或有效對(duì)齊值)的整數(shù)倍,如有需要,編譯器會(huì)在數(shù)據(jù)成員之間加上填充字節(jié);
3).如果結(jié)構(gòu)體中還有嵌套的結(jié)構(gòu)體或結(jié)構(gòu)體變量,那么就要把這些嵌套進(jìn)去的結(jié)構(gòu)體或結(jié)構(gòu)體變量拆成基本類型成員,并取其最長(zhǎng)的基本類型成員的對(duì)齊方式;
4).結(jié)構(gòu)體整體的有效對(duì)齊值必須為其最寬基本類型成員大小的整數(shù)倍;或者說是,結(jié)構(gòu)體整體的大小為結(jié)構(gòu)體中最寬基本類型成員大小的整數(shù)倍,如有需要,編譯器會(huì)在最末一個(gè)成員之后加上填充字節(jié);換句話說是,結(jié)構(gòu)體整體的有效對(duì)齊值按照結(jié)構(gòu)體中最寬基本類型成員的大小和指定對(duì)齊值中較小的那個(gè)值進(jìn)行;
特別注意:如果指定對(duì)齊值大于自身對(duì)齊值,則指定對(duì)齊值無效;
?
然后,我們來看一下C編譯器對(duì)字節(jié)對(duì)齊的相關(guān)處理?
在缺省情況下,C編譯器為每一個(gè)變量或是數(shù)據(jù)單元按其自然對(duì)界條件分配空間。?
在結(jié)構(gòu)中,編譯器為結(jié)構(gòu)的每個(gè)成員按其自然對(duì)界(alignment)條件分配空間。各個(gè)成員按照它們被聲明的順序在內(nèi)存中順序存儲(chǔ)(成員之間可能有插入的空字節(jié)),第一個(gè)成員的地址和整個(gè)結(jié)構(gòu)的地址相同。?
C編譯器缺省的結(jié)構(gòu)成員自然對(duì)界條件為“N字節(jié)對(duì)齊”,N即該成員數(shù)據(jù)類型的長(zhǎng)度。如int型成員的自然對(duì)界條件為4字節(jié)對(duì)齊,而double類型的結(jié)構(gòu)成員的自然對(duì)界條件為8字節(jié)對(duì)齊。若該成員的起始偏移不位于該成員的“默認(rèn)自然對(duì)界條件”上,則在前一個(gè)節(jié)面后面添加適當(dāng)個(gè)數(shù)的空字節(jié)。?
C編譯器缺省的結(jié)構(gòu)整體的自然對(duì)界條件為:該結(jié)構(gòu)所有成員中要求的最大自然對(duì)界條件。若結(jié)構(gòu)體各成員長(zhǎng)度之和不為“結(jié)構(gòu)整體自然對(duì)界條件的整數(shù)倍,則在最后一個(gè)成員后填充空字節(jié)。
例子1(分析結(jié)構(gòu)各成員的默認(rèn)字節(jié)對(duì)界條界條件和結(jié)構(gòu)整體的默認(rèn)字節(jié)對(duì)界條件):
struct Test { char x1; // 成員x1為char型(其起始地址必須1字節(jié)對(duì)界),其偏移地址為0 char x2; // 成員x2為char型(其起始地址必須1字節(jié)對(duì)界,其偏移地址為1 float x3; // 成員x3為float型(其起始地址必須4字節(jié)對(duì)界),編譯器在x2和x3之間填充了兩個(gè)空字節(jié),其偏移地址為4 char x4; // 成員x4為char型(其起始地址必須1字節(jié)對(duì)界),其偏移地址為8 };因?yàn)門est結(jié)構(gòu)體中,最大的成員為flaot x3,因些此結(jié)構(gòu)體的自然對(duì)界條件為4字節(jié)對(duì)齊。則結(jié)構(gòu)體長(zhǎng)度就為12字節(jié),內(nèi)存布局為1100 1111 1000。
例子2:
#include <stdio.h> //#pragma pack(2) typedef struct {int aa1; //4個(gè)字節(jié)對(duì)齊 1111char bb1;//1個(gè)字節(jié)對(duì)齊 1short cc1;//2個(gè)字節(jié)對(duì)齊 011char dd1; //1個(gè)字節(jié)對(duì)齊 1 } testlength1; int length1 = sizeof(testlength1); //4個(gè)字節(jié)對(duì)齊,占用字節(jié)1111 1011 1000,length = 12 typedef struct {char bb2;//1個(gè)字節(jié)對(duì)齊 1int aa2; //4個(gè)字節(jié)對(duì)齊 01111short cc2;//2個(gè)字節(jié)對(duì)齊 11char dd2; //1個(gè)字節(jié)對(duì)齊 1 } testlength2; int length2 = sizeof(testlength2); //4個(gè)字節(jié)對(duì)齊,占用字節(jié)1000 1111 1110,length = 12 typedef struct {char bb3; //1個(gè)字節(jié)對(duì)齊 1char dd3; //1個(gè)字節(jié)對(duì)齊 1int aa3; //4個(gè)字節(jié)對(duì)齊 001111short cc23//2個(gè)字節(jié)對(duì)齊 11 } testlength3; int length3 = sizeof(testlength3); //4個(gè)字節(jié)對(duì)齊,占用字節(jié)1100 1111 1100,length = 12 typedef struct {char bb4; //1個(gè)字節(jié)對(duì)齊 1char dd4; //1個(gè)字節(jié)對(duì)齊 1short cc4;//2個(gè)字節(jié)對(duì)齊 11int aa4; //4個(gè)字節(jié)對(duì)齊 1111 } testlength4; int length4 = sizeof(testlength4); //4個(gè)字節(jié)對(duì)齊,占用字節(jié)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; }改變?nèi)笔〉膶?duì)界條件(指定對(duì)界)
· 使用偽指令#pragma pack (n),C編譯器將按照n個(gè)字節(jié)對(duì)齊。
· 使用偽指令#pragma pack (),取消自定義字節(jié)對(duì)齊方式。
這時(shí),對(duì)齊規(guī)則為:
1、數(shù)據(jù)成員對(duì)齊規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個(gè)數(shù)據(jù)成員放在offset為0的地方,以后每個(gè)數(shù)據(jù)成員的對(duì)齊按照#pragma pack指定的數(shù)值和這個(gè)數(shù)據(jù)成員自身長(zhǎng)度中,比較小的那個(gè)進(jìn)行。
2、結(jié)構(gòu)(或聯(lián)合)的整體對(duì)齊規(guī)則:在數(shù)據(jù)成員完成各自對(duì)齊之后,結(jié)構(gòu)(或聯(lián)合)本身也要進(jìn)行對(duì)齊,對(duì)齊將按照#pragma pack指定的數(shù)值和結(jié)構(gòu)(或聯(lián)合)最大數(shù)據(jù)成員長(zhǎng)度中,比較小的那個(gè)進(jìn)行。
結(jié)合1、2推斷:當(dāng)#pragma pack的n值等于或超過所有數(shù)據(jù)成員長(zhǎng)度的時(shí)候,這個(gè)n值的大小將不產(chǎn)生任何效果。
?
因此,當(dāng)使用偽指令#pragma pack (2)時(shí),Test結(jié)構(gòu)體的大小為8,內(nèi)存布局為11 11 11 10。
需要注意一點(diǎn),當(dāng)結(jié)構(gòu)體中包含一個(gè)子結(jié)構(gòu)體時(shí),子結(jié)構(gòu)中的成員按照#pragma pack指定的數(shù)值和子結(jié)構(gòu)最大數(shù)據(jù)成員長(zhǎng)度中,比較小的那個(gè)進(jìn)行進(jìn)行對(duì)齊。
例3:
#pragma pack(8) struct s1{ short a; long b; };struct s2{ char c; s1 d; long long e; }; #pragma pack()sizeof(s2)的結(jié)果為24。S1的內(nèi)存布局為1100 1111,S2的內(nèi)存布局為1000 1100 1111 0000 1111 1111。
例4:
#include <stdio.h> #pragma pack(2) typedef struct {int aa1; //2個(gè)字節(jié)對(duì)齊 1111char bb1;//1個(gè)字節(jié)對(duì)齊 1short cc1;//2個(gè)字節(jié)對(duì)齊 011char dd1; //1個(gè)字節(jié)對(duì)齊 1 } testlength1; int length1 = sizeof(testlength1); //2個(gè)字節(jié)對(duì)齊,占用字節(jié)11 11 10 11 10,length = 10 typedef struct {char bb2;//1個(gè)字節(jié)對(duì)齊 1int aa2; //2個(gè)字節(jié)對(duì)齊 01111short cc2;//2個(gè)字節(jié)對(duì)齊 11char dd2; //1個(gè)字節(jié)對(duì)齊 1 } testlength2; int length2 = sizeof(testlength2); //2個(gè)字節(jié)對(duì)齊,占用字節(jié)10 11 11 11 10,length = 10 typedef struct {char bb3; //1個(gè)字節(jié)對(duì)齊 1char dd3; //1個(gè)字節(jié)對(duì)齊 1int aa3; //2個(gè)字節(jié)對(duì)齊 11 11short cc23//2個(gè)字節(jié)對(duì)齊 11 } testlength3; int length3 = sizeof(testlength3); //2個(gè)字節(jié)對(duì)齊,占用字節(jié)11 11 11 11,length = 8 typedef struct {char bb4; //1個(gè)字節(jié)對(duì)齊 1char dd4; //1個(gè)字節(jié)對(duì)齊 1short cc4;//2個(gè)字節(jié)對(duì)齊 11int aa4; //2個(gè)字節(jié)對(duì)齊 11 11 } testlength4; int length4 = sizeof(testlength4); //2個(gè)字節(jié)對(duì)齊,占用字節(jié)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))),讓所作用的結(jié)構(gòu)成員對(duì)齊在n字節(jié)自然邊界上。如果結(jié)構(gòu)中有成員的長(zhǎng)度大于n,則按照最大成員的長(zhǎng)度來對(duì)齊。
· __attribute__ ((packed)),取消結(jié)構(gòu)在編譯過程中的優(yōu)化對(duì)齊,按照實(shí)際占用字節(jié)數(shù)進(jìn)行對(duì)齊。
以上的n = 1, 2, 4, 8, 16... 第一種方式較為常見。
?
轉(zhuǎn)載于:https://www.cnblogs.com/xd-elegant/p/4153463.html
總結(jié)
以上是生活随笔為你收集整理的C语言中的字节对齐以及其相关处理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。