技术交流,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,个人理解.
|
请发表评论