轉(zhuǎn)載 結(jié)構(gòu)體對齊詳解
結(jié)構(gòu)體數(shù)據(jù)成員對齊的意義
許多實際的計算機系統(tǒng)對基本類型數(shù)據(jù)在內(nèi)存中存放的位置有限制吃媒,它們會要求這些數(shù)據(jù)的起始地址的值是某個數(shù)k的倍數(shù)绅项,這就是所謂的內(nèi)存對齊还栓,而這個k則被稱為該數(shù)據(jù)類型的對齊模數(shù)(alignment modulus)对嚼。這種強制的要求一來簡化了處理器與內(nèi)存之間傳輸系統(tǒng)的設(shè)計敲霍,二來可以提升讀取數(shù)據(jù)的速度。
比如這么一種處理器鹅颊,它每次讀寫內(nèi)存的時候都從某個8倍數(shù)的地址開始欣除,一次讀出或?qū)懭?個字節(jié)的數(shù)據(jù),假如軟件能保證double類型的數(shù)據(jù)都從8倍數(shù)地址開始挪略,那么讀或?qū)懸粋€double類型數(shù)據(jù)就只需要一次內(nèi)存操作。否則滔岳,我們就可能需要
兩次內(nèi)存操作才能完成這個動作杠娱,因為數(shù)據(jù)或許恰好橫跨在兩個符合對齊要求的8字節(jié)內(nèi)存塊上。
結(jié)構(gòu)體對齊包括兩個方面的含義
- 結(jié)構(gòu)體總長度谱煤;
- 結(jié)構(gòu)體內(nèi)各數(shù)據(jù)成員的內(nèi)存對齊摊求,即該數(shù)據(jù)成員相對結(jié)構(gòu)體的起始位置;
結(jié)構(gòu)體大小的計算方法和步驟
- 將結(jié)構(gòu)體內(nèi)所有數(shù)據(jù)成員的長度值相加刘离,記為sum_a室叉;
- 將各數(shù)據(jù)成員為了內(nèi)存對齊,按各自對齊模數(shù)而填充的字節(jié)數(shù)累加到和sum_a上硫惕,記為sum_b茧痕。
對齊模數(shù)是#pragma pack
指定的數(shù)值以及該數(shù)據(jù)成員自身長度中數(shù)值較小者。該數(shù)據(jù)相對起始位置應(yīng)該是對齊模式的整數(shù)倍; - 將和sum_b向結(jié)構(gòu)體模數(shù)對齊恼除,該模數(shù)是
#pragma pack指定的數(shù)值
踪旷、未指定#pragma pack時,系統(tǒng)默認的對齊模數(shù)(32位系統(tǒng)為4字節(jié)豁辉,64位為8字節(jié))
和結(jié)構(gòu)體內(nèi)部最大的基本數(shù)據(jù)類型成員
長度中數(shù)值較小者令野。結(jié)構(gòu)體的長度應(yīng)該是該模數(shù)的整數(shù)倍。
結(jié)構(gòu)體大小計算舉例
在計算之前徽级,我們首先需要明確的是各個數(shù)據(jù)成員的對齊模數(shù)气破,對齊模數(shù)和數(shù)據(jù)成員本身的長度以及pragma pack編譯參數(shù)有關(guān),其值是二者中最小數(shù)餐抢。如果程序沒有明確指出现使,就需要知道編譯器默認的對齊模數(shù)值低匙。下表是Windows XP/DEV-C++和Linux/GCC中基本數(shù)據(jù)類型的長度和默認對齊模數(shù)。
char | short | int | long | float | double | long long | long double | |
---|---|---|---|---|---|---|---|---|
Win-32長度 | 1 | 2 | 4 | 4 | 4 | 8 | 8 | 8 |
模數(shù) | 1 | 2 | 4 | 4 | 4 | 8 | 8 | 8 |
Linux-32長度 | 1 | 2 | 4 | 4 | 4 | 8 | 8 | 12 |
模數(shù) | 1 | 2 | 4 | 4 | 4 | 4 | 4 | 4 |
Linux-64長度 | 1 | 2 | 4 | 8 | 4 | 8 | 8 | 16 |
模數(shù) | 1 | 2 | 4 | 8 | 4 | 8 | 8 | 16 |
例子1:
struct my_struct
{
char a;
long double b;
};
此例子Windows和Linux計算方法有些許不一致朴下。
在Windows中計算步驟如下:
- 所有數(shù)據(jù)成員自身長度和:1B + 8B = 9B --> sum_a = 9B
- 數(shù)據(jù)成員a放在相對偏移0處努咐,之前不需要填充字節(jié);數(shù)據(jù)成員b為了內(nèi)存對齊殴胧,根據(jù)“結(jié)構(gòu)體大小的計算方法和步驟”中第二條原則渗稍,其對齊模數(shù)是8,之前需填充7個字節(jié)团滥,sum_a + 7 = 16B --> sum_b = 16 B
- 按照定義竿屹,結(jié)構(gòu)體對齊模數(shù)是結(jié)構(gòu)體內(nèi)部最大數(shù)據(jù)成員長度和pragma pack中較小者,前者為8后者為4灸姊,所以結(jié)構(gòu)體對齊模數(shù)是4拱燃。sum_b是4的4倍,不需再次對齊力惯。
綜上3步碗誉,可知結(jié)構(gòu)體的長度是16B,各數(shù)據(jù)成員在內(nèi)存中的分布如圖1-1所示父晶。
在Linux中計算步驟如下:
- 所有數(shù)據(jù)成員自身長度和:1B + 12B = 13B --> sum_a = 13B
- 數(shù)據(jù)成員a放在相對偏移0處哮缺,之前不需要填充字節(jié);數(shù)據(jù)成員b為了內(nèi)存對齊甲喝,根據(jù)“結(jié)構(gòu)體大小的計算方法和步驟”中第二條原則尝苇,其對齊模數(shù)是4,之前需填充3個字節(jié)埠胖,sum_a + 3 = 16B --> sum_b = 16 B
- 按照定義糠溜,結(jié)構(gòu)體對齊模數(shù)是結(jié)構(gòu)體內(nèi)部最大數(shù)據(jù)成員長度和pragma pack中較小者,前者為12后者為4直撤,所以結(jié)構(gòu)體對齊模數(shù)是4非竿。sum_b是4的4倍,不需再次對齊谋竖。
綜上3步汽馋,可知結(jié)構(gòu)體的長度是16B,各數(shù)據(jù)成員在內(nèi)存中的分布如圖1-2所示圈盔。
例子2:
#pragma pack(2)
struct my_struct
{
char a;
long double b;
};
#pragma pack()
例子1和例子2不同之處在于例子2中使用了
#pragma pack(2)
編譯參數(shù)豹芯,它強制指定對齊模數(shù)是2。此例子Windows和Linux計算方法有些許不一致驱敲。
在Windows中計算步驟如下:
- 所有數(shù)據(jù)成員自身長度和:1B + 8B = 13B --> sum_a = 9B
- 數(shù)據(jù)成員a放在相對偏移0處铁蹈,之前不需要填充字節(jié);數(shù)據(jù)成員b為了內(nèi)存對齊众眨,根據(jù)“結(jié)構(gòu)體大小的計算方法和步驟”中第二條原則握牧,其對齊模數(shù)是2容诬,之前需填充1個字節(jié),sum_a + 1 = 10B --> sum_b = 10 B
- 按照定義沿腰,結(jié)構(gòu)體對齊模數(shù)是結(jié)構(gòu)體內(nèi)部最大數(shù)據(jù)成員長度和pragma pack中較小者览徒,前者為8后者為2,所以結(jié)構(gòu)體對齊模數(shù)是2颂龙。sum_b是2的5倍习蓬,不需再次對齊。
綜上3步措嵌,可知結(jié)構(gòu)體的長度是10B躲叼,各數(shù)據(jù)成員在內(nèi)存中的分布如圖2-1所示。
在Linux中計算步驟如下:
- 所有數(shù)據(jù)成員自身長度和:1B + 12B = 13B --> sum_a = 13B
- 數(shù)據(jù)成員a放在相對偏移0處企巢,之前不需要填充字節(jié)枫慷;數(shù)據(jù)成員b為了內(nèi)存對齊,根據(jù)“結(jié)構(gòu)體大小的計算方法和步驟”中第二條原則浪规,其對齊模數(shù)是2或听,之前需填充1個字節(jié),sum_a + 1 = 14B --> sum_b = 14 B
- 按照定義笋婿,結(jié)構(gòu)體對齊模數(shù)是結(jié)構(gòu)體內(nèi)部最大數(shù)據(jù)成員長度和pragma pack中較小者誉裆,前者為8后者為2,所以結(jié)構(gòu)體對齊模數(shù)是2萌抵。sum_b是2的7倍,不需再次對齊元镀。
綜上3步绍填,可知結(jié)構(gòu)體的長度是14B,各數(shù)據(jù)成員在內(nèi)存中的分布如圖2-2所示栖疑。
例子3:
struct my_struct
{
char a;
double b;
char c;
};
前兩例中讨永,數(shù)據(jù)成員在Linux和Windows下都相同,例3中double的對齊模數(shù)在Linux中是4遇革,在Windows下是8卿闹,針對這種模數(shù)不相同的情況加以分析。
在Windows中計算步驟如下:
- 所有數(shù)據(jù)成員自身長度和:1B + 8B + 1B = 10B --> sum_a = 10B
- 數(shù)據(jù)成員a放在相對偏移0處萝快,之前不需要填充字節(jié)锻霎;數(shù)據(jù)成員b為了內(nèi)存對齊,根據(jù)“結(jié)構(gòu)體大小的計算方法和步驟”中第二條原則揪漩,其對齊模數(shù)是8旋恼,之前需填充7個字節(jié),sum_a + 7 = 17B --> sum_b = 17B
- 按照定義奄容,結(jié)構(gòu)體對齊模數(shù)是結(jié)構(gòu)體內(nèi)部最大數(shù)據(jù)成員長度和pragma pack中較小者冰更,前者為8后者為8产徊,所以結(jié)構(gòu)體對齊模數(shù)是8。sum_b應(yīng)該是8的整數(shù)倍蜀细,所以要在結(jié)構(gòu)體后填充8*3 - 17 = 7個字節(jié)舟铜。
綜上3步,可知結(jié)構(gòu)體的長度是24B奠衔,各數(shù)據(jù)成員在內(nèi)存中的分布如圖3-1所示谆刨。
在Linux中計算步驟如下:
- 所有數(shù)據(jù)成員自身長度和:1B + 8B + 1B = 10B,sum_a = 10B
- 數(shù)據(jù)成員a放在相對偏移0處涣觉,之前不需要填充字節(jié)痴荐;數(shù)據(jù)成員b為了內(nèi)存對齊,根據(jù)“結(jié)構(gòu)體大小的計算方法和步驟”中第二條原則官册,其對齊模數(shù)是4生兆,之前需填充3個字節(jié),sum_b = sum_a + 3 = 13B
- 按照定義膝宁,結(jié)構(gòu)體對齊模數(shù)是結(jié)構(gòu)體內(nèi)部最大數(shù)據(jù)成員長度和pragma
pack中較小者鸦难,前者為8后者為4,所以結(jié)構(gòu)體對齊模數(shù)是4员淫。sum_b應(yīng)該是4的整數(shù)倍合蔽,所以要在結(jié)構(gòu)體后填充4*4 - 13 = 3個字節(jié)。
綜上3步介返,可知結(jié)構(gòu)體的長度是16B拴事,各數(shù)據(jù)成員在內(nèi)存中的分布如圖3-2所示。
例子4:
struct my_struct
{
char a[11];
int b;
char c;
};
此例子Windows和Linux計算方法一樣圣蝎,如下:
- 所有數(shù)據(jù)成員自身長度和:11B + 4B + 1B = 16B --> sum_a = 16B
- 數(shù)據(jù)成員a放在相對偏移0處刃宵,之前不需要填充字節(jié);數(shù)據(jù)成員b為了內(nèi)存對齊徘公,根據(jù)“結(jié)構(gòu)體大小的計算方法和步驟”中第二條原則牲证,其對齊模數(shù)是4,之前需填充3個字節(jié)关面,sum_a + 1 = 17B --> sum_b = 17B
- 按照定義坦袍,結(jié)構(gòu)體對齊模數(shù)是結(jié)構(gòu)體內(nèi)部最大數(shù)據(jù)成員長度和pragma pack中較小者,前者為4后者為4等太,所以結(jié)構(gòu)體對齊模數(shù)是4捂齐。sum_b是4的整數(shù)倍,需在結(jié)構(gòu)體后填充4*5 - 17 = 1個字節(jié)缩抡。
綜上3步辛燥,可知結(jié)構(gòu)體的長度是20B,各數(shù)據(jù)成員在內(nèi)存中的分布如圖4所示。
例子5:
struct my_test
{
int my_test_a;
char my_test_b;
};
struct my_struct
{
struct my_test a;
double my_struct_a;
int my_struct_b;
char my_struct_c;
};
例子5和前幾個例子均不同挎塌,在此例子中我們要計算struct my_struct的大小徘六,而my_struct中嵌套了一個my_test結(jié)構(gòu)體。這種結(jié)構(gòu)體應(yīng)該如何計算呢榴都?原則是將my_test在my_struct中先展開待锈,然后再計算,即是展開成如下結(jié)構(gòu)體:
struct my_struct
{
int my_test_a;
char my_test_b;
double my_struct_a;
int my_struct_b;
char my_struct_c;
};
此例子Windows中的計算方法如下:
- 所有數(shù)據(jù)成員自身長度和:4B + 1B + 8B + 4B + 1B= 18B --> sum_a = 18B
- 數(shù)據(jù)成員my_struct_a為了內(nèi)存對齊嘴高,根據(jù)“結(jié)構(gòu)體大小的計算方法和步驟”中第二條原則竿音,其對齊模數(shù)是8,之前需填充3個字節(jié):sum_a + 3 = 21B --> sum_b = 21B
- 按照定義拴驮,結(jié)構(gòu)體對齊模數(shù)是結(jié)構(gòu)體內(nèi)部最大數(shù)據(jù)成員長度和pragma pack中較小者春瞬,前者為8后者為8,所以結(jié)構(gòu)體對齊模數(shù)是8套啤。sum_b是8的整數(shù)倍宽气,需在結(jié)構(gòu)體后填充3*8 - 21 = 3個字節(jié)。
綜上3步潜沦,可知結(jié)構(gòu)體的長度是24B萄涯,各數(shù)據(jù)成員在內(nèi)存中的分布如圖5所示。
此例子Linux中的計算方法如下:
- 所有數(shù)據(jù)成員自身長度和:4B + 1B + 8B + 4B + 1B= 18B唆鸡,sum_a = 18B
- 數(shù)據(jù)成員my_struct_a為了內(nèi)存對齊涝影,根據(jù)“結(jié)構(gòu)體大小的計算方法和步驟”中第二條原則,其對齊模數(shù)是4争占,之前需填充3個字節(jié)燃逻,sum_b = sum_a + 3 = 21B
- 按照定義,結(jié)構(gòu)體對齊模數(shù)是結(jié)構(gòu)體內(nèi)部最大數(shù)據(jù)成員長度和pragma
pack中較小者臂痕,前者為4后者為4伯襟,所以結(jié)構(gòu)體對齊模數(shù)是4。sum_b是4的整數(shù)倍刻蟹,需在結(jié)構(gòu)體后填充6*4 - 21 = 3個字節(jié)逗旁。
綜上3步嘿辟,可知結(jié)構(gòu)體的長度是24B舆瘪,各數(shù)據(jù)成員在內(nèi)存中的分布如圖5所示。
源代碼附錄
上面的例子均在Windows(VC++6.0)和Linux(GCC4.1.0)上測試驗證红伦。下面是測試程序英古。
#include <iostream>
#include <stdio.h>
using namespace std;
int main()
{
cout << "sizeof(char) = " << sizeof(char) << endl;
cout << "sizeof(short) = " << sizeof(short) << endl;
cout << "sizeof(int) = " << sizeof(int) << endl;
cout << "sizeof(long) = " << sizeof(long) << endl;
cout << "sizeof(float) = " << sizeof(float) << endl;
cout << "sizeof(double) = " << sizeof(double) << endl;
cout << "sizeof(long long) = " << sizeof(long long) << endl;
cout << "sizeof(long double) = " << sizeof(long double) << endl << endl;
// 例子1
{
struct my_struct
{
char a;
long double b;
};
cout << "exapmle-1: sizeof(my_struct) = " << sizeof(my_struct) << endl;
struct my_struct data;
printf("my_struct->a: %u\nmy_struct->b: %u\n\n", &data.a, &data.b);
}
// 例子2
{
#pragma pack(2)
struct my_struct
{
char a;
long double b;
};
#pragma pack()
struct my_struct data;
cout << "exapmle-2: sizeof(my_struct) = " << sizeof(my_struct) << endl;
printf("my_struct->a: %u\nmy_struct->b: %u\n\n", &data.a, &data.b);
}
// 例子3
{
struct my_struct
{
char a;
double b;
char c;
};
struct my_struct data;
cout << "exapmle-3: sizeof(my_struct) = " << sizeof(my_struct) << endl;
printf("my_struct->a: %u\nmy_struct->b: %u\nmy_struct->c: %u\n\n", &data.a, &data.b, &data.c);
}
// 例子4
{
struct my_struct
{
char a[11];
int b;
char c;
};
cout << "example-4: sizeof(my_struct) = " << sizeof(struct my_struct) << endl;
struct my_struct data;
printf("my_struct->a: %u\nmy_struct->b: %u\nmy_struct->c: %u\n\n", &data, &data.b, &data.c);
}
// 例子5
{
struct my_test
{
int my_test_a;
char my_test_b;
};
struct my_struct
{
struct my_test a;
double my_struct_a;
int my_struct_b;
char my_struct_c;
};
cout << "example-5: sizeof(my_struct) = " << sizeof(struct my_struct) << endl;
struct my_struct data;
printf("my_struct->my_test_a : %u\n"
"my_struct->my_test_b : %u\n"
"my_struct->my_struct_a: %u\n"
"my_struct->my_struct_b: %u\n"
"my_struct->my_struct_c: %u\n", &data.a.my_test_a, &data.a.my_test_b,
&data.my_struct_a, &data.my_struct_b, &data.my_struct_c);
}
return 0;
}
執(zhí)行結(jié)果
//Linux localhost 3.4.6-2.10-desktop #1 SMP PREEMPT Thu Jul 28 19:20:26 UTC 2012 (641c197) x86_64 x86_64 x86_64 GNU/Linux
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4
sizeof(long) = 8
sizeof(float) = 4
sizeof(double) = 8
sizeof(long long) = 8
sizeof(long double) = 16
exapmle-1: sizeof(my_struct) = 32
my_struct->a: 2163695552
my_struct->b: 2163695568
exapmle-2: sizeof(my_struct) = 18
my_struct->a: 2163695680
my_struct->b: 2163695682
exapmle-3: sizeof(my_struct) = 24
my_struct->a: 2163695648
my_struct->b: 2163695656
my_struct->c: 2163695664
example-4: sizeof(my_struct) = 20
my_struct->a: 2163695616
my_struct->b: 2163695628
my_struct->c: 2163695632
example-5: sizeof(my_struct) = 24
my_struct->my_test_a : 2163695584
my_struct->my_test_b : 2163695588
my_struct->my_struct_a: 2163695592
my_struct->my_struct_b: 2163695600
my_struct->my_struct_c: 2163695604
//Linux localhost 3.4.6-2.10-desktop #1 SMP PREEMPT Thu Jul 26 09:36:26 UTC 2012 (641c197) i686 i686 i386 GNU/Linux
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4
sizeof(long) = 4
sizeof(float) = 4
sizeof(double) = 8
sizeof(long long) = 8
sizeof(long double) = 12
exapmle-1: sizeof(my_struct) = 16
my_struct->a: 3213889904
my_struct->b: 3213889908
exapmle-2: sizeof(my_struct) = 14
my_struct->a: 3213889890
my_struct->b: 3213889892
exapmle-3: sizeof(my_struct) = 16
my_struct->a: 3213889872
my_struct->b: 3213889876
my_struct->c: 3213889884
example-4: sizeof(my_struct) = 20
my_struct->a: 3213889852
my_struct->b: 3213889864
my_struct->c: 3213889868
example-5: sizeof(my_struct) = 24
my_struct->my_test_a : 3213889828
my_struct->my_test_b : 3213889832
my_struct->my_struct_a: 3213889836
my_struct->my_struct_b: 3213889844
my_struct->my_struct_c: 3213889848
//CYGWIN_NT-6.1 motadou-PC 1.7.20(0.266/5/3) 2013-06-07 11:11 i686 Cygwin
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4
sizeof(long) = 4
sizeof(float) = 4
sizeof(double) = 8
sizeof(long long) = 8
sizeof(long double) = 12
exapmle-1: sizeof(my_struct) = 16
my_struct->a: 2272336
my_struct->b: 2272340
exapmle-2: sizeof(my_struct) = 14
my_struct->a: 2272322
my_struct->b: 2272324
exapmle-3: sizeof(my_struct) = 24
my_struct->a: 2272296
my_struct->b: 2272304
my_struct->c: 2272312
example-4: sizeof(my_struct) = 20
my_struct->a: 2272276
my_struct->b: 2272288
my_struct->c: 2272292
example-5: sizeof(my_struct) = 24
my_struct->my_test_a : 2272248
my_struct->my_test_b : 2272252
my_struct->my_struct_a: 2272256
my_struct->my_struct_b: 2272264
my_struct->my_struct_c: 2272268