.NET时代的调用约定
2. __stdcall
这个调用约定的使用也十分广泛,这也就是为什么它的名字是stdcall(standard call,标准调用)。WINAPI, CALLBACK实际上都是定义成__stdcall。Windows的大多数API函数都是采用这种调用约定。
;调用函数f2,4个参数分别是1,2,3和4
push 4 ;和f1一样
push 3 ;和f1一样
push 2 ;和f1一样
push 1 ;和f1一样
call f2 (40100Ah)
;函数f2的实现
push ebp ;和f1一样
mov ebp,esp ;和f1一样
mov eax,dword ptr [ebp+8] ;和f1一样
add eax,dword ptr [ebp+0Ch] ;和f1一样
add eax,dword ptr [ebp+10h] ;和f1一样
add eax,dword ptr [ebp+14h] ;和f1一样
pop ebp ;和f1一样
ret 10h ;函数返回,返回值是eax,并清除栈上的参数
通过比较,我们可以立刻发现__stdcall和__cdecl的反汇编有两个不同点:
a. __stdcall函数返回的时候使用了“ret 10h”,而__cdecl使用的是“ret”,这表明__stdcall函数在返回的时候就清除了4个参数(大小为10h),这个是函数实现部分来做的,而不是由调用者来做
b. 正因为函数本身已经清除了栈上的参数,调用者不需要在"call f2"之后再使用“add esp,10h”了。
可以看到__stdcall把函数返回和清除栈上函数合二为一,用一句“ret xxx”搞定,比__cdecl方便很多,那为什么不全部使用__stdcall呢?
这是因为 __stdcall有一个不足之处:它不能使用于那些可变参数个数的函数,比如printf, sprintf没有办法使用__stdcall。因为函数本身不知道每次调用时到底有几个参数,所以它无法确定ret后面的数字,这项工作只能让调用者自己去做。因此类似于printf, sprintf的函数都是使用__cdecl。注意:在VS2005中,如果给可变参数个数的函数用了__stdcall关键字,编译器不会报错,但是它实际上还是按照__cdecl调用约定进行编译,通过查看反汇编,然后和前面列出的反汇编进行比较,就会发现它用的是__cdecl。
0
相关文章