结构体对齐

关注微信公众号塔容万物

结构体在进行编译时,编译器(gcc)会对结构体中的成员进行对齐。对齐大小默认按照结构体中大小最大的成员进行对齐。

typedef struct {
  char* name;  // 8
  int height;  // 4
  int width;   // 4
} box1_t;

typedef struct {
  int width;  // 4
  char* name; // 8
  int height; // 4
} box2_t;

对于两个含有相同的成员的结构体,由于存在对齐,最终的结构体大小也会不一样。编译器在编译时,不会智能的对结构体成员进行排序,而是按照结构体中成员的定义顺序进行对齐。

box1_t
+-----------------+-----------------+
|      name       | height  | width |
+-----------------+-----------------+
|       8         |    4    |   4   |
+-----------------+-----------------+

box2_t
+-----------------+-----------------+-----------------+
|      width      |      name       |      height     |
+-----------------+-----------------+-----------------+
|        8        |        8        |        8        |
+-----------------+-----------------+-----------------+

由于两个结构体中最大的元素大小均为char*,即大小为8个字节,所以每个元素的大小都将对齐为8字节,如果两个相邻的元素所占大小小于等于8个字节,那么就可以将两个元素放在一起,一共占用8字节,否则则需要两个8字节。

通过#pragma pack()宏可以修改对齐大小

#pragma pack(2) // 2 即为4个字节
typedef struct {
  int width;
  char* name;
  int height;
} box3_t;
#pragma pack()

此时在内存中,结构体成员的对齐方式就变成了这样

box3_t
+--------+---------+-------+----------+
|  width |      name       |  height  |
+--------+---------+-------+----------+
|    4   |        8        |     4    |
+--------+---------+-------+----------+

结构体对齐的原因主要是为了方便内存的读取,当内存对齐都为8字节时,可以确保任何一个结构体成员都可以通过一次内存读取获得,而当内存对齐为4字节时,读取小于等于4个字节以下的元素可以保证一次读取,但当读取一个8字节的成员时,可能会出现两次读取,这将会导致程序运行速度变慢。这也是编译器的无奈之举,速度快,那么内存占用必然会变得很大,反之,内存占用小,程序可能会需要更多的时间执行。

#include <stdio.h>

typedef struct {
  char* name;  // 8
  int height;  // 4
  int width;   // 4
} box1_t;

typedef struct {
  int width;  // 4
  char* name; // 8
  int height; // 4
} box2_t;

#pragma pack(2)
typedef struct {
  int width;
  char* name;
  int height;
} box3_t;
#pragma pack()

int
main()
{
  printf("sizeof(box1_t) = %lu\n", sizeof(box1_t));
  printf("sizeof(box2_t) = %lu\n", sizeof(box2_t));
  printf("sizeof(box3_t) = %lu\n", sizeof(box3_t));
}
// sizeof(box1_t) = 16
// sizeof(box2_t) = 24
// sizeof(box3_t) = 16