因为书已经是很久前出版的了,所以里面的内容在现在看来有点旧了.不过基本的原理还是那样的,^_^.
另:书中的很多东西都是在UNIX写的,所以......
开始吧:
本章开始介绍了一下芯片的发展,本来我觉得没有必要说的.可是现在正在学微机原理,所以顺便说一下,也当是作为复习咯.
从1970年的4004到8008,8086(8088),80286到现在的奔腾系列.从最初的4位到现在普遍运用的32位,
芯片的速度反展无疑是很快的.因为8088在IBM机子上的成功运用,使得Intel得以打败其他对手.在业界存活下来.
8086的寻址是通过用两个16位的地址部分叠加得到20位,从而可以访问1M的地址空间.(就像书中所讲的一样.^_^)
在正式说之前,要说一下段(segment)术语.在UNIX中,段就是一块以二进制形式出现的相关内容.而在Intel 80×86内存模型中,段是内存模型设计的结果.在80*86内存模型中,各处理器的地址空间不一致(因为要保持兼容性),所以内存被划分为64K为单位的区域.每个这样的区域被称为段.一个段由段寄存器指向,然后再加上Ip的值,就得到真实的地址.^_^
在MS-DOS时代,有640k的内存限制.不过现在应该没有了,所以掠过.
现在应该说说虚拟内存了.
虚拟内存的基本思路是用廉价但缓慢的磁盘来扩充快速却昂贵的内存.在任一给定时刻,程序实际需要使用的虚拟内存区段的内容被载入物理内存中.当物理内存中的数据有一段时间未使用的时候,就将它转移到硬盘中.以节省物理空间为其他程序所用.当然,这一切都是基于局部性原理的.
虚拟内存的管理是通过MMU来进行的,并且虚拟内存以"页"的形式被组织.一页一般为几K.可以通过
/usr/ucb/pagesize查看系统中的页大小.
在cpu和物理内存之间,还有cache.正如我们大家都知道的,它的速度快.从而缓和了cpu和内存之间的速度差异.cache一般都会设计一些命中,不命中的概念,还有全写法(write-through)和写回法(write-back)来对cache进行操作.这里说一下cache的组成.
-------------------------------------------------------------------------------------------
术语 定义
-------------------------------------------------------------------------------------------
行(line) 行是对cache进行访问的单元.每行由两部分组成:一个数据部分和一个标签,用于指定 它所代表的 地址.
块(block) 一个cache行内的数据被称作块.块保存来回移动于cache行和内存之间的字节数据.一个典型 的 块为32字节.
一个cache行的内容代表特定的内存块.如果处理器试图访问术语该地址范围的内存,它 就会作出回 应,速度比访问内存快得多.
Cache 一个Cache(一般为64k到1M,or more)由许多行组成.有时也使用相关的硬件来加速对标 签的访问.cache离cpu很近,且内存系统和总线经过高度的优化,尽可能提高cache的命 中率.
-------------------------------------------------------------------------------------------
例子可以用来表示一下,cache对内存访问速度的优化.
#define DUMBCOPY for(i=0;i<65536;i++) destination[i]=source[i]
#define SMARTCOPY memcpy(destination,source,65536)
main(){
char source[65536],destination[65536];
int i,j;
for(j=0;j<100;j++)
SMARTCOPY;//DUMBCOPY
}
在书中,可能是因为作者用的机子是很早前的了.或者是我们现在用的编译器内部做了优化.我在VC6.0下运行,上面的程序在秒级上没有看出差距来.不过,作者给我们提了个醒.就是我们应该尽可能地用系统给的函数,这样可能会更好地利用系统在硬件设计的时候做的优化.还记得在看STL的东西的时候,某书的作者也提到过,要尽可能的用STL自带的东西.我想,道理应该差不多吧.
下面说说数据段和堆.
就像堆栈段能够根据需要自动增长一样,数据段也包含了一个对象用以完成这个工作,这就是堆(heap).如图示.堆区域用于动态分配的存储,用过malloc函数活得内存,并通过指针访问.堆中所有东西都是匿名的,不能按照名字直接访问,只能通过指针间接访问.也就是说,在程序中调用malloc获得的内存是从堆中分配的.当然,在系统中对堆的大小也有限制.(晕,不会传图片,书中152页)
堆分配出去了,在绝大多数情况下,都是应该回收的.即要free的.但是无序的malloc和free会产生堆碎片.
被分配的内存总是经过对齐的,以适应机器上最大尺寸的原子访问.一个malloc请求的内存大小为方便起见一般被圆整为2的乘方.回收的内存可以重新使用,但并没有(方便的)办法把它从你的进程移出交还给操作系统.
堆的末端由一个称为break的指针来标识.当堆管理器需要更多的内存时,它可以通过系统调用brk和sbrk来移动break指针.一般情况下,不必由自己显示地调用brk,如果分配的内存容量很大,brk最终会被自动调用.用于管理内存的调用是:
malloc和free------从堆中获得内存以及把内存返回给堆.
brk和sbrk---------调整数据段的大小至一个绝对值(通过某个增量).
可以通过sbrk将数据段内存返回给系统内核.
在堆的应用中,会出现两类问题.称之为:内存损坏,内存泄漏.
每次调用malloc的时候,注意用free来释放,避免内存泄漏.一种简单的方法可以用alloca来动态分配内存,它会自动地释放所分配的内存.但,它的用处也是有限的.
一般,泄漏的内存往往比忘记释放的数据结构要大.因为malloc所分配的内存往往会圆整为下一个大于声请数量的2的整数次方(如申请212B,会圆整为256B).在资源有限的情况下,即使引起内存泄漏的进程并不运行,整个系统的运行速度也会变慢.
在UNIX中检测内存泄漏,用swap来观察还有多少可用的交换空间.
/usr/sbin/swap -s
在一二分钟内键入该命令三到四次,观察可用的交换区是否减少......当然也可以用/usr/bin/*stat工具如netstat,vmstat等.
确定了由内存泄漏,就要确定是哪个进程出现的泄漏.用ps -lu.
总线错误和段错误.
总线错误几乎都是由于未对齐的读或者写引起的.之所以被称为总线错误,是因为出现未对齐的内存访问请求时,被堵塞的组件就时地址总线.对齐(alignment)的意思就是数据项只能存储在地址时数据项大小的整数倍的内存位置上.
一个会引起总线错误的小程序是:
union{
char a[10];
int j;
}u;
int *p =(int *)(u.a[1]);
*p=17; //错
因为数组和int的联合确保数组a是按照int的4字节对齐的,所以a+1的地址肯定未按int对齐.我们试图对这个地址赋予4个字节的数据,但这个访问只是按照但字节char对齐,这就违反了规则.
编译器是通过自动分配和填充数据(在内存中)来进行对齐的.
段错误是由于内存管理单元的异常所致,而该异常则通常是由于解除引用一个未初始化或非法值的指针引起的.如果指针引用一个并不位于你的地址空间的地址,操作系统变会对此进行干涉.一个会引起段错误的小程序为:
int *p=0;
*p=17; //here
通常导致段错误的几个直接原因:
.解除引用一个包含非法值的指针
.解除引用一个空指针(常由于从系统程序中返回空指针,并未经检查就使用)
.在未得到正确的权限时就进行访问.(读写权限)
.用完了堆栈或者段空间.(虚拟内存也有限啊)
一般可以认为,总线错误意味着CPU对进程引用内存的一些做法不满,而段错误则是MMU对进程引用内存的一些情况发出的抱怨.
以发生频率为序,可能导致段错误的常见编程错误是:
.坏指针值错误(在指针赋值之间就用它来引用内存,向库函数传送一个坏指针,在指针释放之后再访问它的内容)
.改写(overwrite)错误:数组越界,在动态分配的内存两端之外写入数据,改写一些堆管理数据结构
.指针释放引起的错误:释放同一内存块两次,释放一块未曾使用malloc分配的内存,释放仍在使用的内存,释放一个无效的指针.
一般使用临时变量存储,来释放链表中的元素.
C专家编程--学习笔记(2)_对内存的思考
最新推荐文章于 2018-05-21 23:10:46 发布