概念概述
结构体的内存对齐是指在计算机编程中,编译器按照一定的规则对结构体中的数据成员在内存中的存放位置进行调整,以确保数据成员的地址满足特定的对齐要求。这种对齐不是指数据类型本身的大小,而是指数据在内存中的位置对齐到某个边界,通常是数据大小的倍数。
内存对齐的目的是为了提高内存访问的效率。许多处理器在访问非对齐的数据时会有性能损失,甚至可能不支持非对齐访问。
结构体对齐的原则
1. 数据成员对齐
每个数据成员在内存中的位置会根据其类型的大小和对齐要求进行调整。例如,如果一个
int
类型的变量需要在4字节边界上对齐,那么编译器会确保其地址是4的倍数。2. 结构体整体对齐
整个结构体的大小也会根据其最大成员的对齐要求进行调整。这意味着结构体的总大小可能是成员大小之和加上一些填充字节,以满足最大成员的对齐要求。
3. 填充字节
为了满足对齐要求,编译器可能会在结构体中的数据成员之间或结构体末尾添加填充字节。这些填充字节不会存储任何有用的数据,但它们的存在确保了结构体和其成员都能被正确对齐。
4. 对齐边界
对齐边界通常是数据大小的倍数,例如,1字节、2字节、4字节、8字节等。不同的平台和编译器可能有自己的对齐规则。
总结
1、第一个成员在与结构体偏移量为0的地址处;
2、其他成员变量要与自身类型的整数倍地址处对齐;
3、结构体总大小为要与 “处理器字节数与成员类型所占字节数最大的最小值” 的整数倍对齐;
4、如果出现嵌套情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
5、#pragma pack(n) 可以用来控制默认对齐数的大小
结构体内存对齐的详细图解
结构体内存对齐-举例1
首先我们创建一个结构体
我们要计算他的字节大小,如果只是看起来,那就是无非是1字节,4字节,1字节,但是实际放到编译器里面, 计算子字节大小的时候并不是6字节大小,而是12字节大小
这里我们进行图片的解释
步骤详解
结构体内存对齐-举例2
结构体内存对齐-举例3
对齐自己结构体最大成员字节个数
为什么存在内存对齐
硬件性能优化:
- 许多处理器在访问内存时,对数据的地址有一定的对齐要求。如果数据没有对齐,处理器可能需要进行额外的内存访问操作来获取完整的数据,这会降低性能。
- 对齐的数据可以确保处理器能够以最高效的方式访问内存,因为这样可以减少访问次数和提高缓存的利用率。
数据访问速度:
- 数据访问速度是计算机性能的关键因素之一。对齐的数据可以更快地被处理器读取,因为它们可以被一次性完整地加载到处理器的寄存器中。
- 我们可以在使用的时候,减少浪费空间,占用空间小的放在一起,就会最大限度的节约空间
一致性:
- 在多处理器系统中,内存对齐有助于确保数据在不同的处理器之间保持一致性。这是因为对齐的数据更容易被缓存系统正确地识别和同步。
内存访问的原子性:
- 某些硬件平台要求数据在特定的边界上对齐,以确保数据访问的原子性。这意味着在没有其他处理器干预的情况下,可以安全地读取或写入数据。
编译器优化:
- 编译器在生成机器代码时,会利用内存对齐来优化代码。对齐的数据可以使得编译器更容易地生成高效的指令序列。
跨平台兼容性:
- 不同的硬件平台可能有不同的内存对齐要求。通过使用内存对齐,可以提高程序在不同平台上的兼容性。
避免额外的内存访问开销:
- 对于某些数据类型,如浮点数和整数,如果它们没有对齐,处理器可能需要进行两次内存访问来获取完整的数据,这会增加内存访问的开销。
硬件设计:
- 硬件设计者在设计处理器时,会考虑到内存对齐,以确保数据能够以最有效的方式被处理。这通常涉及到处理器的内存访问单元和缓存系统的设计。
内存对齐的类比:
我们可以把有内存对齐的比作一个大巴车,没有内存对齐的比作一个摩托车,不管是大巴车还是摩托车,单趟行动是速度是一样的。没有内存对齐一次只能拉取一个小孩,有内存对齐的大巴车一次可以拉取一车的小孩。
但是,可能存在一个情况就是,小孩是有班级的,那么就会存在这个班级的小孩上去了,但是车还有空位,所以第二个班级的小孩继续上去,那么出来的时候为了防止小孩打乱顺序,找不到班级,就会采取内存对齐的方式,带走,带下去。
同时内存对齐还有一个优点就是,如果你需要两个数据,那么摩托车需要两趟, 但是大巴车一趟就可以,这一趟如果可以存放30个数据,那么就会拉三十个数据,不会说你需要两个我就拉取两个,你需要我就直接给你
大巴车(内存对齐)与摩托车(无内存对齐)
大巴车(内存对齐):大巴车代表有内存对齐的数据结构。它能够一次性装载多个小孩(数据),并且因为大巴车(数据结构)的设计符合特定的对齐要求,所以它可以高效地利用道路(内存总线)和车站(内存地址)。大巴车在每个车站(内存地址)都能快速准确地停靠和出发,因为车站(内存地址)和大巴车(数据结构)都是按照相同的规则设计的。
摩托车(无内存对齐):摩托车代表没有内存对齐的数据结构。它每次只能携带一个小孩(数据),并且因为没有特定的对齐要求,摩托车在接送小孩时可能需要在多个不同的地点停靠,这可能导致效率低下,因为每次接送都需要额外的时间来定位和调整。
班级小孩的比喻
班级组织:每个班级的小孩可以看作是一组数据,这些数据在内存中是连续存放的。内存对齐确保了整个班级(数据组)能够被一次性、高效地加载到大巴车(处理器缓存)中。
空位问题:大巴车可能在装载完一个班级的小孩后还有空位。这时,如果按照对齐规则,可能会有另一个班级的小孩继续上车,直到填满大巴车。这样做的目的是为了充分利用大巴车(内存对齐)的容量,减少往返次数,提高整体的运输效率。
下车顺序:在目的地,为了避免小孩(数据)混乱,大巴车会按照上车时的对齐顺序让小孩下车。这确保了每个班级的小孩能够正确地找到自己的队伍,类比于处理器能够正确地处理和访问内存中的数据。
内存对齐的优点
批量处理:内存对齐允许处理器一次性加载和存储多个数据项,而不是逐个处理。这类似于大巴车一次性装载多个小孩,而不是像摩托车那样一次只拉一个。
减少访问次数:如果需要两个数据,摩托车(无内存对齐)可能需要两次访问,而大巴车(内存对齐)可以一次性提供足够的数据。这减少了内存访问的次数,从而提高了效率。
缓存利用:内存对齐有助于更好地利用处理器缓存。当数据被对齐存储时,它们更有可能被一起加载到缓存中,这减少了缓存未命中的可能性。
修改默认对齐数(Linux没有对齐数那也就是变成了自身的大小)
使用
#pragma pack
指令这是最常用的方法之一,可以在编译时指定结构体或类的对齐字节。这个指令是编译器特定的,因此在不同的编译器中可能有所不同。
//此时默认对齐数设置为2 #pragma pack(2) struct MyStruct { char a; int b; double c; }; #pragma pack()
这里有内存对齐的题型,建议观看
结构体内存对齐+枚举+内存开辟+文件操作的专用题型-CSDN博客https://blog.csdn.net/Jason_from_China/article/details/137007489