什么是字节对齐?

计算机并非逐字节读取内存,而是按2,4,8的倍数的字节块读写内存,故地址必须为上述倍数,故各种数据类型需要按照一定规则在空间上排列

对齐准则

  • 结构体变量首地址可被对齐字节数大小所整除
  • 结构体每个成员相对该结构体首地址的偏移都是成员大小的整数倍(如需要,填充字节)
  • 结构体总大小为结构体对齐字节数大小的整数倍

    为什么要字节对齐?

    可优化【读写效率】【空间存储】【跨平台通信】

读写效率

考虑场景如下:若计算机每次读取8字节块,此时读取一个double,若未字节对其,可能该double位与两个不同的字节块,需要做两次读取才能读出double的值,显然效率低下!

空间存储

字节对齐的细节由编译器实现,为何我们还需要考虑字节对齐?

考虑如下代码

  1. #include<stdio.h>
  2. #include<stdint.h>
  3. #ifdef DEBUG
  4. struct test
  5. {
  6. int a;
  7. char b;
  8. int c;
  9. short d;
  10. test(int v1, char v2, int v3, short v4) :
  11. a(v1),b(v2),c(v3),d(v4){}
  12. }; /* 16字节 */
  13. #else
  14. struct test
  15. {
  16. int a;
  17. char b;
  18. short d;
  19. int c;
  20. test(int v1, char v2, int v3, short v4) :
  21. a(v1),b(v2),c(v3),d(v4){}
  22. };/* 12字节 */
  23. #endif
  24. int main(int argc, char* argv)
  25. {
  26. /*在32位和64位的机器上,size_t的大小不同*/
  27. struct test a(1,'a',2,3);
  28. printf("the size of struct test is %zu\n", sizeof(a));
  29. return 0;
  30. }

实验结果如下:

  • 16字节内存分布:

image.png

  • 12字节内存分布:

image.png

结论

设计结构体时,合理调整成员的位置,可大大较少存储空间(比如说将内存占用较小的变量顺序放置)

  1. /* 32位机器上执行运行 */
  2. struct test
  3. {
  4. int a;
  5. char b;
  6. int c;
  7. short d;
  8. }; /* 大小为16字节 */
  9. /* 未对齐 : 0-3 , 4 , 5-8 , 9-10*/
  10. /* 对齐: 0-3 , 4-7 , 8-11 , 12-15*/
  11. struct test{
  12. int a;
  13. char b;
  14. short d;
  15. int c;
  16. };
  17. /* 对齐:0-3 , 4-5 , 6-7 , 8-11 */
  18. /* b和d 存储在了同一字节块 */

跨平台通信

由于不同平台对齐方式可能不同,故可采用两种处理方式。

  • 1字节对齐
  • 主动对结构【字节填充】

    1字节对齐#pragma pack(n)

    1. #pragma pack(1) /*1字节对齐*/
    2. struct test
    3. {
    4. int a;
    5. char b;
    6. int c;
    7. short d;
    8. };
    9. #pragma pack()/*还原默认对齐*/
    其内存分布如下(占11字节):
    image.png

    主动填充

    1. struct test
    2. {
    3. int a;
    4. char b;
    5. char reserve[3];
    6. int c;
    7. short d;
    8. char reserve1[2];
    9. };
    不节省空间且扩展性差,不推荐

总结

在编程过程中,不需要关注字节对齐的细节,但不可忽略字节对齐,故总结了如下经验:

  1. 结构体合理安排成员位置
  2. 跨平台数据可考虑1字节填充
  3. 本地数据采用默认对齐,以提高访问效率