__stdcall的含义和用法是什么?

时间:2009-08-20 14:03:10

标签: c++ windows calling-convention

这些天我经常遇到__stdcall

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

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

9 个答案:

答案 0 :(得分:55)

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

Windows上最受欢迎的调用约定是

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

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

这里记录了调用约定

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

答案 1 :(得分:53)

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

/* example of __cdecl */
push arg1
push arg2
push arg3
call function
add sp,12 // effectively "pop; pop; pop"

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

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

/* example of __stdcall */
push arg1 
push arg2 
push arg3 
call function // no stack cleanup - callee does this

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

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

链接器符号名称装饰: 如上面的一个要点所述,使用"错误"惯例可能是灾难性的,因此微软有一种避免这种情况发生的机制。它运作良好,但如果不知道原因是什么,它会令人抓狂。 他们选择通过将调用约定编码为具有额外字符的低级函数名称(通常称为" decorations")来解决此问题,并且链接器将这些名称视为不相关的名称。默认调用约定是__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

答案 2 :(得分:7)

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

Raymond Chen写了blog about the major x86 calling conventions,而且CodeProject article也很好。

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

答案 3 :(得分:6)

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

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

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

答案 4 :(得分:3)

它指定函数的调用约定。调用约定是一组规则如何将参数传递给函数:按顺序,每个地址或每个副本,谁来清理参数(调用者或被调用者)等。

答案 5 :(得分:2)

__ stdcall表示调用约定(有关详细信息,请参阅this PDF)。这意味着它指定了如何从堆栈中推送和弹出函数参数,以及谁负责。

__ stdcall只是几个调用约定之一,并在整个WINAPI中使用。如果您将函数指针作为某些函数的回调提供,则必须使用它。通常,您不需要在代码中表示任何特定的调用约定,而只需使用编译器的默认值,除了上面提到的情况(提供对第三方代码的回调)。

答案 6 :(得分:1)

这是一个需要正确调用WinAPI函数的调用约定。调用约定是关于如何将参数传递给函数以及如何从函数传递返回值的一组规则。

如果调用者和被调用代码使用不同的约定,则会遇到未定义的行为(如such a strange-looking crash)。

C ++编译器默认不使用__stdcall - 它们使用其他约定。因此,为了从C ++调用WinAPI函数,您需要指定它们使用__stdcall - 这通常在Windoes SDK头文件中完成,并且在声明函数指针时也会这样做。

答案 7 :(得分:1)

简单地说,当你调用函数时,它会被加载到堆栈/寄存器中。 __stdcall是一种约定/方式(首先是右参数,然后是左参数...),__ dec是另一种用于在堆栈或寄存器上加载函数的约定。

如果您使用它们,则指示计算机在链接期间使用该特定方式加载/卸载该功能,因此您不会遇到不匹配/崩溃。

否则函数调用者和函数调用者可能会使用不同的约定导致程序崩溃。

答案 8 :(得分:1)

__ stdcall 是用于该函数的调用约定。这告诉编译器适用于设置堆栈,推送参数和获取返回值的规则。还有许多其他调用约定,例如 __ cdecl __ thiscall __ fastcall __ naked

__ stdcall 是Win32系统调用的标准调用约定。

更多详情可在Wikipedia找到。