一、為什么結(jié)構(gòu)體內(nèi)存對(duì)齊
其實(shí)我們都知道,結(jié)構(gòu)體只是一些數(shù)據(jù)的集合然爆,它本身什么都沒(méi)有投蝉。我們所謂的結(jié)構(gòu)體地址枚尼,其實(shí)就是結(jié)構(gòu)體第一個(gè)元素的地址。這樣,如果結(jié)構(gòu)體各個(gè)元素之間不存在內(nèi)存對(duì)齊問(wèn)題,他們都挨著排放的向挖。對(duì)于32位機(jī),32位編譯器(這是目前常見的環(huán)境炕舵,其他環(huán)境也會(huì)有內(nèi)存對(duì)齊問(wèn)題)何之,就很可能操作一個(gè)問(wèn)題,就是當(dāng)你想要去訪問(wèn)結(jié)構(gòu)體中的一個(gè)數(shù)據(jù)的時(shí)候咽筋,需要你操作兩次數(shù)據(jù)總線溶推,因?yàn)檫@個(gè)數(shù)據(jù)卡在中間,如圖:在上圖中奸攻,對(duì)于第2個(gè)short數(shù)據(jù)進(jìn)行訪問(wèn)的時(shí)候悼潭,在32位機(jī)器上就要操作兩次數(shù)據(jù)總線。這樣會(huì)非常影響數(shù)據(jù)讀寫的效率舞箍,所以就引入了內(nèi)存對(duì)齊的問(wèn)題。另外一層原因是:某些硬件平臺(tái)只能從規(guī)定的地址處取某些特定類型的數(shù)據(jù)皆疹,否則會(huì)拋出硬件異常疏橄。
二、結(jié)構(gòu)體內(nèi)存對(duì)齊的規(guī)則
下表是Windows XP/DEV-C++和Linux/GCC中基本數(shù)據(jù)類型的長(zhǎng)度和默認(rèn)對(duì)齊模數(shù)略就。
char | short | int | long | float | double | long long | long double | ||
---|---|---|---|---|---|---|---|---|---|
Win-32 | 長(zhǎng)度 | 1 | 2 | 4 | 4 | 4 | 8 | 8 | 8 |
Linux-32 | 長(zhǎng)度 | 1 | 2 | 4 | 4 | 4 | 8 | 8 | 12 |
Linux-64 | 長(zhǎng)度 | 1 | 2 | 4 | 8 | 4 | 8 | 8 | 16 |
MAC-64 | 長(zhǎng)度 | 1 | 2 | 4 | 8 | 4 | 8 | 8 | 16 |
1.未指定#pragma pack時(shí)
a.第一個(gè)成員起始于0偏移處捎迫;
b.每個(gè)成員按其類型大小和指定對(duì)齊參數(shù)n中較小的一個(gè)進(jìn)行對(duì)齊;
c.結(jié)構(gòu)體總長(zhǎng)度必須為所有對(duì)齊參數(shù)的整數(shù)倍,或者理解為最大類型的整數(shù)倍表牢;
d.對(duì)于數(shù)組窄绒,可以拆開看做n個(gè)數(shù)組元素。
2.指定#pragma pack(n)時(shí)
a. n必須為0 1 2 4 8 16崔兴,其中為0時(shí)就相當(dāng)于未指定#pragma pack
b. 當(dāng)結(jié)構(gòu)體中的類型小于n時(shí)彰导,按該類型的對(duì)齊規(guī)則對(duì)齊,對(duì)于大于等于n的類型敲茄,按n對(duì)齊位谋。
c.結(jié)構(gòu)體總長(zhǎng)度: 1.當(dāng)結(jié)構(gòu)體中的類型全都小于n時(shí),總長(zhǎng)度為所有對(duì)齊參數(shù)的整數(shù)倍,或者理解為最大類型的整數(shù)倍堰燎;2.當(dāng)結(jié)構(gòu)體中有大于等于n的類型掏父,按n的整數(shù)倍對(duì)齊。
三秆剪、具體舉例 運(yùn)行環(huán)境為MAC-64
1.未指定#pragma pack時(shí)
例1
struct S1
{
short a1;
short a2;
short a3;
};
struct S2
{
long a1;
short a2;
};
打印結(jié)果為 size of s1:6 -- size of s2:16
sizeof(S1) = 6; 這個(gè)很好理解赊淑,三個(gè)short都為2爵政。
例2
struct S1{
int a;
char b;
short c;
};
struct S2{
char b;
int a;
short c;
};
打印結(jié)果:size of s1:8 -- size of s2:12
上圖中一個(gè)單元格表示一個(gè)字節(jié) 1表示占用,0表示空閑
由上圖可以看出要按類型字節(jié)長(zhǎng)度的整數(shù)倍位置對(duì)齊陶缺,其他位置被空閑钾挟。最終所占長(zhǎng)度為最大類型的整數(shù)倍。
- S1中a 占4個(gè)字節(jié)组哩。b占1個(gè)字節(jié)等龙,c占2個(gè)字節(jié),但是c要從2的倍數(shù)處對(duì)齊伶贰,因此蛛砰,正好S1占8個(gè)字節(jié),是所有類型的整數(shù)倍黍衙,占8個(gè)字節(jié)泥畅。
- S2中b占1個(gè)字節(jié) 占0位置,a 占4個(gè)字節(jié) 從索引4開始對(duì)齊琅翻,c占2個(gè)字節(jié) 從索引8開始對(duì)齊位仁,總長(zhǎng)為10,但是整個(gè)空間應(yīng)該為所有類型的整數(shù)倍方椎,因此需要2個(gè)空閑位聂抢。總共12個(gè)字節(jié)
例3 下面是結(jié)構(gòu)體嵌套情況
struct S1{
int a;
double b;
float c;
};
struct S2{
char e[2];
int f;
double g;
short h;
struct S1 s;
};
打印結(jié)果:size of s1:24 -- size of s2:48
sizeof(S1) = 24; 這個(gè)比較好理解棠众,int為4琳疏,double為8,float為4闸拿,總長(zhǎng)為8的倍數(shù)空盼,補(bǔ)齊,所以整個(gè)S1為24新荤。
我們看看S2的內(nèi)存布局:
e | f | g | h | s |
---|---|---|---|---|
1 1 * * | 1 1 1 1 | 1 1 1 1 1 1 1 1 | 1 1 * * * * * * | 1111**** 11111111 11****** |
s 為結(jié)構(gòu)體S1 看做一個(gè)整體揽趾,他的最大類型double為8位 需要按8的倍數(shù)對(duì)齊,因此從索引24開始 因此總共48位
例4
struct S1
{
short a;
int b;
};
struct S2
{
char c;
struct S1 d;
double e;
};
打印結(jié)果:size of s1:8 -- size of s2:24
內(nèi)存布局如下:
S1 | a | b |
---|---|---|
1 1 * * | 1 1 1 1 |
S2 | c | d | e |
---|---|---|---|
1 * * * | 11** 1111 | * * * * 1 1 1 1 1 1 1 1 |
綜上可知
在結(jié)構(gòu)體嵌套的情況下 結(jié)構(gòu)體看做一個(gè)整體苛骨,他按他自己的最大成員的類型進(jìn)行對(duì)齊篱瞎,最終按整個(gè)結(jié)構(gòu)體中最大類型對(duì)齊。
指定#pragma pack(n)時(shí)(#pragma pack 是編譯預(yù)處理指令痒芝,可以指定按多少字節(jié)對(duì)齊)
a. n必須為0 1 2 4 8 16奔缠,其中為0時(shí)就相當(dāng)于未指定#pragma pack
b. 當(dāng)結(jié)構(gòu)體中的類型小于n時(shí),按該類型的對(duì)齊規(guī)則對(duì)齊吼野,對(duì)于大于等于n的類型校哎,按n對(duì)齊。
c.結(jié)構(gòu)體總長(zhǎng)度: 1.當(dāng)結(jié)構(gòu)體中的類型全都小于n時(shí),總長(zhǎng)度為所有對(duì)齊參數(shù)的整數(shù)倍,或者理解為最大類型的整數(shù)倍闷哆;
2.當(dāng)結(jié)構(gòu)體中有大于等于n的類型腰奋,按n的整數(shù)倍對(duì)齊。
例1
#pragma pack(1)
struct S1
{
char a;
short b;
short c;
};
struct S2
{
long d;
short e;
char f;
};
打印結(jié)果為:size of s1:5 -- size of s2:11
很好理解n=1時(shí)抱怔,按順序排放
pragma pack(2) 時(shí)的結(jié)果:size of s1:6 -- size of s2:12
此時(shí) S1中的short類型正好等于2個(gè)字節(jié) 因此按2個(gè)字節(jié)對(duì)齊劣坊,S2中l(wèi)ong類型占8字節(jié)大于2 因此按2字節(jié)對(duì)齊
pragma pack(4) 時(shí)的結(jié)果: size of s1:6 -- size of s2:12
此時(shí)S1中的所有類型長(zhǎng)度都小于4,因此按S1中最大類型short對(duì)齊屈留。S2中l(wèi)ong類型占8字節(jié)大于4 因此按4字節(jié)對(duì)齊
pragma pack(8) 時(shí)的結(jié)果:size of s1:6 -- size of s2:16
此時(shí)S1中的所有類型長(zhǎng)度都小于8局冰,因此按S1中最大類型short對(duì)齊。
S2中l(wèi)ong類型占8字節(jié)正好等于8 因此按8字節(jié)對(duì)齊灌危,因此為16字節(jié)
pragma pack(16) 時(shí)的結(jié)果:size of s1:6 -- size of s2:16
此時(shí)S1還是所有類型小于16康二,因此按S1中最大類型short對(duì)齊。
S2中l(wèi)ong類型占8字節(jié)小于16 因此按S2中最大類型long 8字節(jié)對(duì)齊勇蝙,因此為16字節(jié).
例2 結(jié)構(gòu)體嵌套情況
#pragma pack(1)
struct S1
{
short a;
int b;
};
struct S2
{
char c;
struct S1 d;
double e;
};
打印結(jié)果:size of s1:6 -- size of s2:15
很好理解所有結(jié)構(gòu)體順序排放
pragma pack(2) 的時(shí)候:size of s1:6 -- size of s2:16
S1還是6字節(jié)沫勿。
S2的布局情況如下
S2 | c | d | e |
---|---|---|---|
1 * | 11 1111 | 11111111 |
pragma pack(4) 的時(shí)候: size of s1:8 -- size of s2:20
此時(shí)S1中的int類型正好是4 因此按4字節(jié)對(duì)齊 因此占8字節(jié)
S2的內(nèi)存布局如下:
S2 | c | d | e |
---|---|---|---|
1 *** | 11**1111 | 11111111 |
此時(shí)因?yàn)閐是S1類型 S1看做一個(gè)整體,在S1中按4字節(jié)對(duì)齊 因此從4開始布局,e由于大于pack(4) 因此按4字節(jié)對(duì)齊排序味混,從12索引出開始布局产雹。因此總共20字節(jié) 也是4的整數(shù)倍。
pragma pack(8) 的時(shí)候: size of s1:8 -- size of s2:24
此時(shí)S1中的int類型小于8翁锡,因此按S1中最大類型int對(duì)齊
S2的內(nèi)存布局如下:
S2 | c | d | e |
---|---|---|---|
1 *** | 11**1111 | **** 11111111 |
此時(shí)S2中最大類型(double)的成員e正好等于8蔓挖,因此按8字節(jié)對(duì)齊,因此從索引16出開始布局馆衔,最終為24個(gè)字節(jié)
pragma pack(16) 的時(shí)候: size of s1:8 -- size of s2:24
此時(shí)S2中最大類型(double)成員e小于16时甚,因此按8字節(jié)對(duì)齊,因此情況和pragma pack(8) 的時(shí)候一樣哈踱。