关于内存对齐

简介: 关于内存对齐

关于内存对齐

计算机内存是以字节(Byte)为单位划分的,理论上 CPU 可以访问任意编号的字节,但实际情况并非如此。

对于一个数据总线宽度为32位的CPU,实际寻址的步长为4个字节,也就是只对编号为 4 的倍数的内存寻址,例如0、4、8、12、1000等,而不会对编号为 1、3、11、1001 的内存寻址。(64位的处理器也是这个道理,每次读取8个字节)。

32位CPU寻址为例:

这样做可以以最快的速度寻址:不遗漏一个字节,也不重复对一个字节寻址。对于程序来说,一个变量最好位于一个寻址步长的范围内,这样一次就可以读取到变量的值;如果跨步长存储,就需要读取两次,然后再拼接数据,效率显然降低了。

如下图:

当数据在 2-5 中,32位CPU 在读取时实际上是先读取 0-3 ,然后再读取 4-7 字节,再将两次获得的数据进行合并,最后获得所需的四字节数据。再比如一个 int 类型的数据,如果地址为 8,那么很好办,对编号为 8 的内存寻址一次就可以。将一个数据尽量放在一个步长之内,避免跨步长存储,这称为内存对齐。

结构体内存对齐

为了提高存取效率,编译器会自动进行内存对齐,请看下面的代码:

#include <stdio.h>
#include <stdlib.h>
struct{
    int a;
    char b;
    int c;
}t={ 10, 'C', 20 };
int main(){
    printf("length: %d\n", sizeof(t));
    printf("&a: %X\n&b: %X\n&c: %X\n", &t.a, &t.b, &t.c);
    system("pause");
    return 0;
}

在32位编译模式下的运行结果:

length: 12
&a: B69030
&b: B69034
&c: B69038

如果不考虑内存对齐,结构体变量 t 所占内存应该为 4+1+4 = 9 个字节。考虑到内存对齐,虽然成员 b 只占用 1 个字节,但它所在的寻址步长内还剩下 3 个字节的空间,放不下一个 int 型的变量了,所以要把成员 c 放到下一个寻址步长。剩下的这 3 个字节,作为内存填充浪费掉了。

编译器之所以要内存对齐,是为了更加高效的存取成员 c,而代价就是浪费了3个字节的空间。

通过上面例子可以得出:结构体变量的起始地址需要让自身变量宽度整除,如果不能就需要往前面填充字节,而在计算结构体大小时还有一个规则,结构的总大小必须可以被最宽成员的大小整除,如果不能则在后面补充字节

全局变量内存对齐

除了结构体,变量也会进行内存对齐,请看下面的代码:

#include <stdio.h>
#include <stdlib.h>
int m;
char c;
int n;
int main(){
    printf("&m: %X\n&c: %X\n&n: %X\n", &m, &c, &n);
    system("pause");
    return 0;
}

运行结果:

&m: DE3384
&c: DE338C
&n: DE3388

字节对齐: 一个变量占用 n 个字节,则该变量的起始地址必须是 n 的整数倍,即:起始存放地址 % n = 0,如果不够则补齐字节

可见它们的地址都是4的整数倍并相互挨着。内存对齐虽然和硬件有关,但是决定对齐方式的是编译器,如果你的硬件是64位的,却以32位的方式编译,那么还是会按照4个字节对齐

对齐方式可以通过编译器参数修改。

设置对齐系数

在内存要求很高的时候,我们可以放弃空间换时间,改成时间换空间,我们自定义对齐,而不全部按照编译器默认对齐方式,通过 #pragma pack(n) 来改变结构体成员的对齐方式,

n 可以定义为 1、2、4、8、16

我们看例子:

// 我们通过切换系数查看不同占用大小
#pragma pack(1)
struct Struct1
{
    char a;//1byte
    int b;//4byte
    char c;//1byte
} t;
#pragma  pack()
int main(){
    printf("length: %d\n", sizeof(t));
    printf("&a: %X\n&b: %X\n&c: %X\n", &t.a, &t.b, &t.c);
    return 0;
}

输出:

length: 6
&a: 407970
&b: 407971
&c: 407975

从上图可以看出,系数改变后,内存占用大小确实发生了变化吗,而规则主要是:变量宽度与对齐系数进行比较,谁小使用哪个进行对齐。例如当 pack(1) 时, a 占用 1个byteb 需要 4个byte,但与系数比较是大于系数的,所以按照 1个byte 进行对齐,则紧挨着 a 的内存, c 也是同理

系数N = Min(最大成员宽度,对齐系数),当然结构体整体大小还得是N的整数倍,不是整数倍需要补齐字节

总结

读完这篇文章可以知道

  • 内存对齐是一种空间换时间的策略
  • 结构体计算内存大小有以下规则:
  • 结构体变量的起始地址能够被其最宽的成员大小整除
  • 结构体每个成员相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个成员后面补充字节
  • 结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节
  • 在内存紧缺的时候还可以手动设置对齐系数,转换策略

实际上字节对齐并非真正表示这个变量实际内存大小就是对齐后的大小,变量真实大小并未改变,对齐是编译器处理的,想了解更多可以细看一下编译原理。

推荐阅读

目录
相关文章
|
6月前
|
存储 编译器 C语言
结构体的内存对齐与位段
当我们描述一个人的年龄时我们可以使用,int age = 18;但是如果我们要描述一个人呢?很显然我们无法仅靠一个age就实现对一个人的描述,所以就有了结构体,在结构体中我们可以包含多种类型的数据,这样就可以实现对一个人的描述比如身高、爱好、体重等等
|
编译器 Linux C++
C/C++中内存对齐的问题的讲解
C/C++中内存对齐的问题的讲解
160 0
|
编译器 C++
结构体内存对齐问题
结构体重点😃 1.结构体内存对齐问题,是在计算结构体的大小时,对结构体成员在内存中的位置进行研究的问题。
|
编译器 C++
【关于结构体内存对齐问题】(上)
【关于结构体内存对齐问题】
116 0
|
编译器 Linux C语言
结构体的内存对齐与位段的实现
注意上面这两种结构体都是属于匿名结构体类型,不告诉你名字,这种结构体类型如果要使用必须在声明的时候就在后面定义变量,不能再到主函数里面引用,因为你不知道这个结构体的名字是什么,所以必须在声明的时候就定义变量。
89 0
|
存储 编译器 C++
【C/C++】结构体&内存对齐
【C/C++】结构体&内存对齐
184 0
|
编译器 C++
什么是结构体内存对齐,位段
什么是结构体内存对齐,位段
109 0
什么是结构体内存对齐,位段
|
编译器 C++
结构体内存对齐,位段
结构体内存对齐,位段
103 0
结构体内存对齐,位段
|
编译器 Linux C++
【C++】C&C++结构体内存对齐
【C++】C&C++结构体内存对齐
【C++】C&C++结构体内存对齐