stdcall 和 cdecl
有(除其他外)两种类型的调用约定 - stdcall 和 cdecl。我对它们有几个问题:
- 当调用 cdecl 函数时,调用者如何 知道是否应该释放堆栈?在呼叫站点,是否 调用者知道被调用的函数是 cdecl 还是 stdcall 功能 ?它是如何运作的?调用者如何知道是否应该 是否释放堆栈?还是链接者的责任?
- 如果一个声明为 stdcall 的函数调用一个函数(其中 有一个调用约定为 cdecl),或者反过来,会 这会不合适吗?
- 一般来说,我们可以说哪个调用会更快 - cdecl 或 标准调用?
There are (among others) two types of calling conventions - stdcall and cdecl. I have few questions on them:
- When a cdecl function is called, how does a caller
know if it should free up the stack ? At the call site, does the
caller know if the function being called is a cdecl or a stdcall
function ? How does it work ? How does the caller know if it should
free up the stack or not ? Or is it the linkers responsibility ? - If a function which is declared as stdcall calls a function(which
has a calling convention as cdecl), or the other way round, would
this be inappropriate ? - In general, can we say that which call will be faster - cdecl or
stdcall ?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
Raymond Chen 很好地概述了
__stdcall
和 < code>__cdecl 会。(1) 调用者“知道”在调用函数后清理堆栈,因为编译器知道该函数的调用约定并生成必要的代码。
调用约定可能不匹配,如下所示:
这么多代码示例犯了这个错误,这一点都不好笑。它应该是这样的:
但是,假设程序员不忽略编译器错误,编译器将生成正确清理堆栈所需的代码,因为它知道所涉及函数的调用约定。
(2) 两种方法都应该有效。事实上,这种情况至少在与 Windows API 交互的代码中经常发生,因为
__cdecl
是根据 Visual C++ 编译器 和 WinAPI 函数使用__stdcall
约定。(3) 两者之间不应该有真正的性能差异。
Raymond Chen gives a nice overview of what
__stdcall
and__cdecl
does.(1) The caller "knows" to clean up the stack after calling a function because the compiler knows the calling convention of that function and generates the necessary code.
It is possible to mismatch the calling convention, like this:
So many code samples get this wrong it's not even funny. It's supposed to be like this:
However, assuming the programmer doesn't ignore compiler errors, the compiler will generate the code needed to clean up the stack properly since it'll know the calling conventions of the functions involved.
(2) Both ways should work. In fact, this happens quite frequently at least in code that interacts with the Windows API, because
__cdecl
is the default for C and C++ programs according to the Visual C++ compiler and the WinAPI functions use the__stdcall
convention.(3) There should be no real performance difference between the two.
在 CDECL 中,参数以相反的顺序压入堆栈,调用者清除堆栈并通过处理器注册表返回结果(稍后我将其称为“寄存器 A”)。在 STDCALL 中,有一个区别,调用者不清除堆栈,而被调用者则这样做。
你问的是哪个更快。没有人。您应该尽可能地使用本机调用约定。仅当使用需要使用特定约定的外部库时,只有在没有出路的情况下才更改约定。
此外,编译器还可以选择其他约定作为默认约定,即 Visual C++ 编译器使用 FASTCALL,理论上速度更快,因为处理器寄存器的使用更广泛。
通常,您必须为传递到某些外部库的回调函数提供正确的调用约定签名,即从 C 库到
qsort
的回调必须是 CDECL(如果编译器默认使用其他约定,那么我们必须将回调标记为CDECL)或各种WinAPI回调必须是STDCALL(整个WinAPI都是STDCALL)。其他常见情况可能是当您存储指向某些外部函数的指针时,即要创建指向 WinAPI 函数的指针,其类型定义必须用 STDCALL 标记。
下面是一个示例,显示编译器如何执行此操作:
CDECL:
STDCALL:
In CDECL arguments are pushed onto the stack in revers order, the caller clears the stack and result is returned via processor registry (later I will call it "register A"). In STDCALL there is one difference, the caller doeasn't clear the stack, the calle do.
You are asking which one is faster. No one. You should use native calling convention as long as you can. Change convention only if there is no way out, when using external libraries that requires certain convention to be used.
Besides, there are other conventions that compiler may choose as default one i.e. Visual C++ compiler uses FASTCALL which is theoretically faster because of more extensive usage of processor registers.
Usually you must give a proper calling convention signature to callback functions passed to some external library i.e. callback to
qsort
from C library must be CDECL (if the compiler by default uses other convention then we must mark the callback as CDECL) or various WinAPI callbacks must be STDCALL (whole WinAPI is STDCALL).Other usual case may be when you are storing pointers to some external functions i.e. to create a pointer to WinAPI function its type definition must be marked with STDCALL.
And below is an example showing how does the compiler do it:
CDECL:
STDCALL:
我注意到一个帖子说,如果您从
__cdecl
调用__stdcall
并不重要,反之亦然。确实如此。原因:在
__cdecl
中,传递给被调用函数的参数会被调用函数从堆栈中删除,在__stdcall
中,参数会被调用函数从堆栈中删除。称为函数。如果您使用 __stdcall 调用 __cdecl 函数,则堆栈根本不会被清理,因此最终当 __cdecl 使用基于堆栈的引用时对于参数或返回地址将使用当前堆栈指针处的旧数据。如果您从__cdecl
调用__stdcall
函数,则__stdcall
函数会清理堆栈上的参数,然后__cdecl< /code> 函数再次执行此操作,可能会删除调用函数的返回信息。
Microsoft 的 C 约定试图通过修改名称来规避这一点。
__cdecl
函数以下划线为前缀。__stdcall
函数以下划线为前缀,并以 at 符号“@”和要删除的字节数为后缀。例如__cdecl
f(x) 链接为_f
,__stdcall f(int x)
链接为_f@4
> 其中sizeof(int)
是 4 个字节)如果您成功通过了链接器,就可以享受调试混乱的乐趣了。
I noticed a posting that say that it does not matter if you call a
__stdcall
from a__cdecl
or visa versa. It does.The reason: with
__cdecl
the arguments that are passed to the called functions are removed form the stack by the calling function, in__stdcall
, the arguments are removed from the stack by the called function. If you call a__cdecl
function with a__stdcall
, the stack is not cleaned up at all, so eventually when the__cdecl
uses a stacked based reference for arguments or return address will use the old data at the current stack pointer. If you call a__stdcall
function from a__cdecl
, the__stdcall
function cleans up the arguments on the stack, and then the__cdecl
function does it again, possibly removing the calling functions return information.The Microsoft convention for C tries to circumvent this by mangling the names. A
__cdecl
function is prefixed with an underscore. A__stdcall
function prefixes with an underscore and suffixed with an at sign “@” and the number of bytes to be removed. Eg__cdecl
f(x) is linked as_f
,__stdcall f(int x)
is linked as_f@4
wheresizeof(int)
is 4 bytes)If you manage to get past the linker, enjoy the debugging mess.
我想改进@adf88的答案。我觉得 STDCALL 的伪代码并没有反映它在现实中发生的方式。 'a'、'b' 和 'c' 不会从函数体中的堆栈中弹出。相反,它们由
ret
指令弹出(在本例中将使用ret 12
),该指令一下子跳回调用者,同时弹出“a” 、“b”和“c”来自堆栈。这是根据我的理解更正的版本:
STDCALL:
I want to improve on @adf88's answer. I feel that pseudocode for the STDCALL does not reflect the way of how it happens in reality. 'a', 'b', and 'c' aren't popped from the stack in the function body. Instead they are popped by the
ret
instruction (ret 12
would be used in this case) that in one swoop jumps back to the caller and at the same time pops 'a', 'b', and 'c' from the stack.Here is my version corrected according to my understanding:
STDCALL:
它在函数类型中指定。当您有一个函数指针时,如果没有显式 stdcall,则假定它是 cdecl。这意味着,如果您获得 stdcall 指针和 cdecl 指针,则无法交换它们。这两种函数类型可以毫无问题地相互调用,只是在您期望另一种类型时得到一种类型。至于速度,他们都扮演着同样的角色,只是在一个非常细微的地方不同,这确实无关紧要。
It's specified in the function type. When you have a function pointer, it's assumed to be cdecl if not explicitly stdcall. This means that if you get a stdcall pointer and a cdecl pointer, you can't exchange them. The two function types can call each other without issues, it's just getting one type when you expect the other. As for speed, they both perform the same roles, just in a very slightly different place, it's really irrelevant.
调用者和被调用者需要在调用时使用相同的约定 - 这是它可靠工作的唯一方法。调用者和被调用者都遵循预定义的协议 - 例如,谁需要清理堆栈。如果约定不匹配,您的程序会遇到未定义的行为 - 可能会严重崩溃。
这仅是每个调用站点所必需的 - 调用代码本身可以是具有任何调用约定的函数。
您不应该注意到这些约定之间的性能有任何真正的差异。如果这成为一个问题,您通常需要减少调用 - 例如,更改算法。
The caller and the callee need to use the same convention at the point of invokation - that's the only way it could reliably work. Both the caller and the callee follow a predefined protocol - for example, who needs to clean up the stack. If conventions mismatch your program runs into undefined behavior - likely just crashes spectacularly.
This is only required per invokation site - the calling code itself can be a function with any calling convention.
You shouldn't notice any real difference in performance between those conventions. If that becomes a problem you usually need to make less calls - for example, change the algorithm.
这些东西是特定于编译器和平台的。除了 C++ 中的
extern "C"
之外,C 和 C++ 标准都没有提及任何有关调用约定的内容。调用者知道函数的调用约定并相应地处理调用。
是的。
它是函数声明的一部分。
调用者知道调用约定并可以采取相应的操作。
不,调用约定是函数声明的一部分,因此编译器知道它需要知道的一切。
不,为什么要这么做?
我不知道。测试一下。
Those things are Compiler- and Platform-specific. Neither the C nor the C++ standard say anything about calling conventions except for
extern "C"
in C++.The caller knows the calling convention of the function and handles the call accordingly.
Yes.
It is part of the function declaration.
The caller knows the calling conventions and can act accordingly.
No, the calling convention is part of a function's declaration so the compiler knows everything it needs to know.
No. Why should it?
I don't know. Test it.
cdecl 修饰符是函数原型(或函数指针类型等)的一部分,因此调用者可以从那里获取信息并采取相应的操作。
不,没关系。
一般来说,我不会发表任何此类言论。区别很重要,例如。当你想使用 va_arg 函数时。从理论上讲,stdcall 可能更快并生成更小的代码,因为它允许将弹出参数与弹出本地变量结合起来,但是使用
cdecl
OTOH,您可以执行以下操作同样的事情,如果你聪明的话。旨在更快的调用约定通常会进行一些寄存器传递。
The
cdecl
modifier is part of the function prototype (or function pointer type etc.) so the caller get the info from there and acts accordingly.No, it's fine.
In general, I would refrain from any such statements. The distinction matters eg. when you want to use va_arg functions. In theory, it could be that
stdcall
is faster and generates smaller code because it allows to combine popping the arguments with popping the locals, but OTOH withcdecl
, you can do the same thing, too, if you're clever.The calling conventions that aim to be faster usually do some register-passing.
调用约定与 C/C++ 编程语言无关,而是编译器如何实现给定语言的具体细节。如果您始终使用相同的编译器,则无需担心调用约定。
然而,有时我们希望不同编译器编译的二进制代码能够正确地互操作。当我们这样做时,我们需要定义称为应用程序二进制接口(ABI)的东西。 ABI 定义编译器如何将 C/C++ 源代码转换为机器代码。这将包括调用约定、名称修改和 v 表布局。 cdelc 和 stdcall 是 x86 平台上常用的两种不同的调用约定。
通过将调用约定的信息放入源头中,编译器将知道需要生成哪些代码才能与给定的可执行文件正确地进行互操作。
Calling conventions have nothing to do with the C/C++ programming languages and are rather specifics on how a compiler implements the given language. If you consistently use the same compiler, you never need to worry about calling conventions.
However, sometimes we want binary code compiled by different compilers to inter-operate correctly. When we do so we need to define something called the Application Binary Interface (ABI). The ABI defines how the compiler converts the C/C++ source into machine-code. This will include calling conventions, name mangling, and v-table layout. cdelc and stdcall are two different calling conventions commonly used on x86 platforms.
By placing the information on the calling convention into the source header, the compiler will know what code needs to be generated to inter-operate correctly with the given executable.