历史选择了Intel
1970年,Intel设计Intel4004 4位芯片,1972年Intel8008 8位芯片,1974年Intel8080 8位芯片。8085,8086-16位,80186,80286-32位,80386,80486,Pentium…
Intel的每一代芯片都要向后兼容原来的芯片,使得曾经的程序可以快速移植到最新的芯片中,8086将两个16位组合形成20位的地址。
要么更快,要么灭亡。
Intel80x86内存模型
在Intel 80x86架构中,段segment是内存模型设计的结果,因为各个处理器的地址空间并不一致(要保证兼容性),但是它们都被分割成64KB为单位的区域,每一个这样的区域称为一个段。内存地址行程的过程是:取得段寄存器的值,左移4位(乘16),也就是在值的左边填充4个0,把寄存器看成20位的。然后就是16位的偏移地址,表示段内部的地址。把段寄存器中经过移位的值加上偏移地址,就得到了最终的地址。
MS-DOS有一个奇怪的限制,是可用内存只有640KB,源于8086只有20位的地址,总共1MB的内存,除去系统占用,仅剩下640KB。
虚拟内存
虚拟内存通过“页”的形式组织,页就是操作系统在磁盘和内存之间移来移去或者进行保护的单位,一般为几K字节。当内存的映象在磁盘和内存中来回移动的时候,称为page in和page out.
Cache存储器
所有现代处理器都使用了Cache储存器,当数据从内存读入的时候,整行(16或者32个字节)的数据被装入了Cache。如果程序具有良好的地址引用局部性,那么CPU以后对临近的数据的引用就可以快速从Cache中读取。
Cache包括一个地址的列表以及它们的内容,随着处理器不断引用新的地址,Cache的地址列表也在不断更新。所有对内存的读写操作都要经过Cache。下图为Cache的组成。
数据段和堆
从堆中获得内存的唯一办法是使用malloc,calloc,realloc等库函数。堆中的东西都是匿名的,不能按照名字直接访问,只能使用指针访问。下图显示了堆和堆栈的关系。
内存泄漏
使用堆内存常见的问题:
- 释放或者改写仍在使用的内存(内存损坏)
- 未释放不再使用的内存(内存泄漏)
总线错误和段错误
bus error(core dumped)//总线错误
segmentation fault(core dumped)//段错误
总线错误几乎都是由于未对齐的读或写造成的,它之所以被称为总线错误,因为出现未对齐的内存访问请求时,被阻塞的组件就是地址总线。数据项不能跨越页面或者Cache边界。例如,访问一个8字节的double,地址只允许时8的整数倍,所以可以存储在24,8008,32768,但是不能存储在1006。页和Cache的大小是经过精心设计的,这样只要遵守对齐规则就可以保证一个原子数据不会跨越一个页面或者Cache的边界。
union
{
char a[10];
int i;
}u;
int *p = (int*)&(u.a[1]);
*p = 17;
//因为a+1的地址肯定未按照int对齐,我们试图向这个地址放入4个字节的数据,但是这次访问只是按照单字节的char对齐。
段错误经常是由于解除引用一个未初始化或非法值的指针,如果指针引用了一个不在你的地址空间的地址,操作系统就会进行干涉。
几个段错误的常见原因?
- 解引用一个包含非法值的指针
- 解引用一个空指针
- 在未得到正确的权限时进行访问(写只读的内存)
- 用完了堆栈或堆空间
free指针后,将指针赋值为NULL。多次free同一个指针
Reference
C专家编程