乐趣区

关于c:c语言结构体字节对齐详解

1. 什么是字节对齐

在 c 语言的构造体外面个别会依照某种规定去进行字节对齐。

咱们先看一段代码:

struct st1
{
    char name;
    double age;
    char sex;
};
//32 位下 sizeof(struct st1) = 16
//64 位下 sizeof(struct st1) = 24
struct st2
{
    char a;
    char b;
    char c;
};
//32 位和 64 位下, sizeof(struct st2)都是 3 个字节

从以上后果能够看出,构造体 st1 在 32 位下是依照 4 个字节来对齐的,在 64 位下则是依照 8 个字节来对齐的,构造体 st2 则不论 32 位还是 64 位则都是依照 1 个字节对齐的。

那么咱们能够总结出对齐规定如下:

  • 在所有构造体成员的字节长度都没有超出操作系统根本字节单位 (32 位操作系统是 4,64 位操作系统是 8) 的状况下,依照构造体中字节最大的变量长度来对齐;
  • 若构造体中某个变量字节超出操作系统根本字节单位,那么就依照零碎字节单位来对齐。

留神:并不是 32 位就间接依照 4 个字节对齐,64 位依照 8 个字节对齐。

2. 为什么要有字节对齐

首先遍及一点小常识,cpu 一次能读取多少内存要看数据总线是多少位,如果是 16 位,则一次只能读取 2 个字节,如果是 32 位,则能够读取 4 个字节,并且 cpu 不能跨内存区间拜访。

假如有这样一个构造体如下:

struct st3
{
    char a;
    int b;
};
// 那么依据咱们第 1 节所说的规定,在 32 位零碎下,它就应该是 8 个字节的。

假如地址空间是相似上面这样的:

在没有字节对齐的状况下,变量 a 就是占用了 0x00000001 这一个字节,而变量 b 则是占用了 0x00000002~0x000000005 这四个字节,那么 cpu 如果想从内存中读取变量 b,首先要从变量 b 的开始地址 0x00000002 读到 0x0000004,而后再读取一次 0x00000005 这个字节,相当于读一个 int,cpu 从内存读取了两次。

而如果进行字节对齐的话,变量 a 还是占用了 0x00000001 这一个字节,而变量 b 则是占用了 0x00000005~0x00000008 这四个字节,那么 cpu 要读取变量 b 的话,就间接一次性从 0x00000005 读到 0x00000008,就一次全副读取进去了。

所以说,字节对齐的根本原因其实在于 cpu 读取内存的效率问题,对齐当前,cpu 读取内存的效率会更快。然而这里有个问题,就是对齐的时候 0x00000002~0x00000004 这三个字节是节约的,所以字节对齐实际上也有那么点以空间换工夫的意思,具体写代码的时候怎么抉择,其实是看集体的。

3. 手动设置对齐

什么状况下须要手动设置对齐:

  • 设计不同 CPU 下的通信协议,比方两台服务器之间进行网络通信,共用一个构造体时,须要手动设置对齐规定,确保两边构造体长度始终;
  • 编写硬件驱动程序时寄存器的构造;

手动设置对齐形式有两种:

  • 代码里增加预编译标识:
// 用法如下
#pragma pack(n)// 示意它前面的代码都依照 n 个字节对齐
struct st3
{
    char a;
    int b;
};
#pragma pack()// 勾销依照 n 个字节对齐,是对 #pragma pack(n)的一个反向操作
// 这里计算 sizeof(st3)=5

下面这两行其实就相似于开车的时候,走到某一段路的时候,发现一个限速 60 公里的指示牌,过了那一段路当前,又会有解除限速 60 公里的指示牌。

  • 定义构造体时:
// 用法如下
struct bbb
{
   char a;
   int b;
}__attribute__((packed));// 间接依照理论占用字节来对齐,其实就是相当于依照 1 个字节对齐了
// 这里计算 sizeof(st3)=5

4. 构造体比拟办法

能够应用内存比拟函数 memcpy 进行构造体比拟,但因为构造体对齐可能会有填充位不统一的状况,此时须要留神:

  1. 设置为 1 个字节对齐,使它没有空位;
  2. 当时对构造体进行初始化;
memcpy(char *dest, const char* src, int len); // 头文件 #include<string.h>

退出移动版