有关内存字节对齐问题的总结
以前也折腾过这个问题,不过不如这次来的更深刻了。
最早的资料来源是陈正冲·石虎的《C语言深度剖析》,但是看过之后还是有些模糊。xczhang的文章用了具体的例子来分析:
http://www.cppblog.com/xczhang/archive/2007/12/23/39393.html
他的原话是:
“事实上,很多人对#pragma pack的理解是错误的。#pragma pack规定的对齐长度,实际使用的规则是:
结构,联合,或者类的数据成员,第一个放在偏移为0的地方,以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
也就是说,当#pragma pack的值等于或超过所有数据成员长度的时候,这个值的大小将不产生任何效果。
而结构整体的对齐,则按照结构体中最大的数据成员 和 #pragma pack指定值 之间,较小的那个进行。”
这里提到了3个对比的优先级:#pragma pack(n)的参数n、结构体最大成员的长度、每个成员在“摆放”位置时自己的自身的长度。
还是用一个例子来说明吧:
struct TestStruct { char a; long long b; short c; //int d; //char d; short d; //用一个默认构造函数进行初始化,便于在“内存”面板查看内存块使用情况 TestStruct::TestStruct() : a('a'), b(10L), c(2), d(3){} }; int normal_size = sizeof(TestStruct); printf("sizeof(TestStruct4) result is : %d\n", normal_size);
成员a、b、c在内存中的排列情况是:
61 cc cc cc cc cc cc cc //成员a(char):a???????
0a 00 00 00 00 00 00 00 //成员b(long long):10
02 00 //成员c(short):2
解释:整个结构体最大成员为b,long long长度为8。首先放成员a:a为char型长度为1,放入[0]号位置。
成员b长度为8,其首元素位置下标必须为8的倍数,取最小值,故将b从[8]开始摆放。
成员c长度为2,首元素位置下标必须为2的倍数,此时最小下标为[16],放入。
注意TestStruct最后一个成员d,当d的类型为char时,第三行排布情况为(假设d初始化为字符'd'):
02 00 64 cc cc cc cc cc
解释:d的自身长度为1,其下标必须为1的倍数,故将其放在c的后面(下标[18]),中间不留空位,剩余部分由“空位(cc)”填充。
当d类型为short时,第三行排布情况为(假设d值为3):
02 00 03 00 cc cc cc cc
解释:d自身长度为2,满足2的倍数条件的最小下标为[18]
当d类型为int时,第三行排布情况为(假设d值为3):
02 00 cc cc 03 00 00 00
解释:d自身长度为4,满足2的倍数条件的最小下标为[20]。故c、d之间补两个空位(cc)
不加pragma pack(n),三种情况结构体大小都一样,为24。
如果加了呢?这里以 pragma pack(2),即我们以2作为对齐长度
解释:整个结构体最大成员为b,long long长度为8。首先放成员a:a为char型长度为1,放入[0]号位置。
成员b长度为8,其首元素位置下标必须为8的倍数,取最小值,故将b从[8]开始摆放。
成员c长度为2,首元素位置下标必须为2的倍数,此时最小下标为[16],放入。
注意TestStruct最后一个成员d,当d的类型为char时,第三行排布情况为(假设d初始化为字符'd'):
02 00 64 cc cc cc cc cc
解释:d的自身长度为1,其下标必须为1的倍数,故将其放在c的后面(下标[18]),中间不留空位,剩余部分由“空位(cc)”填充。
当d类型为short时,第三行排布情况为(假设d值为3):
02 00 03 00 cc cc cc cc
解释:d自身长度为2,满足2的倍数条件的最小下标为[18]
当d类型为int时,第三行排布情况为(假设d值为3):
02 00 cc cc 03 00 00 00
解释:d自身长度为4,满足2的倍数条件的最小下标为[20]。故c、d之间补两个空位(cc)
不加pragma pack(n),三种情况结构体大小都一样,为24。
如果加了呢?这里以 pragma pack(2),即我们以2作为对齐长:
#pragma pack(2) struct TestStruct { char a; long long b; short c; //int d; //char d; short d; //用一个默认构造函数进行初始化,便于在“内存”面板查看内存块使用情况
TestStruct::TestStruct() : a('a'), b(10L), c(2), d(3){} }; #pragma pack() int normal_size = sizeof(TestStruct); printf("sizeof(TestStruct4) result is : %d\n", normal_size);
成员a、b、c在内存中的排列情况是:
61 cc //成员a(char):a?
0a 00
00 00
00 00
00 00 //成员b(long long):10
02 00 //成员c(short):2
解释:整个结构体最大成员为b,long long长度为8。由于加了#pragma pack(2),2比8要小,整个结构体以2作为对齐长度。
首先放成员a:a为char型长度为1,放入[0]号位置。
成员b长度为8,其首元素位置下标必须为2的倍数,取最小值,故将b从[2]开始摆放。
成员c长度为2,首元素位置下标必须为2的倍数,此时最小下标为[10],放入。
注意TestStruct最后一个成员d,当d的类型为char时,第三行排布情况为(假设d初始化为字符'd'):
02 00
64 cc
解释:d的自身长度为1,其下标必须为1的倍数,故将其放在c的后面(下标[12]),中间不留空位,最后一位由“空位(cc)”填充。
此时结构体大小为14 = 2+8+2+2
当d类型为short时,第三行排布情况为(假设d值为3):
02 00
03 00
解释:d自身长度为2,满足2的倍数条件的最小下标为[12],此时结构体大小为14 = 2+8+2+2
当d类型为int时,第三行排布情况为(假设d值为3):
02 00
03 00
00 00
解释:d自身长度为4,满足2的倍数条件的最小下标为[12]。int内存块视作拆成两个short
此时结构体大小为:16 = 2+8+2+4
#pragma pack(4)和#pragma pack(8)同理,这里不做赘述