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 ()