目录
1. 结构体内存对齐规则
- 第一个成员在与结构体变量偏移量为0的地址处。(即结构体的首地址处,即对齐到0处)
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
- 结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
对齐数 = 该结构体成员变量自身的大小与编译器默认的一个对齐数的较小值。
默认对齐值:
Linux 默认#pragma pack(4)
window 默认#pragma pack(8)
注:可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是指定的“对齐系数”。
注:VS中的默认对齐数为8,不是所有编译器都有默认对齐数,当编译器没有默认对齐数的时候,成员变量的大小就是该成员的对齐数。
2. 为什么存在内存对齐
- 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些平台只能在某些地址处取得某些特定类型的数据,否则抛出硬件异常。比如,当一个平台要取一个整型数据时只能在地址为4的倍数的位置取得,那么这时就需要内存对齐,否则无法访问到该整型数据。
- 性能原因: 数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因在于,为了访问未对齐内存,处理器需要作两次内存访问;而对齐的内存访问仅需一次。
内存这么重要,在进行内存对齐的时候怎么还有内存被白白浪费掉呢?其实结构体的内存对齐是拿空间来换取时间的做法。
3. 示例
class A
{
public:
virtual void func() {}
char a;
int b;
double c;
static int d;
};
解析:
64位机器上:
指针占8个字节,char 占1个字节,int占4个字节, double占8个字节
虚函数表指针从0开始偏移,占用8个字节,占用0-7 ,
接下来是char 占用1个字节,偏移位置8是对齐数(min(1, 8)) 的倍数,所以占有1个字节,占用8
接下来是int 占用4个字节,偏移位置9不是对齐数(min(4, 8))的倍数,所以从从12 开始偏移,占用4个字节 占用12-15 ,
接下来是double 占用8个字节,偏移位置16时对齐数(min(8,8))的倍数,所以从从12开始偏移,占用8个字节,占用16-23
所以从 0~23 一共24个字节,24为最大对齐数(max(1,4,8))的整数倍,故而,该结构体大小为24个字节。
4. 结构体某个成员相对于结构体起始位置的偏移量
使用offsetof宏来判断结构体中成员的偏移地址。使用offsetof宏需要包含stddef.h头文件,该宏定义如下:
#define offsetof(type,menber) (size_t)&(((type*)0)->member)