- Assembly code
说实话这两天一直在搞函数调用时的栈帧结构问题,也看了不少资料对于这个问题总算有些理解了11: int func(int x,int y)12: {004015E0 push ebp // 保存调用者函数的栈帧底部地址 esp = esp-4004015E1 mov ebp,esp // 然后新的栈帧开始,起始位置就是esp,004015E3 sub esp,44h // 预留空间 esp = esp-44004015E6 push ebx // 连续压入三个寄存器,我就不大理解了,每个函数里都有这种固定格式004015E7 push esi // 变址寄存器 它们主要用于存放存储单元在段内的偏移量,用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。 没看出方便在哪里...004015E8 push edi // 变址寄存器004015E9 lea edi,[ebp-44h] // 保存这个有什么用?004015EC mov ecx,11h004015F1 mov eax,0CCCCCCCCh004015F6 rep stos dword ptr [edi] // 这句更猫腻了,看不懂13: int z(4);004015F8 mov dword ptr [ebp-4],4 // 将4 放入 ebp-4的地址中14: return 6;004015FF mov eax,6 //15: }
还有,昨天和别人讨论的时候,有人说将cout << &z << endl;放在int z(4);下面会影响栈使z的地址发生改变,我觉得根本不可能改变,在调用cout << &z << endl;之前z的地址都已经确定了如何改变,我觉得就算在z前面调用函数也不会发生变化,因为函数的调用都会新开辟栈帧完了之后又清空了回到了下一条指令地址
打个比较傻的比方:
就像一根面条,每次拉长的时候做个记号,然后又缩短
遇到函数调用时,首先保存调用者的栈帧底部地址,做记号开始拉长
退出函数调用时,缩回原来的长度,执行下一条指令
- Assembly code
|-------------| 22ff30| 3 | push 3 esp-4| 2 | push 2 esp-4| 返回地址 | push re-addr esp-4| EBP | push EBP esp-4 <<------这就记号---------------- <<------- mov %esp,%ebp --->> 新栈帧开始,新栈帧总是从原栈帧顶部位置开始,调用结束后就清栈,结果又变为调用函数的栈帧结构了| z | <<------- movl $0x4,-0x4(%ebp)恰好得到22ff1c| | |-------------| <<------- sub $0x10,%esp --->> ESP 22ff10
我是搞c++的,很多人劝我说搞这个没意义,可我觉得很有意义,C++ISO标准那是公式,我觉得一个职业的C++程序员
不应该只是会用标准,要理解,要深层次理解,有本书叫深度探索C++对象模型,探讨对象模型如果不清楚堆栈模型我觉得那就是瞎扯,而这些问题在汇编层次应该是很简单的东西,掌握这些来更好的理解C++底层机制岂不是事半功倍吗?
------解决方案--------------------------------------------------------
不是太明白究竟什么问题。
第一段代码里,压栈的三个寄存器是由于函数对通用寄存器使用上有个约定,即要保证这三个寄存器的不变。另外的 edx:eax 通常作为返回值,所以可能会被改动;ebp/esp 就是堆栈和局部变量参数方面的应用了。所以,在函数的入口便会有对这三个寄存器的压栈保存操作,函数末尾相应地有出栈恢复的指令。出于代码通用性的考虑,即便函数里没有使用到这三个寄存器,或仅仅是部分使用到,也会对它们进行这一操作。
该堆栈空出额外的空间,并全部填入 0CCCCCCCCh ,这应该是防止堆栈溢出的个措施吧。单字节的 0cc 是 int3h 指令,这样可以在执行流程误入栈里时,也有想当的可能被侦测到。
你这里,语句序列的变化,除非是对局部数据的定义顺序上有变化,否则是不会影响到变量的地址的。
------解决方案--------------------------------------------------------