结构填充与打包在C语言中

你好,未来的计算机巫师们!今天,我们将踏上一段激动人心的旅程,探索C编程世界的概念,特别是结构填充(padding)和结构打包(packing)。现在这些术语可能听起来像是在说胡话——但到了本教程结束时,你会像专家一样向你的朋友们解释它们!

C - Structure Padding and Packing

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

想象你正在为一个旅行打包行李箱。你希望每样东西都能整齐地放下,但有时在物品之间会留下一些奇怪的空间。在C编程中,结构填充就像是行李箱中的那些空间。

当我们创建一个结构体时,编译器有时会在结构成员之间添加额外的字节。这就是填充。但为什么会这样做呢?这一切都是为了效率,确保我们的计算机能够快速读取数据。

让我们来看一个简单的例子:

struct Example {
char c;
int i;
char d;
};

你可能认为这个结构体应该占据6个字节(每个char占用1个字节,int占用4个字节)。但实际上,它通常占用12个字节!让我们来分解一下:

  1. char c占用1个字节。
  2. 为了对齐int i,编译器在c后面添加了3个字节的填充。
  3. int i占用4个字节。
  4. char d占用1个字节。
  5. 为了使整个结构体的大小是4的倍数(以便对齐),在最后添加了3个字节的填充。

所以,1 + 3 + 4 + 1 + 3 = 12个字节总大小。

通过示例理解结构填充

让我们通过更多的例子来更深入地理解这个概念。

示例1:顺序不同,填充不同

struct StructA {
char c;
int i;
char d;
};

struct StructB {
int i;
char c;
char d;
};

在这个例子中,StructA通常占用12个字节,就像我们之前看到的那样。但StructB只占用8个字节!布局将是:

  1. int i:4个字节
  2. char c:1个字节
  3. char d:1个字节
  4. 在最后有2个字节的填充

这向我们展示了结构成员的顺序可以影响其大小,因为填充。

示例2:使用sizeof()检查结构体大小

#include <stdio.h>

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

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

int main() {
printf("Size of PaddedStruct: %lu\n", sizeof(struct PaddedStruct));
printf("Size of PackedStruct: %lu\n", sizeof(struct PackedStruct));
return 0;
}

这段代码将输出:

Size of PaddedStruct: 12
Size of PackedStruct: 6

sizeof()函数在这里是我们的好朋友,帮助我们查看结构体的实际大小。

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

既然我们理解了填充,那么让我们来谈谈它的对应概念:打包。结构打包就像是在用数据玩俄罗斯方块游戏——你试图尽可能紧密地放置所有东西,而不留任何空隙。

当我们打包一个结构体时,我们告诉编译器:“嘿,不要添加任何额外的填充。我希望这些数据尽可能紧凑。”这样可以节省内存,但可能会使数据访问速度稍微慢一些。

通过示例理解结构打包

让我们看一些例子,了解实际中打包是如何工作的。

示例1:使用打包属性

struct PackedExample {
char c;
int i;
char d;
} __attribute__((packed));

通过添加__attribute__((packed)),我们告诉编译器紧紧地打包这个结构体。现在,sizeof(struct PackedExample)将返回6而不是12。

示例2:比较打包和非打包结构体

#include <stdio.h>

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

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

int main() {
printf("Size of UnpackedStruct: %lu\n", sizeof(struct UnpackedStruct));
printf("Size of PackedStruct: %lu\n", sizeof(struct PackedStruct));
return 0;
}

这将输出:

Size of UnpackedStruct: 12
Size of PackedStruct: 7

非打包结构体有填充,而打包的没有。

示例3:打包结构体的潜在问题

虽然打包可以节省内存,但它有时会导致访问速度变慢,甚至在某些系统上导致错误。以下是一个可能导致问题的示例:

#include <stdio.h>

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

int main() {
struct PackedStruct ps;
ps.a = 'A';
ps.b = 12345;

int *ptr = &ps.b;
printf("Value of b: %d\n", *ptr);

return 0;
}

在某些系统上,这可能没问题。在其他系统上,它可能会导致对齐错误,因为ps.b没有对齐到4字节边界。

结论

理解结构填充和打包对于编写高效的C代码至关重要,特别是在处理嵌入式系统或内存优化非常重要时。记住,填充是关于性能,而打包是关于节省空间。和编程中的许多事情一样,找到适合你特定需求的权利平衡非常重要。

以下是我们在本教程中讨论的方法的快速参考表:

方法 描述 示例
默认填充 编译器自动添加填充 struct Example { char c; int i; };
使用属性进行打包 强制结构体打包 struct PackedExample { char c; int i; } __attribute__((packed));
使用sizeof() 检查结构体的实际大小 sizeof(struct Example)

继续尝试这些概念,很快你将成为结构填充和打包的专家!祝未来的技术巨星们编程愉快!

Credits: Image by storyset