深入內(nèi)存對(duì)齊的詳解
1.引子
在結(jié)構(gòu)中,編譯器為結(jié)構(gòu)的每個(gè)成員按其自身的自然對(duì)界(alignment)條件分配空間。各個(gè)成員按照它們被聲明的順序在內(nèi)存中順序存儲(chǔ),第一個(gè)成員的地址和整個(gè)結(jié)構(gòu)的地址相同。
例如,下面的結(jié)構(gòu)各成員空間分配情況(假設(shè)對(duì)齊方式大于2字節(jié),即#pragma pack(n), n = 2,4,8...下文將討論#pragmapack()):
struct test
{
char x1;
short x2;
float x3;
char x4;
};
結(jié)構(gòu)的第一個(gè)成員x1,其偏移地址為0,占據(jù)了第1個(gè)字節(jié)。第二個(gè)成員x2為short類(lèi)型,其起始地址必須2字節(jié)對(duì)界,即偏移地址是2的倍數(shù)。因此,編譯器在x2和x1之間填充了一個(gè)空字節(jié),將x2放在了偏移地址為2的位置。結(jié)構(gòu)的第三個(gè)成員x3和第四個(gè)成員x4恰好落在其自然對(duì)界地址上,在它們前面不需要額外的填充字節(jié)。在test結(jié)構(gòu)中,成員x3要求4字節(jié)對(duì)界,是該結(jié)構(gòu)所有成員中要求的最大對(duì)界單元,因而test結(jié)構(gòu)的自然對(duì)界條件為4字節(jié),整個(gè)結(jié)構(gòu)體的大小是最大對(duì)界單元大小的整數(shù)倍(結(jié)構(gòu)體內(nèi)部有結(jié)構(gòu)體時(shí)也遵循這個(gè)規(guī)則,下文將提到),編譯器在成員x4后面填充了3個(gè)空字節(jié)。整個(gè)結(jié)構(gòu)所占據(jù)空間為12字節(jié)。
關(guān)于為什么要內(nèi)存對(duì)齊,參考<解析內(nèi)存對(duì)齊 Data alignment: Straighten up and fly right的詳解>??戳诉@篇文章便可以更輕松的理解下面的內(nèi)容。
好了,下面說(shuō)說(shuō)#pragma pack:
2.#pragma pack()
該預(yù)處理指令用來(lái)改變對(duì)齊參數(shù)。在缺省情況下,C編譯器為每一個(gè)變量或數(shù)據(jù)單元按其自然對(duì)界條件分配空間。一般地,可以通過(guò)下面的方法來(lái)改變?nèi)笔〉膶?duì)齊參數(shù):
· 使用偽指令#pragma pack (n),C編譯器將按照n字節(jié)對(duì)齊。
· 使用偽指令#pragma pack (),取消自定義字節(jié)對(duì)齊方式。
也可以寫(xiě)成:
#pragma pack(push,n)
#pragma pack(pop)
#pragma pack (n)表示每個(gè)成員的對(duì)齊單元不大于n(n為2的整數(shù)次冪)。這里規(guī)定的是上界,只影響對(duì)齊單元大于n的成員,對(duì)于對(duì)齊字節(jié)不大于n的成員沒(méi)有影響。其實(shí)從字面意思,pack是“包裹,打包”的意思,#pragma pack(n)規(guī)定n個(gè)字節(jié)是一個(gè)“包裹”,個(gè)人認(rèn)為實(shí)在不理解的話(huà)可以認(rèn)為處理器一次性可以從內(nèi)存中讀/寫(xiě)n個(gè)字節(jié),這樣好理解。對(duì)于大小小于n的成員,當(dāng)然是按照自己的對(duì)齊條件對(duì)齊,因?yàn)椴徽撛趺捶哦伎梢砸淮涡匀〕?。?duì)于對(duì)齊條件大于n個(gè)字節(jié)的成員,成員按照自身的對(duì)齊條件對(duì)齊和按照n字節(jié)對(duì)齊需要相同的讀取次數(shù),但按照n字節(jié)對(duì)齊節(jié)省空間,何樂(lè)而不為呢??梢詤⒖嘉疑厦嫣岬降?lt;解析內(nèi)存對(duì)齊 Data alignment: Straighten up and fly right的詳解>。下面是一位大牛的觀點(diǎn),和我說(shuō)的是一個(gè)意思:
All it means is that each member of it will require alignment no greater than n.It doesn't mean that each member will have alignment requirement n.Notice, after all, it's called pack and not align for a reason-- precisely because it controls packing, not alignment.
另外,GNU C還有如下的一種方式:
· __attribute__((aligned (n))),讓所作用的結(jié)構(gòu)成員對(duì)齊在n字節(jié)自然邊界上。如果結(jié)構(gòu)中有成員的長(zhǎng)度大于n,則按照最大成員的長(zhǎng)度來(lái)對(duì)齊。
· __attribute__ ((packed)),取消結(jié)構(gòu)在編譯過(guò)程中的優(yōu)化對(duì)齊,按照實(shí)際占用字節(jié)數(shù)進(jìn)行對(duì)齊。
以上的n = 1, 2, 4, 8, 16... 第一種方式較為常見(jiàn)。
3.結(jié)構(gòu)體內(nèi)成員如何找出自己的位置
首先遵循以下規(guī)則:
1. 每個(gè)成員分別取自己的對(duì)齊方式和#pragma pack指定的對(duì)齊參數(shù)二者的較小值作為自己的對(duì)齊方式。
2. 復(fù)雜類(lèi)型(如結(jié)構(gòu))的對(duì)齊方式是該類(lèi)型聲明時(shí)所使用的對(duì)齊方式,或者說(shuō)是聲明時(shí)它的所有成員使用的對(duì)齊參數(shù)的最大值,最后和此時(shí)的#pragma pack指定的對(duì)齊參數(shù)二者取極小值。大牛是這么說(shuō)的:
The documentation for #pragma pack(n) says that "The alignment of a member will be on a boundary that is either a multiple of n or a multiple of the size of the member,whichever is smaller". However I think this is incorrect; the docs should say that the alignment of a member will be on a boundary that is either a multiple of n or the alignment requirement of the member, whichever is smaller.
3. 對(duì)齊后的長(zhǎng)度必須是成員中最大的對(duì)齊參數(shù)(不是成員的大小)的整數(shù)倍,這樣在處理數(shù)組時(shí)可以保證每一項(xiàng)都邊界對(duì)齊。
4. 對(duì)于數(shù)組,比如:char a[3];這種,它的對(duì)齊方式和分別寫(xiě)3個(gè)char是一樣的。也就是說(shuō)它還是按1個(gè)字節(jié)對(duì)齊.
如果寫(xiě): typedef char Array3[3];
Array3這種類(lèi)型的對(duì)齊方式還是按1個(gè)字節(jié)對(duì)齊,而不是按它的長(zhǎng)度。
5. 不論類(lèi)型是什么,對(duì)齊的邊界一定是1,2,4,8,16,32,64....中的一個(gè)。
看一個(gè)簡(jiǎn)單的例子:
#pragma pack(8)
struct s1
{
short a;
long b;
};
struct s2
{
char c;
s1 d;
long long e;
};
#pragma pack()
成員對(duì)齊有一個(gè)重要的條件:每個(gè)成員分別對(duì)齊。即每個(gè)成員按自己的方式對(duì)齊.
也就是說(shuō)上面雖然指定了按8字節(jié)對(duì)齊,但并不是所有的成員都是以8字節(jié)對(duì)齊。其對(duì)齊的規(guī)則是,每個(gè)成員按其類(lèi)型的對(duì)齊參數(shù)(通常是這個(gè)類(lèi)型的大小)和指定對(duì)齊參數(shù)(這里是8字節(jié))中較小的一個(gè)對(duì)齊。并且結(jié)構(gòu)的長(zhǎng)度必須為所用過(guò)的所有對(duì)齊參數(shù)的整數(shù)倍(只要是最大的對(duì)齊參數(shù)的整數(shù)倍即可),不夠就補(bǔ)空字節(jié)(視編譯器而定)。
S1中,成員a是2字節(jié)默認(rèn)按2字節(jié)對(duì)齊,指定對(duì)齊參數(shù)為8,這兩個(gè)值中取2,a按2字節(jié)對(duì)齊;成員b是4個(gè)字節(jié),默認(rèn)是按4字節(jié)對(duì)齊,這時(shí)就按4字節(jié)對(duì)齊,a后補(bǔ)2個(gè)字節(jié)后存放b,所以sizeof(S1)應(yīng)該為8。8是4的倍數(shù),滿(mǎn)足上述的第3條規(guī)則。
S2中,c和S1中的a一樣,按2字節(jié)對(duì)齊,而d是個(gè)結(jié)構(gòu),它是8個(gè)字節(jié),它按什么對(duì)齊呢?對(duì)于結(jié)構(gòu)來(lái)說(shuō),它的默認(rèn)對(duì)齊方式就是該結(jié)構(gòu)定義(聲明)時(shí)它的所有成員使用的對(duì)齊參數(shù)中最大的一個(gè),S1的是4,小于指定的8。所以成員d就是按4字節(jié)對(duì)齊,c后補(bǔ)2個(gè)字節(jié),后面是8個(gè)字節(jié)的結(jié)構(gòu)體d。成員e是8個(gè)字節(jié),它是默認(rèn)按8字節(jié)對(duì)齊,和指定的一樣,所以它對(duì)到8字節(jié)的邊界上,這時(shí),已經(jīng)使用了12個(gè)字節(jié)了,所以d后又補(bǔ)上4個(gè)字節(jié),從第16個(gè)字節(jié)開(kāi)始放置成員e。這時(shí),長(zhǎng)度為24,已經(jīng)可以被最大對(duì)齊參數(shù)8(成員e按8字節(jié)對(duì)齊)整除。這樣,一共使用了24個(gè)字節(jié)。
上面的不夠復(fù)雜?再來(lái)一個(gè):
#pragma pack(4)
struct s1
{
char a;
double b;
};
#pragma pack()
#pragma pack(2)
struct s2
{
char c;
struct s1 st1;
};
#pragma pack()
#pragma pack(2)
struct s3
{
char a;
long b;
};
#pragma pack()
#pragma pack(4)
struct s4
{
char c;
struct s3 st3;
};
#pragma pack()
先看s1,a放在偏移地址為0的位置(第一個(gè)字節(jié))。b默認(rèn)8字節(jié)對(duì)齊,但指定對(duì)齊參數(shù)是4字節(jié),所以b按4字節(jié)對(duì)齊,放在偏移地址為4的位置,a后補(bǔ)3個(gè)字節(jié)。所以sizeof(s1)是12。結(jié)構(gòu)體s1的對(duì)齊參數(shù)是4,下面會(huì)用到。
再看s2,c放在第一個(gè)字節(jié)。st1自己的對(duì)齊參數(shù)是4,但此時(shí)指定的對(duì)齊參數(shù)是2,所以st1按照2字節(jié)對(duì)齊,c后補(bǔ)一個(gè)字節(jié)后存放st1。注意,st1內(nèi)部是不會(huì)變的,聲明s1時(shí)是什么樣就是什么樣,因?yàn)槲覀円WCsizeof(s2.st1) == sizeof(s1),如果不這樣就亂套了。這樣sizeof(s2)是14。結(jié)構(gòu)體s2的對(duì)齊參數(shù)是2,14是2的整數(shù)倍。
再看s3,a放在第一個(gè)字節(jié)。b默認(rèn)4字節(jié)對(duì)齊,但指定的對(duì)齊參數(shù)是2,所以b按2字節(jié)對(duì)齊,放在偏移地址為2的位置,a后補(bǔ)一個(gè)字節(jié)。sizeof(s3)是6。結(jié)構(gòu)體s3的對(duì)齊參數(shù)是2(后面會(huì)用到),6是2的整數(shù)倍。
最后看s4,c放在第一個(gè)字節(jié)。st3自己的對(duì)齊參數(shù)是2,指定的對(duì)齊參數(shù)是4,所以st3取極小值,按2字節(jié)對(duì)齊,放在偏移地址為2的位置,c后補(bǔ)一個(gè)字節(jié)。sizeof(s4)是8,結(jié)構(gòu)體的對(duì)齊參數(shù)是2,8是2的整數(shù)倍。
- 深入理解c/c++ 內(nèi)存對(duì)齊
- 深入理解C語(yǔ)言?xún)?nèi)存對(duì)齊
- 淺析內(nèi)存對(duì)齊與ANSI C中struct型數(shù)據(jù)的內(nèi)存布局
- 解析內(nèi)存對(duì)齊 Data alignment: Straighten up and fly right的詳解
- c++動(dòng)態(tài)內(nèi)存空間示例(自定義空間類(lèi)型大小和空間長(zhǎng)度)
- C/C++語(yǔ)言中結(jié)構(gòu)體的內(nèi)存分配小例子
- C/C++動(dòng)態(tài)分配與釋放內(nèi)存的區(qū)別詳細(xì)解析
- 深入解析C++ Data Member內(nèi)存布局
- C/C++ 傳遞動(dòng)態(tài)內(nèi)存的深入理解
- 關(guān)于C++內(nèi)存中字節(jié)對(duì)齊問(wèn)題的詳細(xì)介紹
- 基于C++中常見(jiàn)內(nèi)存錯(cuò)誤的總結(jié)
- VC++中內(nèi)存對(duì)齊實(shí)例教程
相關(guān)文章
C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)中堆排序的分析總結(jié)
堆是計(jì)算機(jī)科學(xué)中一類(lèi)特殊的數(shù)據(jù)結(jié)構(gòu)的統(tǒng)稱(chēng),通常是一個(gè)可以被看做一棵完全二叉樹(shù)的數(shù)組對(duì)象。而堆排序是利用堆這種數(shù)據(jù)結(jié)構(gòu)所設(shè)計(jì)的一種排序算法。本文將通過(guò)圖片詳細(xì)介紹堆排序,需要的可以參考一下2022-04-04C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)哈希表詳解
哈希表是一種根據(jù)關(guān)鍵碼去尋找值的數(shù)據(jù)映射結(jié)構(gòu),該結(jié)構(gòu)通過(guò)把關(guān)鍵碼映射的位置去尋找存放值的地方,說(shuō)起來(lái)可能感覺(jué)有點(diǎn)復(fù)雜,我想我舉個(gè)例子你就會(huì)明白了,最典型的的例子就是字典2022-02-02C語(yǔ)言設(shè)計(jì)實(shí)現(xiàn)掃描器的自動(dòng)機(jī)的示例詳解
這篇文章主要為大家詳細(xì)介紹了如何利用C語(yǔ)言設(shè)計(jì)實(shí)現(xiàn)掃描器的自動(dòng)機(jī),可識(shí)別的單詞包括:關(guān)鍵字、界符、標(biāo)識(shí)符和常整型數(shù),感興趣的小伙伴可以了解一下2022-12-12數(shù)據(jù)結(jié)構(gòu)之堆的具體使用
本文主要介紹了數(shù)據(jù)結(jié)構(gòu)之堆的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02C++數(shù)據(jù)結(jié)構(gòu)模板進(jìn)階的多方面分析
今天我要給大家介紹C++中的模板更深的一些知識(shí)。有關(guān)于非類(lèi)型的模板參數(shù)和模板特化的一些知識(shí),感興趣的朋友快來(lái)看看吧2022-02-02詳解C++設(shè)計(jì)模式編程中責(zé)任鏈模式的應(yīng)用
這篇文章主要介紹了C++設(shè)計(jì)模式編程中責(zé)任鏈模式的應(yīng)用,責(zé)任鏈模式使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求,從而避免請(qǐng)求的發(fā)送者和接收者之間的耦合關(guān)系,需要的朋友可以參考下2016-03-03C++中delete和delete[]的區(qū)別詳細(xì)介紹
一直對(duì)C++中的delete和delete[]的區(qū)別不甚了解,今天遇到了,上網(wǎng)查了一下,得出了結(jié)論,拿出來(lái)和大家分享一下2012-11-11