MSVC 下 C/C++ 结构体内存对齐与填充规则
版权声明:原创文章,未经授权,请勿转载
MSVC 系列编译器下 C/C++
结构体成员在内存中的布局,主要受编译器的打包对齐规则影响。
而所谓的打包就是指将结构体成员按照特定的规则映射到内存中,这样可以使某些硬件架构能够更快地访问数据,并且结构体在存储时更紧凑,从而节省空间。
MSVC 系列的打包对齐主要由下面两个方面决定:
/Zp
编译器选项#pragma pack([n])
预处理器指令
两者的作用是一样的,都是控制结构体成员对齐的规则,而主要区别在于前者作用于在整个文件的编译期间,而后者书写在源代码中,影响后面的所有代码,并且后者可以覆盖前者。
/Zp 编译器选项
/Zp
是一个编译器选项,用于控制如何将结构成员打包到内存中,并在模块中为所有结构指定同一包装 [1] 。
1
将结构打包在 1 字节边界上。与/Zp
一样。2
在 2 字节边界上打包结构。4
在 4 字节边界上打包结构。8
在 8 字节边界上打包结构(x86、ARM 和 ARM64 的默认值)。16
在 16 字节边界上打包结构(x64 和 ARM64EC 的默认值)。
MSVC x86 体系下 默认的 /Zp
是 8
,所以结构体成员对齐到 8 字节边界上。
MSVC x64 体系下 默认的 /Zp
是 16
。
#pragma pack([n])
预编译指令
#pragma pack([n])
是一个预编译指令,用于设置结构体成员对齐的规则 [2]。
其中n为 1、2、4、8 或 16,作用同/Zp
。如果使用不带参数的#pragma pack
,结构成员将打包为 /Zp
指定的值。
对齐规则
- 结构成员按其声明顺序进行存储 [3]
- 第一个成员的内存地址最低,最后一个成员的内存地址最高。
- 每个成员在内存中的位置偏移量记作
offset
, 第一个成员的偏移量总是offset 0
。
- 结构成员的
offset
分配依赖于 对齐需求alignment-requirement
,而这个 对齐需求 需要满足如下公式:alignment-requirement = min(n, sizeof(item))
offset % alignment-requirement == 0
- 其中
n
是使用/Zp[n]
选项 或者#pragma pack(n)
杂注 表示的包装大小,而item
是结构成员。 默认包装大小为/Zp8
。 - 这种 对齐需求 也叫做 字节边界。
- 经过 对齐需求 分配
offset
后, 结构成员之间会出现 间隙,这个间隙也可以称为 填充。
参考链接
测试代码
// test.cpp
#include <stddef.h>
#include <stdio.h>
// MSVC x86 default alignment is 8 because of /Zp8
#pragma pack(show)
struct S {
int a; // offset 00, size 04, alignment 4
short b; // offset 04, size 02, alignment 2
double c; // offset 08, size 08, alignment 8
};
struct C {
char a; // offset 00, size 01, alignment 1
short b; // offset 02, size 02, alignment 2
double c; // offset 08, size 08, alignment 8
int d; // offset 16, size 04, alignment 4
char e; // offset 20, size 01, alignment 1
double f; // offset 24, size 08, alignment 8
};
#pragma pack(2) // set alignment to 2 overwrite /Zp8
#pragma pack(show)
struct T {
char a; // offset 00, size 01, alignment 1
int b; // offset 02, size 04, alignment 2
double c; // offset 06, size 08, alignment 2
short d; // offset 14, size 02, alignment 2
char e; // offset 16, size 01, alignment 1
int f; // offset 18, size 04, alignment 2
};
#pragma pack() // restore default alignment to 8
#pragma pack(show)
#ifndef max
# define max(a, b) (((a) > (b)) ? (a) : (b))
#endif
#ifndef min
# define min(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef offsetof
# define offsetof(s,m) ((size_t)&(((s*)0)->m))
#endif
#define memsize(s,m) (sizeof(((s*)0)->m))
#define memptrint(s,m,a) \
printf("%s::%s offset %02d, size %02d, alignment %d \n", \
#s, #m, offsetof(s, m), memsize(s,m), min(a, memsize(s,m))); \
#ifndef PACKALIGN
# define PACKALIGN 8
#endif
int main()
{
memptrint(S, a, PACKALIGN);
memptrint(S, b, PACKALIGN);
memptrint(S, c, PACKALIGN);
printf("\n");
memptrint(C, a, PACKALIGN);
memptrint(C, b, PACKALIGN);
memptrint(C, c, PACKALIGN);
memptrint(C, d, PACKALIGN);
memptrint(C, e, PACKALIGN);
memptrint(C, f, PACKALIGN);
printf("\n");
memptrint(T, a, 2);
memptrint(T, b, 2);
memptrint(T, c, 2);
memptrint(T, d, 2);
memptrint(T, e, 2);
memptrint(T, f, 2);
}
下面是MSVC x86平台下的编译结果及输出:
$ ..\x86\cl.exe /Zi /DDEBUG=1 test.cpp && test.exe
test.cpp
test.cpp(6): warning C4810: pragma pack(show) 的值 == 8
test.cpp(23): warning C4810: pragma pack(show) 的值 == 2
test.cpp(33): warning C4810: pragma pack(show) 的值 == 8
Microsoft (R) Incremental Linker Version 14.29.30153.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:test.exe
/debug
test.obj
S::a offset 00, size 04, alignment 4
S::b offset 04, size 02, alignment 2
S::c offset 08, size 08, alignment 8
C::a offset 00, size 01, alignment 1
C::b offset 02, size 02, alignment 2
C::c offset 08, size 08, alignment 8
C::d offset 16, size 04, alignment 4
C::e offset 20, size 01, alignment 1
C::f offset 24, size 08, alignment 8
T::a offset 00, size 01, alignment 1
T::b offset 02, size 04, alignment 2
T::c offset 06, size 08, alignment 2
T::d offset 14, size 02, alignment 2
T::e offset 16, size 01, alignment 1
T::f offset 18, size 04, alignment 2
指定 /Zp1
后,结构体成员对齐到1字节边界上:
$ ..\x86\cl.exe /Zi /Zp1 /DPACKALIGN=1 /DDEBUG=1 test.cpp && test.exe
test.cpp
test.cpp(6): warning C4810: pragma pack(show) 的值 == 1
test.cpp(23): warning C4810: pragma pack(show) 的值 == 2
test.cpp(33): warning C4810: pragma pack(show) 的值 == 1
Microsoft (R) Incremental Linker Version 14.29.30153.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:test.exe
/debug
test.obj
S::a offset 00, size 04, alignment 1
S::b offset 04, size 02, alignment 1
S::c offset 06, size 08, alignment 1
C::a offset 00, size 01, alignment 1
C::b offset 01, size 02, alignment 1
C::c offset 03, size 08, alignment 1
C::d offset 11, size 04, alignment 1
C::e offset 15, size 01, alignment 1
C::f offset 16, size 08, alignment 1
T::a offset 00, size 01, alignment 1
T::b offset 02, size 04, alignment 2
T::c offset 06, size 08, alignment 2
T::d offset 14, size 02, alignment 2
T::e offset 16, size 01, alignment 1
T::f offset 18, size 04, alignment 2
Comments ()