内存对齐,提高寻址效率

内存对齐,提高寻址效率

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

CPU通过地址总线来访问内存,一次能处理几个字节的数据,就命令地址总线读取几个字节的数据。例如,32位的CPU一次能处理4个字节的数据,那么每次就从内存读取4个字节的数据。少了浪费主频,多了也没有用。64位的处理器也是这个道理,每次读取8个字节。

以32位CPU位例,实际寻址的步长为4个字节,也就是只对编号为4的倍数内存寻址,例如0、4、8、12、1000等,而不会对编号1、3、11、1001的内存寻址。如下图:

在这里插入图片描述

这样做可以以最快的速度寻址,不遗漏一个字节,也不重复对一个字节寻址。

对于程序来说,一个变量最好位于一个寻址步长的范围内,这样就可以一次性读取到变量的值;如果跨步长存储,就需要读取两次,然后再拼接数据,效率显然降低了。

例如一个int类型的数据,如果地址为8,那么只需要对编号为8的内存寻址一次就可以。如果编号为10,就比较麻烦,CPU需要先对编号为8的内存寻址,读取4个字节,得到该数据的前半部分,然后再对编号为12的内存寻址,读取四个字节,得到该数据的后半部分,再将两个部分的数据拼接起来,才能取得数据的值。

将一个数据尽量放在一个步长之内,避免跨步长存储,这称为内存对齐。在32位编译模式下,默认以4字节对齐;在64位编译模式下,默认以8字节对齐。

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

#include <stdio.h>
#include <stdlib.h>

struct{
    int a;
    char b;
    int c;
}t={ 10, 'C', 20};

int main(){
    printf("Length: %d\h",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>

struct{
    int a;
    char b;
    char d;
    int c;
}t={ 10, 'C', 20};

int main(){
    printf("Length: %d\h",sizeof(t));
    printf("&a: %X\n&b: %X\n&d: %X\n&c: %X\n",&t.a, &t.b,&t.d,&t.c)
    
    system("pause");
    
    return 0;
}

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

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

可以看到总长度还是12,由于d变量与b变量的长度加起来没有超过一个步长,所以他们被放在了一个步长内,并且寻址的起点都是一样的。

除了结构体,变量也会进行内存对齐,如下:

#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;
}

VS运行结果:

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

可见他们的地址都是4的整数倍,并相互挨着。

经过测试,对于全局变量,GCC在Debug和Release模式下都会进行内存对齐,而VS只有在Release模式下才会进行对齐。而对于局部变量,GCC和VS都不会进行内存对齐,无论是Debug还是Release模式。

改变对齐方式

内存对齐虽然和硬件有关,但是决定对齐方式的是编译器,如果你的硬件是64位的,却以32位方式编译,那么还是会按照4个字节对齐。

对齐方式可以通过编译器参数修改,以VS2010为例,更改对齐方式的步骤为:项目 --> 属性 --> C/C++ -->代码生成 --> 结构成员对齐,如图:

在这里插入图片描述

最后需要说明的是:内存对齐不是某某语言(C、C++等)的特性,他属于计算机运行原理,Java、Python等其他编程语言同样也会有内存对齐问题。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
内存工作原理 1.内存寻址 首先,内存从CPU获得查找某个数据的指令,然后再找出存取资料的位置时(这个动作称为“寻址”),它先定出横坐标(也就是“列地址”)再定出纵坐标(也就是“行地址”),这就好像在地图上画个十字标记一样,非常准确地定出这个地方。对于电脑系统而言,找出这个地方时还必须确定是否位置正确,因此电脑还必须判读该地址的信号,横坐标有横坐标的信号(也就是RAS信号,Row Address Strobe)纵坐标有纵坐标的信号(也就是CAS信号,Column Address Strobe),最后再进行读或写的动作。因此,内存在读写时至少必须有五个步骤:分别是画个十字(内有定地址两个操作以及判读地址两个信号,共四个操作)以及或读或写的操作,才能完成内存的存取操作。 2.内存传输 为了储存资料,或者是从内存内部读取资料,CPU都会为这些读取或写入的资料编上地址(也就是我们所说的十字寻址方式),这个时候,CPU会通过地址总线(Address Bus)将地址送到内存,然后数据总线(Data Bus)就会把对应的正确数据送往微处理器,传回去给CPU使用。 3.存取时间 所谓存取时间,指的是CPU读或写内存内资料的过程时间,也称为总线循环(bus cycle)。以读取为例,从CPU发出指令给内存时,便会要求内存取用特定地址的特定资料,内存响应CPU后便会将CPU所需要的资料送给CPU,一直到CPU收到数据为止,便成为一个读取的流程。因此,这整个过程简单地说便是CPU给出读取指令,内存回复指令,并丢出资料给CPU的过程。我们常说的6ns(纳秒,秒-9)就是指上述的过程所花费的时间,而ns便是计算运算过程的时间单位。我们平时习惯用存取时间的倒数来表示速度,比如6ns的内存实际频率为1/6ns=166MHz(如果是DDR就标DDR333,DDR2就标DDR2 667)。 4.内存延迟 内存的延迟时间(也就是所谓的潜伏期,从FSB到DRAM)等于下列时间的综合:FSB同主板芯片组之间的延迟时间(±1个时钟周期),芯片组同DRAM之间的延迟时间(±1个时钟周期),RAS到CAS延迟时间:RAS(2-3个时钟周期,用于决定正确的行地址),CAS延迟时间 (2-3时钟周期,用于决定正确的列地址),另外还需要1个时钟周期来传送数据,数据从DRAM输出缓存通过芯片组到CPU的延迟时间(±2个时钟周期)。一般的说明内存延迟涉及四个参数CAS(Column Address Strobe 行地址控制器)延迟,RAS(Row Address Strobe列地址控制器)-to-CAS延迟,RAS Precharge(RAS预冲电压)延迟,Act-to-Precharge(相对于时钟下沿的数据读取时间)延迟。其中CAS延迟比较重要,它反映了内存从接受指令到完成传输结果的过程中的延迟。大家平时见到的数据3—3—3—6中,第一参数就是CAS延迟(CL=3)。当然,延迟越小速度越快。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JayerZhou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值