7. 字节对齐控制

上节课我们学习了结构体字节对齐,编译器为了优化程序,让程序更快的运行,它在编辑阶段可能在结构体内添加填充字节以让结构体内所有的成员变量都字节对齐。但这些填充字节有时会给我们带来不小的麻烦。比如我们将一个结构体内的数据以二进制方式发送到远程计算机或保存到文件中时,这些填充字节也会随之发送。我们浪费了流量和存储空间,同时为系统安全留下了隐患。

在 C 语言中 我们可以使用 #pragma pack 预处理指令来对编译器默认的字节对齐字节数进行修改。

#pragma pack 预处理指令

#pragma pack 预处理指令的作用是指示编译器以下代码以哪种对齐字节数进行对齐。

语法:

// 设置指定的对齐字节数
#pragma pack(n)

// 恢复编译器默认的对齐字节数
#pragma pack()

说明

  1. 当使用 #pragma pack(n) 时,编译器默认对齐字节数将被替换为 n
  2. n 的值必须是 2 的 x 次方的形式,通常是:1、2、4、8 等。
  3. 当使用 #pragma pack() 时,将恢复对齐字节数为编译器默认对齐字节数。

示例:

将以下结构体设置为 1 字节对齐

struct mydata {
    char c;
    short int s;
    int i;
};

示例代码如下:

// filename: pragma_pack.c
#include <stdio.h>

#pragma pack(1)  // 设置为 1 字节对齐
struct mydata {
    char c;
    short int s;
    int i;
};
#pragma pack()  //  恢复为默认对齐方式

int main(int argc, char * argv[]) {
    struct mydata d;

    printf("sizeof(d): %ld\n", sizeof(d));

    printf("&d.c: %p\n", &d.c);
    printf("&d.s: %p\n", &d.s);
    printf("&d.i: %p\n", &d.i);

    return 0;
}

编译和运行结果如下:

weimingze@mzstudio:~$ gcc -o pragma_pack pragma_pack.c
weimingze@mzstudio:~$ ./pragma_pack
sizeof(d): 7
&d.c: 0x7ffdd39b4421
&d.s: 0x7ffdd39b4422
&d.i: 0x7ffdd39b4424
weimingze@mzstudio:~$

根据上述结果分析结构体的内存结构如下:

  ____________ 共占用 7 字节 _____________
 /                                       \
+-----+-----+-----+-----+-----+-----+-----+
| d.c |    d.s    |          d.i          |
+-----+-----+-----+-----+-----+-----+-----+
^     ^           ^
|     |           |
&d.c  &d.s        &d.i
&d

GCC 编译器的 __attribute__((packed)) 特性

__attribute__((packed)) 特性 的作用同 #pragma pack(1) 一致,它用于结构体声明中,它可使当前声明的结构体取消字节对齐(即设置编译器对齐字节数是1)。

语法:

struct 结构体名 {
    数据类型 1成员变量名1;
    ...
} __attribute__((packed));

说明:

  1. __attribute__((packed)) 特性仅能用于 GCC 编译器中。
  2. __attribute__((packed)) 特性仅对当前声明的结构体有效。

示例:

上述结构体中

#pragma pack(8)  // 设置为 1 字节对齐
struct mydata {
    char c;
    short int s;
    int i;
};
#pragma pack()  //  恢复为默认对齐方式

在 GCC 编译器中可以替换成如下代码,但运行结果是一样的。

struct mydata {
    char c;
    short int s;
    int i;
}__attribute__((packed));

如果你安装的是 GCC 编译器,请自行尝试运行上述代码。

实验

已知结构体如下:

struct pack_demo {
    char a;
    int b;
    short c;
};

其尝试使用 1 字节对齐、2字节对齐、4字节对齐、8字节对齐和编译器默认的对齐方式来测试其结构体的内存结构。