【C语言】深度剖析:结构体内存对齐原理与实践

您所在的位置:网站首页 c语言的深度剖析 【C语言】深度剖析:结构体内存对齐原理与实践

【C语言】深度剖析:结构体内存对齐原理与实践

2024-07-01 10:24| 来源: 网络整理| 查看: 265

欢迎浏览高耳机的博客

希望我们彼此都有更好的收获

感谢三连支持!

C语言中,结构体(struct)是一种用户定义的数据类型,允许你将不同类型的数据组合在一起,形成一个新的数据类型。

结构体由多个成员变量组成,每个成员变量可以是不同的数据类型。你可以使用结构体来表示复杂的数据结构,比如表示学生信息、坐标点、汽车属性等。

在本篇博客中,我将重点介绍结构体的内存布局以及对齐方法

(重点)结构体的内存布局以及对齐

对齐规则

为什么存在内存对齐?

结构体的声明

 例如描述一个学生:

struct Stu { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 }; //分号不能丢

结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。 

结构体变量的创建和初始化 

再例如: 

// 定义一个结构体 struct Point { int x; int y; }; int main() { // 声明一个结构体变量 struct Point p1; // 访问结构体成员并赋值 p1.x = 10; p1.y = 20; // 打印结构体成员的值 printf("x 坐标:%d\n", p1.x); printf("y 坐标:%d\n", p1.y); return 0; }

在这个示例中,我们定义了一个名为Point的结构体,它有两个成员变量x和y,分别表示坐标点的横纵坐标。在main函数中,我们声明了一个Point类型的变量p1,并对其成员变量进行初始化和访问。 

结构体成员的访问和操作 

使用点号运算符(.)来访问结构体的成员变量 

#include // 定义一个结构体 struct Person { char name[50]; int age; }; int main() { // 声明并初始化一个结构体变量 struct Person person1 = {"Alice", 25}; // 访问结构体成员并打印 printf("Name: %s\n", person1.name); printf("Age: %d\n", person1.age); // 修改结构体成员的值 person1.age = 26; printf("Updated Age: %d\n", person1.age); return 0; }

在示例中,声明了一个名为Person的结构体,包含了name和age两个成员变量。创建一个结构体变量person1,并通过点号运算符访问和修改其成员变量的值。 

结构体作为函数参数的传递

结构体作为参数传递给函数,实际上是将整个结构体的副本传递给函数。这意味着在函数内部对结构体的操作不会影响到原始结构体变量,除非通过指针传递结构体的地址。

#include // 定义一个结构体 struct Person { char name[50]; int age; }; // 函数接受结构体参数并打印成员值 void printPerson(struct Person p) { printf("Name: %s\n", p.name); printf("Age: %d\n", p.age); } int main() { // 声明并初始化一个结构体变量 struct Person person1 = {"Bob", 30}; // 调用函数,将结构体作为参数传递 printPerson(person1); return 0; }

在示例中,定义了一个名为Person的结构体,以及一个接受结构体参数的printPerson函数。然后创建一个结构体变量person1,在主函数中调用printPerson函数将结构体作为参数传递,并在函数内部打印结构体成员的值。 

结构体数组

结构体数组是一种数据结构,可以在一个数组中存储多个相同类型的结构体实例。这样可以方便地组织和管理多个相关联的数据。

#include // 定义一个结构体 struct Person { char name[50]; int age; }; int main() { // 定义并初始化结构体数组 struct Person people[3] = { {"Alice", 25}, {"Bob", 30}, {"Charlie", 28} }; // 遍历结构体数组并打印每个元素 for (int i = 0; i < 3; i++) { printf("Person %d - Name: %s, Age: %d\n", i+1, people[i].name, people[i].age); } return 0; }

在这个示例中,我们定义了一个包含3个元素的结构体数组people,并对每个元素进行了初始化。然后通过循环遍历结构体数组,并打印每个元素的成员值。 

(重点)结构体的内存布局以及对齐

在内存中,结构体的成员是按照定义的顺序依次分配内存空间的。每个成员的字节长度取决于其数据类型,不同的数据类型可能占用不同的字节数。

对齐规则 结构体的第一个成员对齐到结构体变量起始位置偏移量为0的地址处。其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值。在 Visual Studio 中,默认的值为 8。在 Linux 中 gcc 没有默认对齐数,对齐数就是成员自身的大小。结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,取所有对齐数中的最大值)的整数倍。如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包括嵌套结构体中成员的对齐数)的整数倍。

 s1中:c1是第一个结构体成员并占用一个字节,所以放在0地址处;由于i为整型,占用四个字节,但由于对齐规则的第二条,i需要从4地址处开始连续存放;这也就造成了c1与i之间产生了内存的浪费;最后,因为任何正整数都是一的倍数,所以将c2放在i的后面,也就是8地址;又因为结构体总大小为最大对齐数的整数倍,也就是整型的整数倍,也就是4字节的整数倍,即总大小为12(0-11)。

s2中:c1是第一个结构体成员并占用一个字节,所以放在0地址处;因为任何正整数都是一的倍数,所以将c2放在c1的后面,也就是1地址;由于对齐规则的第二条,i需要从4地址处开始连续存放;这也就造成了c2与i之间产生了内存的浪费;而8刚好是4字节的整数倍,所以总大小为8(0-7)。

绿色代表c1 黄色代表c2 蓝色代表i 红色代表内存碎片 

上图两个例子中 ,s1浪费掉的内存(红色)明显比s2要多

所以在设计结构体的时候,我们既要满足对齐,又要节省空间:

让占用空间小的成员尽量集中在一起。

为什么存在内存对齐?

 总的来说:结构体的内存对⻬是拿空间来换取时间的做法

提高内存访问效率:内存对齐可以使结构体成员按照特定规则排列,确保每个成员变量都位于其自然边界上。这样可以减少因为非对齐访问而引起的额外开销,提高内存访问的效率。

CPU处理器性能优化:许多 CPU 处理器要求数据类型在特定地址上的访问是对齐的,否则可能导致性能下降甚至错误。通过对结构体进行内存对齐,可以避免这种性能损失,提高程序的运行效率。

减少内存碎片:内存对齐可以减少内存碎片的产生,使得内存使用更加紧凑和高效,避免因为内存碎片而造成额外的内存开销。

优化缓存性能:当结构体按照对齐规则排列时,可以更好地利用 CPU 缓存的特性,提高缓存命中率,进而提升程序的性能。

因此,结构体的内存对齐是一种通过调整内存布局的方式来提高程序运行效率和性能的技术手段,它在设计和优化程序时发挥着重要作用。通过合理的内存对齐可以更好地利用硬件资源,从而达到更高的性能表现。

希望这篇博客能为你学习结构体以及了解原理提供一些帮助。

如有不足之处请多多指出。

我是高耳机。 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3