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

本文详细介绍了进程内存布局,包括栈和堆的特点及管理方式。解释了函数调用过程中的栈操作,以及 cdecl 调用惯例的具体内容。此外还探讨了 Linux 进程堆的管理方法,包括 brk 和 mmap 的使用,以及 glibc 中 malloc 函数的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、进程的内存布局


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

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

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

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

        保留区:内存中受保护而禁止访问区域,访问该处会出现段错误,通常靠近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()分配一块匿名空间。

堆分配算法

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

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

        对象池

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值