stdcall 和 cdecl

来源:互联网 发布:中国统计年鉴2017数据 编辑:程序博客网 时间:2024/05/01 17:54

_stdcall 是Standard Call的缩写,是C++的标准调用方式:

1) 所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针。

2) 这些堆栈中的参数由被调用的 函数在返回后清除,使用的指令是 retn X,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间。称为自动清栈。

3) 函数在编译的时候就必须确定参数个数,并且调用者必须严格的 控制参数的生成,不能多,不能少,否则返回后会出错。

 

 

int function(int a,int b)

 

int __stdcall function(int a,int b)

stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数
名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸

以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处
翻译成汇编语言将变成:

push 2 第二个参数入栈
push 1 第一个参数入栈
call function 调用参数,注意此时自动把cs:eip入栈


而对于函数自身,则可以翻译为:


push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出
时恢复
mov ebp,esp 保存堆栈指针
mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向
a
add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b
mov esp,ebp 恢复esp
pop ebp
ret 8

而在编译时,这个函数的名字被翻译成_function@8

 

 

 

 

 

cdecl 是C Declaration的缩写,表示C语言默认的函数调用方法:

1) 所有参数从右到左依次入栈

2) 这些参数由调用者清除,称为手动清栈。

3) 被调用函数无需要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。

 

int function (int a ,int b) //不加修饰就是C调用约定
int __cdecl function(int a,int b)//明确指出C调用约定


在写本文时,出乎我的意料,发现cdecl调用约定的参数压栈顺序是和stdcall是一样的
,参数首先由有向左压入堆栈。所不同的是,函数本身不清理堆栈,调用者负责清理堆
栈。由于这种变化,C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大
特色。对于前面的function函数,使用cdecl后的汇编码变成:


调用处
push 1
push 2
call function
add esp,8 注意:这里调用者在恢复堆栈
被调用函数_function处
push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出
时恢复
mov ebp,esp 保存堆栈指针
mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向
a
add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b
mov esp,ebp 恢复esp
pop ebp
ret 注意,这里没有修改堆栈


MSDN中说,该修饰自动在函数名前加前导的下划线,因此函数名在符号表中被记录为_f
unction,但是我在编译时似乎没有看到这种变化。

由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用
不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据
第一个后者后续的明确的参数确定下来,就可以使用不定参数,例如对于CRT中的sprin
tf函数,定义为:

int sprintf(char* buffer,const char* format,...)

由于所有的不定参数都可以通过format确定,因此使用不定个数的参数是没有问题的。

 

 

 

#define CALLBACK     __stdcall
     #define WINAPI       __stdcall
     #define WINAPIV      __cdecl
     #define APIENTRY     WINAPI
     #define APIPRIVATE   __stdcall
     #define PASCAL       __stdcall
     #define cdecl _cdecl
     #ifndef CDECL
     #define CDECL _cdecl
     #endif
    几乎我们写的每一个WINDOWS API函数都是__stdcall类型的

原创粉丝点击