概述
结构体的内存布局
为了提高内存访问效率,降低处理器从内存读取数据的开支,各种数据类型的对象并不是连续存放在内存中的任意起始地址上的,而是尽可能的对齐到机器字长上面。编译器出于优化的考虑,会给各种数据类型的变量在内存的存储方式施加限制,我们可以从两个方面来考虑这个问题,一个是对齐(alignment),另一个就是间距(padding)。
1. 对齐
各个变量在内存中的起始地址能够被变量所属类型的宽度所整除。比如说占据2个字节的 short
类型的地址应该是偶数,占据4个字节的 int
和 float
应该放到能被4整除的起始地址上,占据8个字节的 double
和 long
应该放置在能够被8整除的起始地址上(注意,数据类型的宽度在 C/C++ 中并没有被严格定义,通常来说32位应用程序中 int
和 long
都是4个字节的,64位应用程序中 int
为4字节,long
是8字节,某些编译器可能会有与此不同的其他实现)。而 char
类型由于只有一个字节,所以他存放在哪里都可以。
指针变量同样是要对齐的,由于指针变量的宽度和地址总线的宽度一致,因此32位应用程序中指针变量总是对齐到能被4整除的字节上,而64位应用程序中指针变量则存放到可以被8整除的起始地址处。
2. 间距
为了保证各种数据类型的变量都很好地对齐到机器字长上,变量间的间距是另一个简单有效的手段。连续定义的变量在内存中可能并不连续,他们之间会存在一个不被使用的间距(由于两个变量都要遵循前面说的对齐规则,他们之间自然而然就会有间距)。比如:
int *p = NULL;
int n = 0;
double f = 0.0;
首先最前面的指针变量p毫无疑问占据一个字长(4字节或者8字节),后面的 int
类型的变量 n 的开始于一个新的字,其首地址必然可以被4整除。紧随其后定义的 double
类型变量 f 的起始地址则应该被8整除,因此他们之间就会出现一个4字节的间距,实际的存放方式类似于下面这样:
int *p = NULL;
int n = 0;
char padding[4] = {0};
double f = 0.0;
有些情况下,变量定义的顺序也会影响他们之间的内存间距:
char c = 0;
int n = 0;
double f = 0.0;
因为字符类型的 c 可以存放在任意位置,因此他和后面的 int
类型变量之间的间距无法判断,从最小的0到最大的7字节都有可能,这种不确定性也导致 n 和 f 之间的填充大小不能判断,他们可能是0也可能是4字节。同样是定义三个变量,换一个顺序对内存的占用就会完全不同:
double f = 0.0;
int n = 0;
char c = 0;
此时第一个 double
类型的变量首地址应该被8整除,然后它本身占据8个字节。紧随其后的 int
类型的变量n对齐条件自然满足,占据4个字节。最后字符变量c占据一个字节,不需要遵循对齐条件,所以会紧挨着变量n存放。因此上面三个变量共占据13字节的内存空间。
3. 结构体的内存布局
结构体的内存布局也是以此为基础的,只不过结构体类型的变量本身也要受限于内存对齐的要求,他的起始地址应该能够被其中宽度最长的类型的宽度所整除。总结一下,可以得到下面几个规律:
- 结构体变量的首地址可以被该结构体中宽度最长的类型的字节宽度所整除,结构体变量的首地址就是其第一个成员变量的地址。
- 每一个结构体成员都要受到对齐规则的限制,对齐宽度取该成员变量的宽度和默认对齐宽度中的较小值,对于32位应用程序默认对齐宽度是4字节,对于64位应用程序对齐宽度是8字节。
- 结构体变量所占据的总字节数应该可以被最长类型宽度所整除,相当于在结构体的末尾填充一定数量的字节。
比如说下面这个例子:
struct s1
{
char c; // 1
//char padding[7]
double i; // 8
};
struct s2
{
char c1; // 1
//char padding[7]
struct s1 s; // 16
char c2; // 1
//char padding[7]
double c3; // 8
};
s1
结构体包含一个字符类型和一个双精度浮点类型,其中最长类型宽度为8字节,因此结构体的起始地址应该被8整除,第一个成员 c 占据1字节,后面填充7个字节,然后存放第二个成员 i ,占据8个字节。结构体的总大小是16个字节,可以被8整除不必再做填充。
s2
结构体第一个成员 c1 占据1个字节,后面填充7个字节,然后存放占据16个字节的成员结构体s,接下来存放占据一个字节的变量 c2,然后填充7字节,最后存放占据8个字节的 c3 变量。把他们加在一起可知总长度是40字节,可以被8整除,末尾不必填充。
实际上如果理解了上面所说对齐规则的最终目的,并没有必要去刻意记忆这些规则也可以得到结构体变量在内存中的正确布局。
4. 修改默认对齐宽度
可以通过预处理指令 #pragma pack(n)
来修改默认对齐宽度,其中n可以等于1,2,4,8,16。
在visual studio 中,通过项目属性中的 C/C++ 选项,在里面的代码生成一栏下面有结构成员对齐这一选项,可以修改这个来设置整个项目的默认对齐参数。不过通常来说没必要修改这个选项。
在某些特殊场合,比如解析具有特定文件头二进制文件时,可以通过设置 #pragma pack(1)
来强制结构体中各个成员变量连续存放。
最后
以上就是俊逸面包为你收集整理的结构体的内存布局的全部内容,希望文章能够帮你解决结构体的内存布局所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复