__stdcall的含义和用法是什么?

veh*_*zzz 70 c++ windows calling-convention

__stdcall这些天我经历过很多次.

MSDN没有非常清楚地解释它的真正含义,何时以及为什么要使用它,如果有的话.

如果有人提供解释,我会很感激,最好是一两个例子.

小智 63

传统上,C函数调用是在调用者将一些参数推入堆栈,调用函数,然后弹出堆栈以清理那些推送的参数的情况下进行的.

/* example of __cdecl */
push arg1
push arg2
push arg3
call function
add sp,12 // effectively "pop; pop; pop"
Run Code Online (Sandbox Code Playgroud)

注意:默认约定 - 如上所示 - 称为__cdecl.

另一个最受欢迎的约定是__stdcall.其中参数再次由调用者推送,但是被调用者清理堆栈.它是Win32 API函数的标准约定(由WINAPI宏定义),有时也称为"Pascal"调用约定.

/* example of __stdcall */
push arg1 
push arg2 
push arg3 
call function // no stack cleanup - callee does this
Run Code Online (Sandbox Code Playgroud)

这看起来像是一个小技术细节,但如果在调用者和被调用者之间如何管理堆栈存在分歧,那么堆栈将以不太可能被恢复的方式被销毁.由于__stdcall执行堆栈清理,因此执行此任务的(非常小的)代码只能在一个地方找到,而不是像__cdecl中的每个调用者那样重复.这使得代码略小,尽管大小影响仅在大型程序中可见.

像printf()这样的变量函数几乎不可能使用__stdcall,因为只有调用者才知道传递了多少个参数才能清理它们.被调用者可以做出一些好的猜测(比如通过查看格式字符串),但是栈清理必须由函数的实际逻辑决定,而不是由调用约定机制本身决定.因此只有__cdecl支持可变参数函数,以便调用者可以进行清理.

链接器符号名称装饰:如上面的项目符号所述,调用具有"错误"约定的函数可能是灾难性的,因此Microsoft有一种机制可以避免这种情况发生.它运作良好,但如果不知道原因是什么,它会令人抓狂.他们已选择通过将调用约定编码为具有额外字符的低级函数名称(通常称为"装饰")来解决此问题,并且链接器将这些名称视为不相关的名称.默认调用约定是__cdecl,但每个都可以使用/ G明确请求?编译器的参数.

__cdecl(cl/Gd ......)

此类型的所有函数名都以下划线为前缀,参数数量并不重要,因为调用者负责堆栈设置和堆栈清理.调用者和被调用者可能会对实际传递的参数数量感到困惑,但至少可以正确维护堆栈规则.

__stdcall(cl/Gz ...)

这些函数名称以下划线为前缀,并附加@加上传递的参数的字节数.通过这种机制,不可能使用"错误"类型调用函数,或者甚至使用错误数量的参数调用函数.

__fastcall(cl/Gr ...)

这些函数名以@符号开头,后缀为@parameter,与__stdcall非常相似.

例子:

Declaration                        ----------------------->    decorated name


void __cdecl foo(void);            ----------------------->    _foo

void __cdecl foo(int a);           ----------------------->    _foo

void __cdecl foo(int a, int b);    ----------------------->    _foo

void __stdcall foo(void);          ----------------------->    _foo@0

void __stdcall foo(int a);         ----------------------->    _foo@4

void __stdcall foo(int a, int b);  ----------------------->    _foo@8

void __fastcall foo(void);         ----------------------->    @foo@0

void __fastcall foo(int a);        ----------------------->    @foo@4

void __fastcall foo(int a, int b); ----------------------->    @foo@8
Run Code Online (Sandbox Code Playgroud)


Jar*_*Par 61

C/C++中的所有函数都有一个特定的调用约定.调用约定的关键是确定如何在调用者和被调用者之间传递数据,以及谁负责清除调用堆栈等操作.

Windows上最流行的调用约定是

  • __stdcall,以相反的顺序(从右到左)按下堆栈上的参数
  • __cdecl,以相反的顺序(从右到左)按下堆栈上的参数
  • __clrcall,按顺序将参数加载到CLR表达式堆栈(从左到右).
  • __fastcall,存储在寄存器中,然后推入堆栈
  • __thiscall,推上堆栈; 这个指针存储在ECX中

将此说明符添加到函数声明实质上告诉编译器您希望此特定函数具有此特定调用约定.

这里记录了调用约定

Raymond Chen还从这里开始了关于各种呼叫惯例(5部分)历史的长篇系列.


Nic*_*yer 7

__stdcall是一种调用约定:一种确定参数如何传递给函数(在堆栈或寄存器中)以及在函数返回后负责清理的人(调用者或被调用者)的方法.

Raymond Chen写了一篇关于主要x86调用约定博客,并且有一篇很好的CodeProject文章.

在大多数情况下,您不必担心它们.唯一的情况是,如果您正在调用使用非默认值的库函数 - 否则编译器将生成错误的代码,您的程序可能会崩溃.


Jon*_*han 6

不幸的是,什么时候使用它,什么时候没有,没有简单的答案.

__stdcall意味着函数的参数从第一个到最后一个被压入堆栈.这与__cdecl相反,这意味着参数从最后一个推到第一个,而__fastcall将前四个(我认为)参数放在寄存器中,其余参数放在堆栈上.

您只需要知道被调用者期望的内容,或者您​​是否正在编写库,调用者可能期望的内容,并确保记录您选择的约定.

  • `__stdcall`和`__cdecl`仅在返回(和装饰)后清理的责任不同.对于他们两个(从右到左),参数传递是相同的.你所描述的是Pascal调用约定. (2认同)