细说C++中字节的对齐方式

本文介绍了编译器默认字节对齐方式和自定义字节对齐方式。默认方式下,编译器会根据成员类型插入空字节以实现对齐。自定义可使用伪指令#pragma pack,成员按类型长度和指定对齐字节数中较小的一个对齐,还对比了#pragma pack(n)与#pragma pack (push,n)的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自https://blog.youkuaiyun.com/yanhang1589/article/details/46008583


C++中字节的对齐方式可以采用编译器默认的字节对齐方式, 也可以采用自定义的对齐方式。



编译器默认字节对齐方式



1、结构体中每个成员分别按自己的方式对齐(注:对于数组如int a[3], 其对齐字节为sizeof(int))

例1:
  1. struct test1
  2. {
  3. char c1; //c1长度1个字节, 采用1字节对齐
  4. short s; // s长度为2个字节,采用2字节对齐
  5. int i; // i长度为4个字节,采用4字节对齐
  6. char c2; //c2长度1个字节,采用1字节对齐
  7. };

sizeof(test1)的结果为12。

test1 的内存分布如下(以1表示占用字节,*表示空字节)
c1sic2
1*1111111***


原因如下:

  • 成员c1,其偏移地址为0,占据了第1个字节。此时共分配了1个字节。
  • 成员s为short类型,占用2个字节,采用2字节对齐。已经分配的1个字节不是2的整数倍,所以编译器在c1与s之间插入1个空字节,然后为s分配2个字节。此时共分配了4个字节。
  • 成员i为int类型,占用4个字节,采用4字节对齐。已经分配的4个字节是4的整数倍,所以编译器在i与s之间不插入空字节,然后直接为i分配4个字节。此时共分配了8个字节。
  • 成员c2为char类型,占用1个字节,采用1字节对齐。已经分配的8个字节是1的整数倍,所以编译器在c2与i之间不插入空字节,然后直接为c2分配1个字节。此时共分配了9个字节。
  • 此时所有的成员已经分配完了,但test1是采用4字节对齐的(请查看第2点),9不是4的整数倍(请查看第3点),故在c2之后插件3个空字节以使test1对齐。此时共分配了12个字节,所以Sizeof(test)的结果为12。


2、复杂类型(包含结构体成员)的默认对齐方式是它最长的成员的对齐方式,这样的成员是复杂类型时,以其最长的成员的对齐方式对齐。

例2:
  1. struct test2
  2. {
  3. char c1; // 1字节对齐
  4. test1 t1; // 4字节对齐
  5. short s; // 2字节对齐
  6. };

根据第2点知道test1的对齐方式是4字节对齐。所以test2的对齐方式是t1字节的对齐方式,即4字节对齐。


sizeof(test2)的结果为20。

test2 的内存分布如下(以1表示占用字节,*表示空字节)

c1t1s
1***11111111111111**


3、对齐后的长度必须是该类型的对齐字节数的整数倍。



自定义字节对齐方式



使用伪指令#pragma pack(__attribute((aligned (n)))也可以实现, 这里不讨论)可以实现字节对齐方式设置。


设置自定义成员对齐后,同样要满足每个成员按自己的方式对齐,也就是说虽然指定对齐字节数,但并不是所有的成员都以指定对齐字节数来对齐。其对齐的规则是:每个成员按其类型的长度和指定对齐字节数中较小的一个对齐。例如变量var的长度是4个字节, 自定义对齐字节长度为2(或8), 则var的对齐字节数为2(或4)。


使用格式如下(建议:使用时最好相互匹配):


1、#pragma pack(n)与#pragma pack ()


  1. #pragma pack(n) // 编译器将按照n个字节对齐。
  2. #pragma pack() // 取消自定义字节对齐方式(取消后恢复到编译器默认的对齐方式)。


例3:
  1. #pragma pack(2)
  2. struct test3 // test3采用2字节对齐(满足默认对齐中的第2点)
  3. {
  4.     char c1;    //c1长度1个字节, 采用1字节对齐
  5.     short s;    // s长度为2个字节,采用2字节对齐
  6.     int i;      // i长度为4个字节,大于自定义的2字节,故采用2字节对齐 
  7.     char c2;    //c2长度1个字节,采用1字节对齐
  8.     float f;    // f长度4字节,大于自定义的2字节,故采用2字节对齐
  9.     double d; //d长度8字节,大于自定义的2字节,故采用2字节对齐
  10.     char c3;    //c2长度1个字节,采用1字节对齐
  11. };
  12. #pragma pack()

sizeof(test3)的结果为24。

test3 的内存分布如下(以1表示占用字节,*表示空字节)

c1sic2fdc3
1*1111111*1111111111111*


原因如下:
  • 成员c1,其偏移地址为0,占据了第1个字节。此时共分配了1个字节。
  • 成员s为short类型,占用2个字节,大于等于自定义的2字节,故采用2字节对齐。已经分配的1个字节不是2的整数倍,所以编译器在c1与s之间插入1个空字节,然后为s分配2个字节。此时共分配了4个字节。
  • 成员i为int类型,占用4个字节,大于等于自定义的2字节,故采用2字节对齐。已经分配的4个字节是2的整数倍,所以编译器在i与s之间不插入空字节,直接为i分配4个字节。此时共分配了8个字节。
  • 成员c2为char类型,占用1个字节,小于等于自定义的2字节,故采用1字节对齐。已经分配的8个字节是1的整数倍,所以编译器在c2与i之间不插入空字节,直接为c2分配1个字节。此时共分配了9个字节。
  • 成员f为float类型,占用4个字节,大于等于自定义的2字节,故采用2字节对齐。已经分配的9个字节不是2的整数倍,所以编译器在c2与f之间插入1个空字节,然后为f分配4个字节。此时共分配了14个字节。
  • 成员d为double类型,占用8个字节,大于等于自定义的2字节,故采用2字节对齐。已经分配的14个字节是2的整数倍,所以编译器在d与f之间不插入空字节,直接为d分配8个字节。此时共分配了22个字节。
  • 成员c3为char类型,占用1个字节,小于等于自定义的2字节,故采用1字节对齐。已经分配的22个字节是1的整数倍,所以编译器在c3与d之间不插入空字节,直接为c3分配1个字节。此时共分配了23个字节。
  • 此时所有的成员已经分配完了,但test3是采用2字节对齐的,23不是2的整数倍,故在c2之后插件1个空字节以使字节对齐。此时共分配了24个字节,所以sizeof(test3)的结果为24。

如果test采用编译器默认对齐方式,则sizeof(test)的结果为32,此处省略原因。


2、#pragma pack (push,n)与#pragma pack(pop)


  1. #pragma pack (push,n) // 把原来对齐方式设置压栈,并设新的对齐方式为n个字节对齐
  2. #pragma pack(pop) // 恢复原来对齐方式


从上面两点来看#pragma pack(n)与#pragma pack (push,n)来看,他们之间的区别并不大,可以相互代替,只是后者恢复时是恢复到了原来的对齐方式,而前者是恢复到了编译器默认的对齐方式。推荐使用后者。


例4:#pragma pack(n)与#pragma pack (push,n)的混合使用

  1. #include <stdio.h>
  2. // 采用默认的字节对齐方式, 即t1中最长的类型int, 即4字节对齐
  3. struct t1
  4. {
  5. char c;
  6. int i;
  7. short s;
  8. float f;
  9. char c1;
  10. };
  11. #pragma pack(2) // 编译器将按照2个字节对齐
  12. // 2字节对齐
  13. struct t2
  14. {
  15. char c;
  16. int i;
  17. short s;
  18. float f;
  19. char c1;
  20. };
  21. #pragma pack(push, 1) // 把原来对齐方式(2字节)置压栈,并设新对齐方式为1字节
  22. // 1字节对齐
  23. struct t3
  24. {
  25. char c;
  26. int i;
  27. short s;
  28. float f;
  29. char c1;
  30. };
  31. #pragma pack(2) // 编译器将按照2个字节对齐
  32. // 2字节对齐
  33. struct t4
  34. {
  35. char c;
  36. int i;
  37. short s;
  38. float f;
  39. char c1;
  40. };
  41. #pragma pack() // 取消自定义字节对齐(2字节)方式, 采用默认字节对齐方式
  42. // 采用默认的字节对齐方式, 即t5中最长的类型int, 即4字节对齐
  43. struct t5
  44. {
  45. char c;
  46. int i;
  47. short s;
  48. float f;
  49. char c1;
  50. };
  51. #pragma pack(pop) // 取消1字节对齐方式。恢复到原来的2个字节对齐方式
  52. // 2字节对齐
  53. struct t6
  54. {
  55. char c;
  56. int i;
  57. short s;
  58. float f;
  59. char c1;
  60. };
  61. #pragma pack() // 取消自定义字节对齐(2个字节)方式, 采用默认的字节对齐方式
  62. // 采用默认的字节对齐方式, 即t7中最长的类型int, 即4字节对齐
  63. struct t7
  64. {
  65. char c;
  66. int i;
  67. short s;
  68. float f;
  69. char c1;
  70. };
  71. int main()
  72. {
  73. printf("sizeof(char): %d\n", sizeof(char));
  74. printf("sizeof(short): %d\n", sizeof(short));
  75. printf("sizeof(int): %d\n", sizeof(int));
  76. printf("sizeof(float): %d\n", sizeof(float));
  77. printf("\n=======================================\n\n");
  78. printf("sizeof(t1): %d\n", sizeof(t1));
  79. printf("sizeof(t2): %d\n", sizeof(t2));
  80. printf("sizeof(t3): %d\n", sizeof(t3));
  81. printf("sizeof(t4): %d\n", sizeof(t4));
  82. printf("sizeof(t5): %d\n", sizeof(t5));
  83. printf("sizeof(t6): %d\n", sizeof(t6));
  84. printf("sizeof(t7): %d\n", sizeof(t7));
  85. return 0;
  86. }
  87. /* 输出结果:
  88. sizeof(char): 1
  89. sizeof(short): 2
  90. sizeof(int): 4
  91. sizeof(float): 4
  92. =======================================
  93. sizeof(t1): 20
  94. sizeof(t2): 14
  95. sizeof(t3): 12
  96. sizeof(t4): 14
  97. sizeof(t5): 20
  98. sizeof(t6): 14
  99. sizeof(t7): 20
  100. 请按任意键继续. . .
  101. */


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值