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

[读书笔记]程序员的自我修养 chp10

2016-12-04 21:07 225 查看

10.1 程序的内存布局

一个32bit 指针的寻址空间是 4GB, 其中一部分空间会被分配给内核空间, 剩余部分给用户



可以很容易看到栈是向低地址增长, 堆是向高地址增长

10.2 栈和调用惯例

10.2.1 什么是栈

经典的 i386 系统中, 栈总是向下增长的, 栈顶通过 esp 寄存器定位, 压栈操作使得栈顶的地址减少, 弹出操作使得栈顶地址增大



栈通常用来维护 一个函数调用所需要的信息, 称为 堆栈帧 或者 活动记录

函数返回地址 和 参数

临时变量

保存的上下文

在 i386 中一般通过 ebp 和 esp 寄存器划定函数范围, esp 指向栈的顶部, ebp 指向函数活动记录的一个固定位(被称为 帧指针)



一个普通函数的反汇编

int foo(){
return 123;
}




这里的第四步, 实际上是加入调试信息, 编译器一般将栈空间数据初始化成 0xCC, 对应的汉子编码就是 烫,如果编译器使用 0xCD 作为未初始化标识, 相应的汉字编码就是 屯

另外, 反汇编代码中常常可以看到 nop , 这个是纯粹作为占位符来使用的, 可以很容易的被其他函数进行替换, ie, hook 技术





10.2.2 调用惯例

C 语言默认的调用惯例是使用 cdecl 方式调用



对于下面函数

int _cdecl foo(int n, int m)


他对应的调用栈:



还有其他形式的函数调用惯例



当然 C++ 也有自己的一种特殊的调用惯例, thiscall, 专门用于类成员函数的调用

对于MSVC, this 指针存放在 ecx 寄存器中, 其他从右向左压栈

对于gcc, thiscall 和 cdecl 完全一致, 只是将this 看做是函数的第一个参数

10.2.3 函数返回值传递

当返回值小于4byte 时候, 通过 eax 返回

当返回值 5~8 byte 时候, 通过eax 和 edx 联合返回

而对于 大于 8 byte时候, 编译器采用了一个trick, 传入一个隐含的临时空间的地址, 先将返回值得数据拷贝到临时空间中去, 然后再从临时空间拷贝到调用方的地址空间中

10.3 堆与内存管理

10.3.1 什么是堆

系统中管理堆空间分配的往往是程序的运行库

10.3.2 linux 进程堆管理

linux 中采用两种方式:

brk 系统调用

实际上就是设置进程数据段的结束位置, ie, 可以扩大或者缩小数据段

mmap 调用

类似 windows 中的VirtualAlloc, 他用来向操作系统申请一段虚拟地址空间, 当不把地址空间映射到某个文件的时候, 我们又称这个空间为匿名, 可以用来作为堆空间

glibc 的malloc 对于 128 kb 的请求而言, 会在现有的堆空间里面, 按照堆空间分配算法为他分配一块空间并返回, 而对于128kb 的请求而言, 他会使用mmap 函数为他分配一段匿名空间, 然后再这段匿名空间中为用户分配空间。

mmap 申请空间的大小不能超出空闲内存+空闲交换空间的总和

10.3.3 windows 进程堆管理

windows 的进程空间比较凌乱。。。



linux 中堆是向上增长的, 但是HeapCreate 系列函数并不完全按照这个规律增长

10.3.4 堆分配算法

空闲链表

实际上就是将堆中的各个空闲块, 按照链表的方式连接起来

位图

优点:

5. 速度快

6. 稳定性好

缺点:

分配内存容易产生碎片

如果堆很大, 而设定的块很小, 会导致位图非常大, 失去cache 命中率高的优势

对象池

对于某些情况下, 被分配对象是几个比较固定的值得时候, 可以设计一个更为高效的堆算法, ie, 对象池

思路:

按照每个申请的大小将空间分成大量的小块, 每次请求的时候只要找到其中的一个小块就可以了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  读书笔记