__stdcall的含义和用法是什么?


76

__stdcall这些天我碰到很多东西。

MSDN并未非常清楚地解释其真正含义,何时以及为何使用它(如果有的话)。

如果有人提供解释,最好提供一个或两个示例,我将不胜感激。

Answers:


68

C / C ++中的所有函数都有特定的调用约定。调用约定的重​​点是确定如何在调用者和被调用者之间传递数据,以及负责执行诸如清除调用堆栈之类的操作的人员。

Windows上最流行的呼叫约定是

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

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

调用约定记录在这里

从这里开始,Raymond Chen还对各种调用约定的历史做了很长的系列讨论(共五部分)。


那么x64版本呢?
Pablo Ariel

72

传统上,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中那样在每个调用方中都重复该代码。尽管大小影响仅在大型程序中可见,但这使代码非常小。

像stfcall这样的可变参数函数几乎不可能用__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

7

__stdcall是一种调用约定:一种确定如何将参数传递给函数的方法(在堆栈上或在寄存器中),以及由谁负责在函数返回后进行清理(调用方或被调用方)。

Raymond Chen撰写了有关主要x86调用约定博客,并且还有一篇不错的CodeProject文章

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


6

不幸的是,对于何时使用它以及何时不使用它没有简单的答案。

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

您只需要知道被调用者的期望,或者如果您正在编写库,则调用者可能期望的是什么,并确保您记录选择的约定。


2
__stdcall并且__cdecl仅在退货(和装修)后负责清理的责任不同。他们两个的参数传递都是相同的(从右到左)。您所描述的是Pascal调用约定。
a3f

3

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


2

__stdcall表示调用约定(有关详细信息,请参见此PDF)。这意味着它指定如何将函数自变量从堆栈中推入和弹出,以及由谁负责。

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


1

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

如果调用者和被调用的代码使用不同的约定,则会遇到未定义的行为(例如看起来很奇怪的崩溃)。

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


1

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

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

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


1

__stdcall是用于该函数的调用约定。这将告诉编译器适用于设置堆栈,推送参数和获取返回值的规则。还有一些其他的调用约定一样的__cdecl__thiscall__fastcall__naked

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

可以在Wikipedia上找到更多详细信息。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.