内存的编址与寻址
首先什么是内存?要理解内存首先要了解内存有两个视图,一个是逻辑视图,一个是物理视图,对应地址空间就是我们常说的逻辑地址空间和物理地址空间,如果内存在管理上采用的是虚拟内存扩展技术,那这时候把逻辑地址空间也叫虚拟地址空间,两者还是有差别的(差别不属于本文要说的内容,感兴趣的可自行查看如x86实模式的逻辑地址和物理地址间的关系,实模式就没用虚拟内存机制,那个年代没出这种高科技),很多地方把两者等同了,主要是现在的操作系统大都采用的是虚拟内存扩展技术。
在采用虚拟内存管理机制的操作系统控制下的计算机中,CPU地址线出来的地址都是虚拟地址,操作系统要经过MMU(Memory Manage Unit)内存管理单元对虚拟地址进行转换,才能得到真实的物理地址(连接到物理内存上的),站在CPU指令级层面,计算的操作数或指令的内存有效地址EA此时都是虚拟地址,这个地址空间就是下面我们要讨论的程序视角内存。

1、 什么是内存:
这里说的内存是从程序角度看,内存是按地址访问的一维线性空间。

这个一维存储空间是对物理内存的抽象描述,怎么理解这里的抽象呢,就是从程序角度屏蔽了物理内存不同的存储字宽、地址线信息、甚至存取周期等差异,在程序端,就是按地址访问存储单元。
内存由大量的存储单元组成,每个存储单元有一个唯一的编号,这个编号就是我们说的该存储单元的地址,用该地址可以唯一地访问到一个存储单元(重要的事情来回多说几遍)。
每个存储单元可以存放多个二进制位(bit),一般是字节(Byte)的整数倍,在计算机里定义1字节 = 8位。
接下来就是按多大的存储单元来设置地址了,引出了第二个概念----编址方式。
2、 什么是编址方式?
编址方式就是按照多大的存储单元分割内存。目前常用的编址方式有字节编址和字编址,大多数计算机采用的是字节编址。
字节编址:一个内存地址对应一个字节信息;
字编址(字指机器字长):一个内存地址对应一个字信息;
还是用例子说名下:假设一个字=32bits(位) =4Byte(字节)
下图是两者的对照图示:

为了让大家理解两者的区别,我们再看下在同样的存储容量下,不同的编址方式对物理地址寻址范围的要求和相应地址线的影响。

地址分配问题解决了,接下来我们讨论多字节数据在内存中怎么存放的问题,从而引出了字节顺序的概念。
3、 什么是字节顺序?
在字节编址的大背景下,一个多字节的数据在内存中怎么存放呢?目前常用的有两种存放方式:
大端(Big endian):低有效字节存放在高地址;
小端(Little endian):低有效字节存放在低地址。
通过例子看区别吧,如一个4字节数据:01234567H(H代表十六进制数,也可表示为0x01234567)

如果是在一些调试环境里看就是下图的情况:

小端字节顺序看到的和数据本身顺序是反的,这是为什么后来的精简指令集(RISC)很多都用大端字节顺序的原因,大端字节顺序更符合人类阅读习惯,但是采用小端字节顺序更利于硬件处理,不同长度数据的低位位置(地址)是相同的,在进行类型扩展时方便处理。采用小端的有x86处理器,采用大端字节顺序的有MIPS处理器,ARM处理器是可配置的,也叫双端口字节顺序。以整数7为例,它以1个字节、2个字节、4个字节在小端和大端存储时的内存图示:

由于存在两种不同字节顺序,不同处理器上使用的数据就不能拿来就用了,机器指令级程序更不存在拿来主义了(0/1指令编码格式各不相同)。
内存的编址和字节存放顺序确定下来以后,还有另外一个问题,就是对齐方式:
1、 什么是对齐方式?也叫边界对齐、字节对齐。
其定义:边界对齐是信息宽度不超过主存宽度的信息必须存放在一个存储字内,不能跨边界。(说了好像跟没说一样)
其实就是:
字节信息的起始地址为:×…××××(x均为二进制,任意位置)
半字信息的起始地址为:×…×××0(2B对齐)
单字信息的起始地址为:×…××00(4B对齐)
双字信息的起始地址为:×…×000(8B对齐)
还是用图说话吧:

为什么会采用边界对齐呢?看上图,其实就是计算机里面“用空间换时间”的策略实例,尤其在现在存储空间绰绰有余的环境下,编译器默认都是采用边界对齐的(及右边方式),不同的系统对不同类型的数据采用默认对齐方式还不一样,例如C语言中的double类型在Linux系统下是4B对齐,在windows下是8B对齐。那为什么采用边界对齐后就能更快获取到数据?如上图所示,对于双字类型(图中黄色背景区),如果采用非对齐方式(左图),空间是节省了,但是访存至少要两次,之后还要对数据进行拼接,如果采用对齐方式(右图),虽然浪费了空间,但是一个访存周期就可以得到该双字数据,时间节省下来了。
知道了编制方式、字节顺序和边界对齐,接下来就是寻址了,寻址和上面几点均相关。
5、 什么是寻址?
这里说的寻址不是指令集设计里的什么立即数寻址、寄存器寻址、直接寻址、间接寻址等等,这些寻址方式是解释怎么由指令封装中的形式地址得到内存有效地址(EA)的方法。而本文说的寻址是强调通过给定地址怎么得到相应数据,,更准确说是信息,因为程序和数据在内存中是没区别的,都是0、1信息。
还是用例子说话吧,如下图:

通过这个例子,能看到,信息获取和字的大小、编址方式、字节顺序、对齐方式都相关。
如果是4B数据,又要求边界对齐(假设align是4),那么该数据在图示的几个地址中只能从0x0C开始存放。
了解上面之后,体会C语言中指针变量的类型的实际意义。
尝试运行下面的C语言程序,并能解释原因:
----------------------------------
#include <stdio.h>
char buff[]={3,2,1,0,131};
int a, b;
int main(){
char *p = buff;
a = *(int *)p;
b = *(int *)(p+1);
printf("a = 0x%x, b = 0x%x\n", a, b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
----------------------------------------

以上都是站在程序视角看的内存空间,另外一个视角就是从物理内存角度看内存,这里包括内存的位扩展、字扩展和并行存储等概念,这些是计组中存储器设计部分相关的内容,不属于本文设计内容,感兴趣可查阅相应资料。