和很多IT新人一样,经常对结构体的 size 搞不太清楚,于是今天花了点儿时间仔细研究了一下,记录如下以便参考。
问题1:对如下程序其运行结果是什么?
int main()
{
typedef struct A{
bool b;
int i;
short s;
struct{
bool b;
double d;
}B;
char ca[5];
double d;
union{
char ca[3];
int i;
}U;
}A;
A a;
cout << "Sizeof(A) = " << sizeof(A) << " \t Addr: " << &a << endl;
cout << "Sizeof(A.b) = " << sizeof(a.b) << " \t Addr: " << &(a.b) << endl;
cout << "Sizeof(A.i) = " << sizeof(a.i) << " \t Addr: " << &(a.i) << endl;
cout << "Sizeof(A.s) = " << sizeof(a.s) << " \t Addr: " << &(a.s) << endl;
cout << "Sizeof(A.B) = " << sizeof(a.B) << " \t Addr: " << &(a.B) << endl;
cout << "Sizeof(A.ca) = " << sizeof(a.ca) << " \t Addr: " << &(a.ca) << endl;
cout << "Sizeof(A.d) = " << sizeof(a.d) << " \t Addr: " << &(a.d) << endl;
cout << "Sizeof(A.U) = " << sizeof(a.U) << " \t Addr: " << &(a.U) << endl;
return 0;
}
这里主要涉及到的就是字节对齐问题,先定义一个概念,结构体宽度 = 结构体的最宽 基本类型成员宽度。
字节对齐的细节由具体编译器决定,但一般都满足以下三个规则:
1. 结构体变量的首地址能够被其结构体宽度所整除;
2. 结构体的每个成员相对于结构体首地址的偏移量都是该成员宽度的整数倍,多余的空间被填充;
3. 结构体的总大小为结构体宽度的整数倍,不够的空间将用填充位补齐。
从理论上来说,将结构体的成员一个接一个地放也是可以的,不过将数据存放进行对齐主要是基于CPU在存取效率方面的考虑。
对于第2条的理解可以从本文的两个实例入手,如果该成员是基本数据类型,那么这里的宽度就指其sizeof()返回值,如果该成员也是一个结构体,那么这里的宽度就指其结构体宽度。
以下是问题1的程序在VS2010上编译后Win32系统下运行的结果:
由此可分析Struct A 的内存映像:
问题2:对结构体A内的结构体B,其相对于结构体A首地址的偏移量是sizeof(B)的整数倍吗?
int main()
{
typedef struct A{
bool b;
int i;
short s;
struct{
bool b;
int d;
}B;
char ca[5];
double d;
union{
char ca[3];
int i;
}U;
}A;
A a;
cout << "Sizeof(A) = " << sizeof(A) << " \t Addr: " << &a << endl;
cout << "Sizeof(A.b) = " << sizeof(a.b) << " \t Addr: " << &(a.b) << endl;
cout << "Sizeof(A.i) = " << sizeof(a.i) << " \t Addr: " << &(a.i) << endl;
cout << "Sizeof(A.s) = " << sizeof(a.s) << " \t Addr: " << &(a.s) << endl;
cout << "Sizeof(A.B) = " << sizeof(a.B) << " \t Addr: " << &(a.B) << endl;
cout << "Sizeof(A.ca) = " << sizeof(a.ca) << " \t Addr: " << &(a.ca) << endl;
cout << "Sizeof(A.d) = " << sizeof(a.d) << " \t Addr: " << &(a.d) << endl;
cout << "Sizeof(A.U) = " << sizeof(a.U) << " \t Addr: " << &(a.U) << endl;
return 0;
}
相对于问题1,问题2的程序只是将结构体B第2个成员的类型改成了int,则运行后可以看到如下输出:
其内存映像亦可分析出来:
由此可见,对于第2条规则并不是以成员的大小计算偏移地址,而是以成员的宽度来计算的。此外,对于同样的一些成员,不同的排列顺序也会导致结构体 size 的不同,即编译器是根据成员出现的次序来分配空间的。