您的位置:首页 > 职场人生

《程序员的自我修养》读书笔记5 -- 内存

2014-11-08 15:46 134 查看
一、进程的内存布局

一般来讲进程有如下默认区域:

栈:用于维护函数调用上下文,通常从用户空间高地址开始向低地址增长;

堆:进程动态分配内存区域,一直存在直到被手动回收或者进程结束;

可执行文件和共享对象镜像:

保留区:内存中受保护而禁止访问区域,访问该处会出现段错误,通常靠近0x0处。比如int *p = NULL就是无效指针。



其中栈和堆是我们主要需要分析的部分。

二、栈和惯例

栈的布局如下图。





函数调用过程:

调用方需要做的事情:

(1)将参数压入栈 (2)将当前指令下一条指令地址压入栈 (3)跳转到被调函数体; 其中(2)(3)两步通过call函数一起执行。

被调函数首先需要做的事情:

push %ebp 将old ebp压入栈中
mov %esp %ebp 将新的ebp指向栈顶
sub %esp XXX 分配新的临时栈空间
push XXX 将一些需要保存寄存器压栈

被调函数结束时则进行相反的过程。

调用惯例:

函数调用方和被调方共同遵守的约定,使函数能够正确被调用。主要有以下几方面内容:

函数参数的传递顺序:即多个参数的压栈顺序
栈的维护方式,比如函数调用结束,由谁来弹出栈中函数参数
名字修饰策略:调用惯例需要对函数本身名字进行修饰

cdecl是C语言中默认调用惯例,内容如下:



函数返回值参数传递:

函数可以将返回值存储在寄存器中。对于返回值5~8字节情况,一般通过eax和edx联合返回方式进行。

如果是返回对象很长的情况。。。

(1)调用方使用临时栈上空间作为中转,并将此区域首指针作为隐含参数传递给被调方;

(2)被调方根据该隐含参数,将返回对象拷贝给中转区域;

(3)调用方将中转区域拷贝给最终对象。

三、堆和内存管理

Linux进程堆管理

Linux提供两种堆空间分配方式:

int brk(void *end_data_segment) 实际作用是扩大或缩小数据段,若我们将数据段结束地址向高端地址移动,则扩大的空间将作为堆空间,这是最常用的做法之一。
void mmap(*start, length, prot,flags,...) 指定需要申请的地址大小和长度,都是页的整数倍。

glibc中malloc()函数是处理用户空间请求:小于128k请求,在现有堆空间里,按照堆分配算法分配空闲块;对于大于128k请求,直接通过mmap()分配一块匿名空间。

堆分配算法

如何管理一大块连续的内存空间,能够按照需求分配,释放其中的空间。
空闲链表法:将整个堆空间中,空闲的块通过链表方式链接,用户请求空间时,遍历链表找到合适空闲块,并对块进行拆分(用户请求部分和剩余空闲部分),并更新链表里空闲块。用户释放时将他合并至空闲链表中。

位图:将整个堆划分成大量的块,每个块大小相同。用位图记录所有块的使用情况。

对象池
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: