内存布局
linux下一个进程的典型内存分布:
函数与栈
栈保存一个函数调用所需要的维护信息,这常常被称为堆栈帧 或 活动记录.
保存如下内容:
1. 函数的放回地址和参数
2. 临时变量: 包括函数的非静态局部变量 以及 比一起自动生成的其他临时变量.
3. 保存的上下文: 包括在函数调用前后需要保持不变的寄存器.
一个常见的活动记录如下:
i386函数体的"标准"开头是这样的:
1. push ebp: 把ebp压入栈中
2. mov ebp, esp: ebp = esp (这时ebp指向栈顶, 而此时栈顶就是old ebp)
3. [可选]sub esp, XXX: 在栈上分配XXX字节的临时空间
4. [可选]push XXX: 如有必要,保存名为XXX的几个寄存器
那么"标准"结尾是这样的:
1. [可选]pop XXX: 如有必要, 恢复寄存器
2. mov esp, ebp: 恢复esp同时回收局部变量空间
3. pop: 从栈中恢复保存的ebp的值.
4. ret: 从栈中取得返回地址, 并跳转到该位置
反汇编一个函数:
int foo()
{
return 123;
}
如下:
补充:
函数的调用:
call 0x0EA23h
将ECS或EIP压栈;
转移到0x0EA23h。
ret函数的返回:
ret
弹栈得到EIP或ECS;
返回EIP所指的指令继续执行。
函数返回值传递方式
用伪代码表示如下:
步骤如下:
1. 首先main函数在栈上额外开辟了一片空间, 并将这块空间的一部分作为传递返回值的临时对象,temp.
2. 将temp对象的地址作为隐藏参数传递给return_test函数
3. return_test函数将数据拷贝给temp对象, 并将temp对象的地址用eax传出.
4. return_test返回之后, main函数将eax指向的temp对象的内容拷贝给n.
当c++对象返回时,
结果:
当将函数改成这样时, C++进行了返回值优化, 直接将对象构造在传出时使用的临时对象上, 结果如下:
堆
在windows中,利用VirtualAlloc取得的虚拟空间,类似于向操作系统"批发"空间.
而用HeapAlloc或者是malloc分配的空间实际上是堆空间的"零售".
这是因为如果内存管理由操作系统内核来进行,系统调用时的性能开销较大.而如果由程序自己管理时,有更高的效率.
堆分配算法有:
1. 空闲链表
2. 头/主体/空闲 位图
3. 对象池
在实际系统中, 是利用多种方法共同分配堆空间的.