关于内存对齐

来源:百度文库 编辑:神马文学网 时间:2024/04/29 10:11:21
老朋友建议我了解一下结构体内存对齐的事儿,我就去了解了一番:
以下实验平台为 VC9。
这里先引出几条VC下结构体对齐的原则:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员自身大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。
第一条与第三条针对结构体视为一个整体对像来说的,第二条是针对结构体成员来说的。
至于为何第一条与第三条都要去考虑“最长类型”,是因为如果声明为结构体的数组的话,那么对于该数组的每一个元素的成员的访问都必须“对齐”,这也就是为何必须要考虑“最长类型对齐”的原因。
这里还有一个问题,
#pragma pack(push)
#pragma pack(n)
.....
#pragma pack(pop)
这些可以改变对齐状态,同样,在编译命令行里面设置 /Zpn 也可以改变对齐的状态。
如果这里的 n 与结构体内的“最长类型”不一致怎么办?
取最小的对齐,若n较小,则“最长类型”失效,以n为准。
所以,上三条原则听是针对默认的情况来说的,default情况下n为多少呢?肯定大于sizeof(double)。
还有一个问题,如果结构体内部有数组,怎么算?比如说 short fuck[5]; 数组将被视为连续存在的基本类型,而不被当成一个整体,比如说 fuck 将被视为 5 个 short ,而不是一个 short[5]。
记得微软有本书上说,“聪明”的VC会调整成员的顺序,使其整个结构体的大小最小,比如说:
struct fuck
{
char  cf;
int   if;
short sf;
};
 
大小将是 12 ,但若将 cf 调至最末一个成员,则大小将缩为 8。
但我今天拿 VC9 试了一把,好像满不是那么一回事儿!看来还真是“百见不如一闻”啊……
所以说,程序员在自己写STRUCT的时候,一定要注意成员的顺序问题。
下面我查了GCC的原则:
在GCC中,对齐模数的准则是:对齐模数最大只能是 4,也就是说,即使结构体中有double类型,对齐模数还是4,所以对齐模数只能是1,2,4。而且在上述的三条中,第2条里,offset必须是成员大小的整数倍,如果这个成员大小小于等于4则按照上述准则进行,但是如果大于4了,则结构体每个成员相对于结构体首地址的偏移量(offset)只能按照是4的整数倍来进行判断是否添加填充。
我对GCC不了解,还请对此了解的行家指正。
还有,C++还有一个类继承的问题,就是父子类的内存对齐怎么办呢?
玄机逸士前辈的这篇博文里有说明。直接看结论就好了。
前辈有两个结论,
其一、C++ 语言保证“出现在 derived class 中的 base class suboject 有其完整性”;
其二、derived class 的对齐数 = min( 指定的全局对齐数,max( base class 的对齐数,derived class 的对齐数 ))
第一个结论没有问题。
第二个结论,需要修正一下,考虑如下情况:
#define NA 2
#define NB 1
#pragma pack(push)
#pragma pack(NA)
class A
{
public:
int i;
char c;
};
#pragma pack(push)
#pragma pack(NB)
class B : public A
{
public:
int i;
char c;
};
#pragma pack(pop)
#pragma pack(pop)
void main()
{
printf ( "%d\n", sizeof(A));
printf ( "%d\n", sizeof(B));
system("pause");
}
 
输出是 6 和 11,若把NA和NB互换,则输出为 5 和 12。
这里面 6 和 5 是没问题的,关系在于派生类 B 的对齐怎么回事儿。
有人说 12 好理解,因为是 NB = 2 的整数倍,但问题在这儿,其实 B 在满足结论一的情况下的有效长度正是 6 + 5 = 11,它补了一位。
我的理解是,前辈的结论二可能是想证明父子类的对齐方式是相关的,而事实上,该例证明了,二者不相关,各按各的对齐方式来。
所以我将前辈的结论二拆成两个式子:
1.派生类的对齐数 = Sum (所有父类的对齐数) + 自身的对齐数;
2.类的对齐数  = Min (指定对齐数, 类成员最长字节数);
讨论完毕。