字节对齐或者结构体对齐有什么作用?需要如何实现?本文详细分析了其中的细节,也欢迎大家一起来讨论,如有错误也请一并指出。
实际面试中可能不会涉及到那么具体的细节,一般只会问结构体对齐的规则是什么?关于这个问题可以参见本文的后一部分的内容,也可以看我的另一个文章:
https://blog.csdn.net/Sansipi/article/details/121599501
1. 名词解析
首先关于字节对齐(对齐的英文原名是alignment,但是英文里面没有byte alignment这种叫法,我见到的有data alignment, data structure alignment, structure alignment,这几个我觉得意思相近,都可以理解为data alignment,用byte alignment去搜索可能很久才能找到答案),结构对齐事实上涉及到的内容是非常多的,如果需要详细的掌握,
see:
http://www.catb.org/esr/structure-packing/#_version_history
这个资料是我目前觉得最为详细的关于structure alignment的资料,里面还涵盖了Java,Go等语言的情况。本文不涉及那么多只讨论c/c++中的情况。
对齐的意义:
c++的低级特性之一是能够指定内存中对象的精确对齐方式,从而最大限度地利用特定硬件架构的优势。在大多数情况下,不必关心对齐,因为默认对齐已经是最优的。但是,在某些情况下,通过为数据结构指定自定义对齐方式,可以实现显著的性能改进或内存节省。
(see: https://docs.microsoft.com/en-us/cpp/cpp/alignment-cpp-declarations?view=msvc-170)
现代计算机硬件中的CPU在数据自然对齐时最有效地执行对内存的读和写,这通常意味着数据的内存地址是数据大小的倍数。如char 类型的自然对齐方式应该是1字节对齐,int类型应该是4字节对齐,注意对于32和64位的系统可能不一样,如long类型在32位系统下是4-byte aligned,64位系统下是8-byte aligned
(see: https://en.wikipedia.org/wiki/Data_structure_alignment)
硬件平台对对齐的支持:
在允许非对齐的架构上(如x86和amd64),非对齐的内存访问会比较慢,而在SPARC等严格对齐的架构上则是明确禁止的。
对齐的类型:
本文主要讨论结构体内部的数据对齐,事实上还有其他的对齐,比如两个不同大小的结构体如何对齐,这个问题本文不讨论,如想进一步了解,
see:
https://stackoverflow.com/questions/4306186/structure-padding-and-packing
http://www.catb.org/esr/structure-packing/#_version_history
2. padding 和 packing区分
英文里面的paddling是填充的意思,packing是压紧的意思,前者是在进行对齐的时候需要做的事,后者是在使用pack pragma的时候会改变的填充的规则。
3. 无pragma pack的结构体对齐规则
- 分配内存的顺序是按照声明的顺序
- 结构体内每一个数据成员的起始位置(第一个元素的起始位置看成0)都应当能被该成员的大小整除,如果不能,就需要对该成员的前一个成员进行填充
- 对于整个结构体而言,还需要满足整个结构体的大小能被大小最大的成员的大小整除,如果不能,就在最后一个成员进行填充
上面的规则比较拗口,下面是实例:
/*
S 的大小为24。
首先i,j, k的起始位置分别为0, 4, 6,但是6不能被8整除,
因此需要对j进行填充,填充到7,以8作为k的起始位置就能符合要求,
因此实际的i,j, k的起始位置分别为0, 4, 8;
最后由于S的大小位16(4+2+2(填充)+8+4 = 20)不能被大小最大的元素k的
大小8整除,因此需要在l的后面填充4个字节,最终S的大小为24
*/
struct S {
int i; // size 4
short j; // size 2
double k; // size 8
int l; // size 4
} S1;
4. 有pragma pack的结构体对齐规则
关于pack的详细解释:
see:
https://docs.microsoft.com/en-us/cpp/preprocessor/pack?view=msvc-170
加入了#pragma pack(n)之后规则变成了(这是在一个面经里面看到的,希望有人能指出最原始的出处):
- 结构体内每一个数据成员的起始位置要是n和当前成员大小中较小值的整数倍
- 结构体的整体大小要是n和最大变量大小中较小值的整数倍
- n值必须为1,2,4,8,16,为其他值时就按照默认的分配规则
下面是具体的实例
#include <stddef.h>
#include <stdio.h>
#pragma pack(push)
#pragma pack(2)
struct S {
int i; // size 4
short j; // size 2
double k; // size 8
int l; // size 4
} S1;
#pragma pack(pop)
int main() {
// 打印每一个元素的偏移量(也就是起始位置)
printf("%zu ", offsetof(S, i));
printf("%zu ", offsetof(S, j));
printf("%zu ", offsetof(S, k));
printf("%zu\n", offsetof(S, l));
printf("S1 size is : %ld\n", sizeof(S1));
}
程序输出结果:
这是32位平台下运行的结果。
如果是应付面试的问题,我觉得记住3,4部分的内容就可以了,下面是我觉得有用的参考资料:
https://stackoverflow.com/questions/3318410/pragma-pack-effect
https://en.wikipedia.org/wiki/Data_structure_alignment
https://cloud.tencent.com/developer/article/1631792