__stdcall的含义和用法是什么?

发布于 2024-08-03 06:38:00 字数 223 浏览 6 评论 0原文

我遇到过 __stdcall这些天很多。

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

如果有人能提供解释,最好是举一两个例子,我将不胜感激。

I've come across __stdcall a lot these days.

MSDN doesn't explain very clearly what it really means, when and why should it be used, if at all.

I would appreciate if someone would provide an explanation, preferably with an example or two.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

虫児飞 2024-08-10 06:38:00

这个答案涵盖了 32 位模式。 (Windows x64 仅使用 2 种约定:普通约定(如果有名称,则称为 __fastcall)和 __vectorcall,除了 SIMD 向量如何使用之外,其他约定相同像 __m128i 这样的参数被传递)。


传统上,C 函数调用是通过调用者将一些参数压入堆栈,调用该函数,然后弹出堆栈以清理这些压入的参数来进行的。

/* example of __cdecl */
push arg1
push arg2
push arg3
call function
add esp,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 中那样在每个调用者中重复。这使得代码稍微小一些,尽管大小影响仅在大型程序中可见。

(优化编译器有时可以为在同一函数和 mov 参数中进行的多个 cdecl 调用中分配的参数留出空间,而不是总是 add esp, n / 这可以节省指令,但会增加代码大小,例如,gcc -maccumulate-outgoing-args 总是这样做,并且在 push< 之前对旧版 CPU 的性能有好处。 /code> 是高效的。)

像 printf() 这样的可变函数是 不可能正确使用 __stdcall,因为只有调用者真正知道为了清理它们而传递了多少参数。被调用者可以做出一些好的猜测(例如,通过查看格式字符串),但在 C 中向 printf 传递比格式字符串引用更多的参数是合法的(它们将被默默地忽略)。因此,只有 __cdecl 支持可变参数函数,调用者在其中进行清理。

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

__cdecl (cl /Gd ...)

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

__stdcall (cl /Gz ...)

这些函数名称以下划线为前缀,并附加@加上传递参数的字节数。通过这种机制,不可能调用带有错误数量的参数的函数。调用者和被调用者明确同意返回 ret 12 指令例如,弹出 12 个字节的堆栈参数以及返回地址。

您将收到链接时或运行时 DLL 错误,而不是让函数返回且 ESP 指向调用者不期望的地方。 (例如,如果您添加了一个新的参数并且没有重新编译主程序和库。假设您没有通过使早期的参数变窄来欺骗系统,例如 int64_t -> < code>int32_t.)

__fastcall (cl /Gr ...)

这些函数名称以 @ 符号开头,并以 @bytes 计数作为后缀,与 __stdcall 非常相似。前 2 个参数在 ECX 和 EDX 中传递,其余参数在堆栈上传递。字节计数包括寄存器 args。与 __stdcall 一样,像 char 这样的窄参数仍然会占用 4 字节参数传递槽(寄存器或堆栈上的双字)。
示例:

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

请注意,在 C++ 中,使用允许函数重载的正常名称修改机制而不是而不是@8,效果也不太好。因此,您只能在 extern "C" 函数中看到实际数字。例如,https://godbolt.org/z/v7EaWs

This answer covers 32-bit mode. (Windows x64 only uses 2 conventions: the normal one (which is called __fastcall if it has a name at all) and __vectorcall, which is the same except for how SIMD vector args like __m128i are passed).


Traditionally, C function calls are made with the caller pushing some parameters onto the stack, calling the function, and then popping the stack to clean up those pushed arguments.

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

Note: The default convention — shown above — is known as __cdecl.

The other most popular convention is __stdcall. In it the parameters are again pushed by the caller, but the stack is cleaned up by the callee. It is the standard convention for Win32 API functions (as defined by the WINAPI macro in <windows.h>), and it's also sometimes called the "Pascal" calling convention.

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

This looks like a minor technical detail, but if there is a disagreement on how the stack is managed between the caller and the callee, the stack will be destroyed in a way that is unlikely to be recovered.
Since __stdcall does stack cleanup, the (very tiny) code to perform this task is found in only one place, rather than being duplicated in every caller as it is in __cdecl. This makes the code very slightly smaller, though the size impact is only visible in large programs.

(Optimizing compilers can sometimes leave space for args allocated across multiple cdecl calls made from the same function and mov args into it, instead of always add esp, n / push. That saves instructions but can increase code-size. For example gcc -maccumulate-outgoing-args always does this, and was good for performance on older CPUs before push was efficient.)

Variadic functions like printf() are impossible to get right with __stdcall, because only the caller really knows how many arguments were passed in order to clean them up. The callee can make some good guesses (say, by looking at a format string), but it's legal in C to pass more args to printf than the format-string references (they'll be silently ignored). Hence only __cdecl supports variadic functions, where the caller does the cleanup.

Linker symbol name decorations:
As mentioned in a bullet point above, calling a function with the "wrong" convention can be disastrous, so Microsoft has a mechanism to avoid this from happening. It works well, though it can be maddening if one does not know what the reasons are.
They have chosen to resolve this by encoding the calling convention into the low-level function names with extra characters (which are often called "decorations"), and these are treated as unrelated names by the linker. The default calling convention is __cdecl, but each one can be requested explicitly with the /G? parameter to the compiler.

__cdecl (cl /Gd ...)

All function names of this type are prefixed with an underscore, and the number of parameters does not really matter because the caller is responsible for stack setup and stack cleanup. It is possible for a caller and callee to be confused over the number of parameters actually passed, but at least the stack discipline is maintained properly.

__stdcall (cl /Gz ...)

These function names are prefixed with an underscore and appended with @ plus the number of bytes of parameters passed. By this mechanism, it's not possible to call a function with the wrong amount of parameters. The caller and callee definitely agree on returning with a ret 12 instruction for example, to pop 12 bytes of stack args along with the return address.

You'll get a link-time or runtime DLL error instead of having a function return with ESP pointing somewhere the caller isn't expecting. (For example if you added a new arg and didn't recompile both the main program and the library. Assuming you didn't fool the system by making an earlier arg narrower, like int64_t -> int32_t.)

__fastcall (cl /Gr ...)

These function names start with an @ sign and are suffixed with the @bytes count, much like __stdcall. The first 2 args are passed in ECX and EDX, the rest are passed on the stack. The byte count includes the register args. As with __stdcall, a narrow arg like char still uses up a 4-byte arg-passing slot (a register, or a dword on the stack).
Examples:

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

Note that in C++, the normal name-mangling mechanism that allows function overloading is used instead of @8, not as well. So you'll only see actual numbers in extern "C" functions. For example, https://godbolt.org/z/v7EaWs for example.

情场扛把子 2024-08-10 06:38:00

C/C++ 中的所有函数都有特定的调用约定。调用约定的要点是确定数据在调用者和被调用者之间传递的方式以及谁负责清理调用堆栈等操作。

Windows 上最流行的调用约定是

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

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

调用约定记录在此处

Raymond Chen 还从这里开始撰写了有关各种调用约定历史的长系列文章(5 部分)。

All functions in C/C++ have a particular calling convention. The point of a calling convention is to establish how data is passed between the caller and callee and who is responsible for operations such as cleaning out the call stack.

The most popular calling conventions on windows are

  • __stdcall, Pushes parameters on the stack, in reverse order (right to left)
  • __cdecl, Pushes parameters on the stack, in reverse order (right to left)
  • __clrcall, Load parameters onto CLR expression stack in order (left to right).
  • __fastcall, Stored in registers, then pushed on stack
  • __thiscall, Pushed on stack; this pointer stored in ECX

Adding this specifier to the function declaration essentially tells the compiler that you want this particular function to have this particular calling convention.

The calling conventions are documented here

Raymond Chen also did a long series on the history of the various calling conventions (5 parts) starting here.

一江春梦 2024-08-10 06:38:00

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

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

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

__stdcall is a calling convention: a way of determining how parameters are passed to a function (on the stack or in registers) and who is responsible for cleaning up after the function returns (the caller or the callee).

Raymond Chen wrote a blog about the major x86 calling conventions, and there's a nice CodeProject article too.

For the most part, you shouldn't have to worry about them. The only case in which you should is if you're calling a library function that uses something other than the default -- otherwise the compiler will generate the wrong code and your program will probably crash.

蝶舞 2024-08-10 06:38:00

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

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

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

Unfortunately, there is no easy answer for when to use it and when not.

__stdcall means that the arguments to a function are pushed onto the stack from the first to the last. This is as opposed to __cdecl, which means that the arguments are pushed from last to first, and __fastcall, which places the first four (I think) arguments in registers, and the rest go on the stack.

You just need to know what the callee expects, or if you are writing a library, what your callers are likely expect, and make sure you document your chosen convention.

情话已封尘 2024-08-10 06:38:00

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

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

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

That's a calling convention that WinAPI functions need to be called properly. A calling convention is a set of rules on how the parameters are passed into the function and how the return value is passed from the function.

If the caller and the called code use different conventions you run into undefined behaviour (like such a strange-looking crash).

C++ compilers don't use __stdcall by default - they use other conventions. So in order to call WinAPI functions from C++ you need to specify that they use __stdcall - this is usually done in Windoes SDK header files and you also do it when declaring function pointers.

¢好甜 2024-08-10 06:38:00

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

It specifies a calling convention for a function. A calling convention is a set of rules how parameters are passed to a function: in which order, per address or per copy, who is to clean up the parameters (caller or callee) etc.

风柔一江水 2024-08-10 06:38:00

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

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

__stdcall denotes a calling convention (see this PDF for some details). This means it specifies how function arguments are pushed and popped from the stack, and who is responsible.

__stdcall is just one of several calling conventions, and is used throughout the WINAPI. You must use it if you provide function pointers as callbacks for some of those functions. In general, you do not need to denote any specific calling convention in your code, but just use the compiler's default, except for the case noted above (providing callbacks to 3rd party code).

嘿哥们儿 2024-08-10 06:38:00

简而言之,当您调用函数时,它会被加载到堆栈/寄存器中。 __stdcall 是一种约定/方式(先是右参数,然后是左参数...), __decl 是另一种约定,用于将函数加载到堆栈或寄存器上。

如果您使用它们,您将指示计算机在链接期间使用特定方式加载/卸载函数,因此您不会出现不匹配/崩溃。

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

simply put when you call function, it gets loaded in stack/register. __stdcall is one convention/way(right argument first, then left argument ...), __decl is another convention that are used to load the function on the stack or registers.

If you use them you instruct the computer to use that specific way to load/unload the function during linking and hence you would not get a mismatch/crash.

Otherwise the function-callee and function-caller might use different conventions causing program to crash.

并安 2024-08-10 06:38:00

__stdcall 是函数使用的调用约定。这告诉编译器适用于设置堆栈、推送参数和获取返回值的规则。还有许多其他调用约定,例如 __cdecl__thiscall__fastcall__naked

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

更多详细信息可以在维基百科上找到。

__stdcall is the calling convention used for the function. This tells the compiler the rules that apply for setting up the stack, pushing arguments and getting a return value. There are a number of other calling conventions like __cdecl, __thiscall, __fastcall and __naked.

__stdcall is the standard calling convention for Win32 system calls.

More details can be found on Wikipedia.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文