C语言中的结构体填充和打包



什么是C语言中的结构体填充?

C语言中的结构体填充是由CPU架构处理的过程。结构体填充在结构体中添加一定数量的空字节,以便数据成员在内存中自然对齐。对齐要求由处理器架构决定,而不是由语言本身决定。当然,对齐要求会根据某个CPU架构的数据总线大小或其他架构方面的考虑而改变。

通过示例理解结构体填充

让我们定义如下结构体类型:

struct struct1 {
   char x;
   int y;
   char z;
};

示例1

让我们检查这种类型变量所需的字节大小:

#include <stdio.h>

struct struct1{
   char a;
   char b;
   int c;
};

int main(){

   printf("Size: %d", sizeof(struct struct1));  
   return 0;
}

输出

运行此代码时,将产生以下输出:

Size: 8

结果与预期相反。

考虑到char类型需要1个字节,int类型需要4个字节,人们可能会认为输出应该是“1 + 1 + 4 = 6字节”。

Structure Padding

但是,CPU架构需要更改此结构。考虑到我们使用的是具有32位处理器的CPU,它一次读取4个字节,这意味着一个字等于4个字节。

在一个CPU周期中,它访问char "a",然后是char "b"和int "c"的前两个字节。在第二个周期中,访问其他两个字节。

即使我们只想读取“c”,也需要两个CPU周期。为此,CPU在存储“c”值的字节之前添加两个空字节。这种机制称为填充

Structure Padding

现在解释了我们上面获得的结果,即结构体类型的尺寸为8字节。

示例2

让我们更改上述结构体成员的顺序,并设置“b”和“c”的类型。

#include <stdio.h>

struct struct1{
   char a;
   int b;
   char c;
};

int main(){

   printf("size: %d", sizeof(struct struct1));
   return 0;
}

输出

运行代码并检查其输出:

size: 12

在第一个字的4个字节中,第一个字节分配给char "a",后面跟着三个空字节。

构成下一个字的接下来的4个字节用于存储int "b"。随后,在接下来的4个字节组中,只有一个用于“c”。但是,结构体大小为12。

什么是C语言中的结构体打包?

另一方面,结构体打包是一种最小化填充影响的机制,从而尝试减少浪费的内存空间。我们可以使用某些pragma指令和属性来实现打包。

通过示例理解结构体打包

由CPU架构强制执行的填充是不可避免的,但是有一些方法可以最大限度地减少填充。可以使用以下方法:

  • 使用 #pragma pack(1) 指令
  • 使用 packed 属性

使用 #pragma pack(1) 指令

#pragma pack(1) 预处理器指令强制编译器忽略填充,并在内存分配过程中将结构体成员端对端对齐。

示例

让我们将此指令添加到前面使用的代码顶部,并查看结果:

#include <stdio.h>
#pragma pack(1)

struct struct1{
   char a;
   int b;
   char c;
};

int main(){

   printf("size: %d", sizeof(struct struct1));
   return 0;
}
输出

运行代码并检查其输出:

size: 6

我们可以看到结构体填充已被避免,并减少了内存浪费。

使用 __attribute__((packed))

使用GCC,我们可以使用属性来指定结构体和联合体类型的各种特殊属性。这些属性是:aligned, deprecated, packed, transparent_union, unusedvisibility。应用这些属性的语法是“__attribute__ ((...))”。

示例

在这里,我们将使用packed属性在我们的结构体类型定义中。

#include <stdio.h>

struct __attribute__((packed)) struct1{
   char a;
   int b;
   char c;
};

int main(){

   printf("size: %d", sizeof(struct struct1));
   return 0;
}
输出

运行代码并检查其输出:

size: 6

此方法也避免了填充的影响。

广告