看了C++编译器怎么实现异常处理1 sdssly(翻译)
没有下文,于是自己去看原文,也翻译了一部分,只是还是没有到关键部分
函数和堆栈
堆栈是一块连续的内存,用来保存函数的局部对象。更明确的说,每一个函数都有关联的栈帧(译注:stack frame,在调用函数时,进入函数以后第一句应该是push ebp,然后mov ebp,esp,所以ebp一般都指向当前函数进入时的栈顶,而且指向的内容是上一层调用函数进入时栈顶,如此向外,最后找到0,就是系统的入口,这样一个函数使用的那些栈应该就是一帧)来保存所有的函数局部对象和函数表达式产生的临时对象(译注:1.C++里对象的意义很广泛,不只是class, 结构,简单类型也是对象2.临时对象,学过编译原理的话应该很清楚,举个例子,比如有一个函数是int myfun();你在函数中这样写int ret = myfun();这样myfun()返回的结果就放到了ret里,如果你写成myfun();而不理它的返回值,你虽然不理它,但是仍然会有它返回值存放的地方,这就是一个临时的对象,在vc的调试环境了,看auto变量的页面就可以看到临时的变量)。请注意上面描述只是很典型的情况。而实际上,编译器可能会储存所有或部分的变量到寄存器里,以便获得更快的执行速度(译注:编译器优化)。堆栈是一个处理器(CPU)级就支持的概念(译注:之所以这么说,因为汇编代码里就有push和pop ).处理器提供内部的寄存器和特殊的指令来实现堆栈处理
编译器用ESP 寄存器来鉴别当前的栈帧,在上图所示的情况下,widget时正在执行的函数, EBP寄存器指向了widget的栈帧(就是函数进入时,push了ebp以后的栈顶位置)。函数访问局部变量都是用局部变量所在的位置相对于帧顶的偏移量。编译器在编译的时候就把所有的局部变量从名字变成固定的相对于帧顶的偏移,例如,widget函数访问它的一个局部变量就用ebp-24来指明它的位置
上图也显示了ESP寄存器,在图示的情况下,它指向了堆栈的最后一项,也就是处于widget帧的尾部,下一帧将从这个位置开始
处理器支持两种栈操作:push 和 pop:
pop EAX
意味着从esp所在的位置读4个字节到eax中,然后把esp增加4(32位的处理器下)。同样的,
push EBP
意味着把esp减4,然后把ebp的内容写到esp指向的地方。
当编译器编译一个函数, 它在函数头部添加一些创建和初始化函数栈帧的代码,同样,在函数结尾加上从堆栈里弹出栈帧的代码。
典型的,编译器在函数头部生成
Push EBP ; save current frame pointer on stack
Mov EBP, ESP ; Activate the new frame
Sub ESP, 10 ; Subtract. Set ESP at the end of the frame
第一句保存当前的帧指针ebp到堆栈里,第二句通过设置ebp到当前的esp来激活当前的函数帧,.第三句设置esp寄存器到当前帧的尾部,就是把esp减去本函数内的局部对象的总长度。编译器在编译的时候就知道有多少的局部对象和每个对象的长度,所以能够清楚地知道一个函数的帧的确切长度
在函数结束时把当前帧从堆栈中弹出
Mov ESP, EBP
Pop EBP ; activate caller´s frame
Ret ; return to the caller
恢复ESP和EBP,然后执行RET
当处理器执行RET指令时,实际上类似执行了一条pop eip,把栈里保存的EIP弹出,然后跳到EIP处开始执行。相反,call指令执行的时候先把当前的EIP推入堆栈,然后jmp到相应的地址,
图 3 显示了运行时堆栈的更多详细信息。如图所示,函数参数也是函数帧的一部分,调用函数者把参数推入堆栈,然后函数返回否执行
Add ESP, args_size
或者,采用另一种RET指令,如下
Ret 24
相当于返回后,执行了 ADD ESP , 24
注意没有进程里的每隔线程都有它自己的栈