10.1 程序的内存布局
linux进程地址空间布局
windows进程地址空间布局
10.2栈与调用惯例
栈,也被称作堆栈,就是先进先出的数据结构,FIFO。初学者请查看我的用ruby实现算法:(题外话:很奇妙的事情是,当我学了栈之后,我觉得我的学习计算机的过程就是一个栈的数据结构:读一本书时,遇到不懂的问题,记下读到的页数,就去查第二本书,读到一半又发现了不懂的问题,于是做了书签,去看第三本书。。。然后再回过头来对第三本、第二本、第一本。)
栈的实例:
堆栈帧包括如下内容:函数的返回地址和参数
临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量
保存的上下文:包括在函数调用前后需要保存不变的寄存器
栈的活动记录:
一个i386下的函数总是这样调用的:把所有或一部分参数压入栈中,如果有其他参数没有入栈,那么使用某些特定的寄存器传递
把当前指令的下一条指令的地址压入栈中。
跳转到函数体执行
i386函数体的标准开头push bp:把bp压入栈中(称为old ebp)
mov ebp, esp:这时ebp指向栈顶,而此时栈顶就是old ebp,为了调用参数方便。
可选 sub esp, XXX:在栈上分配XXX字节的临时空间
可选 push XXX:如有必要,保存名为XXX的寄存器(可重复多次)
i386函数的标准结尾:可选pop XXX:如有必要,恢复保存过的寄存器(可重复多次)
mov esp,ebp:恢复esp同时回收局部变量空间
pop ebp:恢复保存的ebp值
ret 从栈中取得返回地址,并跳转到该位置
调用惯例包括函数参数的传递顺序和方式、栈的维护方式、名字修饰策略。c语言默认的调用惯例是cdecl。
函数返回值传递(32位的情况,64位的可以类比)4个字节:用eax寄存器
5-8个字节:用eax和edx寄存器
高于8个字节:调用方在栈上开辟一片空间,将这块空间的一部分作为传递返回值的临时对象,我们记作temp。将temp的地址作为隐藏参数传递进函数。函数将数据拷贝给temp,并将temp地址通过eax返回调用方。调用方根据eax的temp地址从临时对象区获得返回数据。
10.3堆与内存管理
堆的管理采用“批发”和“零售”结合的方法。系统调用的性能开销很大,因此不能每次都向系统申请内存。比较好的做法是程序向操作系统申请一块适当大小的堆空间,然后由程序自己管理这块空间。当需要的堆较小时,程序使用这部分内存。当需要的堆较大或已经申请的堆用完时,再由程序向系统进行申请。
Linux进程堆管理
Linux提供了两个堆分配的系统调用:brk和mmap
brk的作用实际上时设置进程数据段的结束地址。
mmap的作用与windows系统下的VirtualAlloc很相似,它的作用是向操作系统申请一段虚拟地址空间,当然这块虚拟地址空间可以映射到某个文件,当它不将地址空间映射到某个文件时,我们又称这块儿空间为匿名空间,匿名空间就可以拿来作为堆空间。
Linux标准库的malloc这样处理用户的空间请求:对于小于128kb的请求,它会在现在有的堆空间里,按照分配算法返回一块空间,对于大于128kb的请求,它会调用mmap系统调用分配一块匿名空间。
windows进程堆管理
windows提供了一个API叫做VirtualAlloc,用来向系统申请空间。
windows还提供了堆管理器,堆管理器提供了一套与堆相关的API用来创建、分配、释放和销毁堆空间。HeapCreate,HeapAlloc,HeapFree,HeapDestroy。
堆分配算法:
空闲链表法、位图法、对象池法。