MSVC 下 C/C++ 结构体内存对齐与填充规则

版权声明:原创文章,未经授权,请勿转载

MSVC 系列编译器下 C/C++ 结构体成员在内存中的布局,主要受编译器的打包对齐规则影响。

而所谓的打包就是指将结构体成员按照特定的规则映射到内存中,这样可以使某些硬件架构能够更快地访问数据,并且结构体在存储时更紧凑,从而节省空间。

MSVC 系列的打包对齐主要由下面两个方面决定:

  1. /Zp 编译器选项
  2. #pragma pack([n]) 预处理器指令

两者的作用是一样的,都是控制结构体成员对齐的规则,而主要区别在于前者作用于在整个文件的编译期间,而后者书写在源代码中,影响后面的所有代码,并且后者可以覆盖前者。

/Zp 编译器选项

/Zp 是一个编译器选项,用于控制如何将结构成员打包到内存中,并在模块中为所有结构指定同一包装 [1]

  • 1 将结构打包在 1 字节边界上。与 /Zp 一样。
  • 2 在 2 字节边界上打包结构。
  • 4 在 4 字节边界上打包结构。
  • 8 在 8 字节边界上打包结构(x86、ARM 和 ARM64 的默认值)。
  • 16 在 16 字节边界上打包结构(x64 和 ARM64EC 的默认值)。

MSVC x86 体系下 默认的 /Zp8,所以结构体成员对齐到 8 字节边界上。

MSVC x64 体系下 默认的 /Zp16

#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