Delphi - 调侃调用方式
技术交流,DH讲解.
本随笔,就自己一心得+笔记,而自己水平有限,所以本文也可能是水文.哈哈
讲解前先来个表格:有条件的朋友可以看加密解密(第三版)一书,哈哈,我书还是买了一些的.
方式 | 传值方向 | 传值位置 | 谁来平衡堆栈 | 备注 |
_cdecl | 从右到左 | 直接压栈 | 调用者 | 默认C++的 |
_stdcall | 从右到左 | 直接压栈 | 函数本身 | Win32 API |
register | 从左到右 | adc寄存器然后压栈 | 函数本身 | Delphi默认的 |
pascal | 从左到右 | 直接压栈 | 函数本身 | |
safecall | 同stdcall |
function TestStdcall(a,b:Integer):Integer ;stdcall; var c:Integer; begin c:=a+b; if c>2*a then Dec(c,b) else Dec(c,a); Result:=c; end; function TestCdecl(a,b:Integer):Integer ;cdecl; var c:Integer; begin c:=a+b; if c>2*a then Dec(c,b) else Dec(c,a); Result:=c; end; function TestRegister(a,b:Integer):Integer ; var c:Integer; begin c:=a+b; if c>2*a then Dec(C, B) Else Dec(C, A); Result:= C; End; {$R *.dfm} Procedure TForm4.FormCreate(Sender: TObject); Var C: Integer; Begin C:= TestStdcall(5, 6); ShowMessage(IntToStr(C)); C:= TestCdecl(5, 6); ShowMessage(IntToStr(C)); C:= TestRegister(5, 6); ShowMessage(IntToStr(C)); End;
代码都一样,我们来看看执行时候有什么不一样的地方呢?
第一个TestStdcall:
我们看见先压6再压5,也就是从右到左压栈.函数内部:
先将栈里面的参数取出来放到ecx和edx中,最后清除栈Ret 8;
stdcall很明显了.
接下来看cdecl调用方式吧.
也是先压6再压5,但是我们看到最后面add esp,8,这个就是平衡栈了,因为我们压入了2个Integer,8个字节.
函数内部:
还是先从栈里面去参数到寄存器,最后直接ret咯.
接下来是Delphi默认的方式:
将5传入eax,6传入edx,它这里虽然现传的6,但是我们要注意寄存器的顺序应该是 eax,edx,ecx,如果还有多余的参数再压栈.
函数内部:
晕这个例子没有选好,因为参数没有用到栈,所以不存在栈平衡,我改一下...
从压栈的顺序我们再一次看出来了,是从左到右的,先压的8.
释放参数压栈用的空间,ret 8;
最后改一下,来看看Pascal调用方式,这个是Delphi1.0时候用的:
从左到右压栈的.
细心的朋友会发现这次没有讲栈里面东西取到寄存器中去,主要因为这次运算太简单了.哈哈,最后自己清除压的栈.
为什么要了解这些?
答案肯定是为了程序能够正常的工作了.这个问题...
如果声明和调用的方式不一样会怎么样?
1 走错路.如果只是从左到右和从右到左弄错了,程序能运行,只是参数a被当成参数b来用,也就是结果可能不对.
2 走上不归路.因为我们知道堆栈里面保存了函数的返回地址这些,但是如果我们调用方式不一样就可能造成堆栈被破坏了,程序无法正常返回,就会报错了.
ok,个人理解.