C++ stdcall 和 cdecl

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3404372/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 12:46:21  来源:igfitidea点击:

stdcall and cdecl

c++stdcallcdecl

提问by Rakesh Agarwal

There are (among others) two types of calling conventions - stdcalland cdecl. I have few questions on them:

有(除其他外)两种类型的调用约定 - stdcallcdecl。我对他们有几个问题:

  1. 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 ?
  2. 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 ?
  3. In general, can we say that which call will be faster - cdecl or stdcall ?
  1. 当调用 cdecl 函数时,调用者如何知道它是否应该释放堆栈?在调用站点,调用者是否知道被调用的函数是 cdecl 还是 stdcall 函数?它是如何工作的 ?调用者如何知道它是否应该释放堆栈?还是链接器的责任?
  2. 如果声明为 stdcall 的函数调用函数(调用约定为 cdecl),或者反过来,这是否不合适?
  3. 一般来说,我们可以说哪个调用会更快 - cdecl 还是 stdcall ?

回答by In silico

Raymond Chen gives a nice overview of what __stdcalland __cdecldoes.

雷蒙德陈给出了一个什么样的很好的概述__stdcall__cdecl

(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.

(1) 调用者“知道”在调用函数后清理堆栈,因为编译器知道该函数的调用约定并生成必要的代码。

void __stdcall StdcallFunc() {}

void __cdecl CdeclFunc()
{
    // The compiler knows that StdcallFunc() uses the __stdcall
    // convention at this point, so it generates the proper binary
    // for stack cleanup.
    StdcallFunc();
}

It is possible to mismatch the calling convention, like this:

调用约定可能不匹配,如下所示:

LRESULT MyWndProc(HWND hwnd, UINT msg,
    WPARAM wParam, LPARAM lParam);
// ...
// Compiler usually complains but there's this cast here...
windowClass.lpfnWndProc = reinterpret_cast<WNDPROC>(&MyWndProc);

So many code samples get this wrong it's not even funny. It's supposed to be like this:

如此多的代码示例都弄错了,这甚至不好笑。它应该是这样的:

// CALLBACK is #define'd as __stdcall
LRESULT CALLBACK MyWndProc(HWND hwnd, UINT msg
    WPARAM wParam, LPARAM lParam);
// ...
windowClass.lpfnWndProc = &MyWndProc;

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 __cdeclis the default for C and C++ programs according to the Visual C++ compilerand the WinAPI functions use the __stdcallconvention.

(2) 两种方式都应该有效。事实上,至少在与 Windows API 交互的代码中,这种情况经常发生,因为__cdecl根据 Visual C++ 编译器WinAPI 函数使用__stdcall约定,这是 C 和 C++ 程序的默认值

(3) There should be no real performance difference between the two.

(3) 两者之间应该没有真正的性能差异。

回答by adf88

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.

在 CDECL 参数以相反的顺序压入堆栈中,调用者清除堆栈并通过处理器注册表返回结果(稍后我将称其为“寄存器 A”)。在 STDCALL 中有一个区别,调用者不清除堆栈,被调用者做。

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.

此外,编译器可能会选择其他约定作为默认约定,即 Visual C++ 编译器使用 FASTCALL,由于处理器寄存器的更广泛使用,理论上速度更快。

Usually you must give a proper calling convention signature to callback functions passed to some external library i.e. callback to qsortfrom 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).

通常,您必须为传递给某些外部库的回调函数提供正确的调用约定签名,即qsort从 C 库的回调必须是 CDECL(如果编译器默认使用其他约定,那么我们必须将回调标记为 CDECL)或各种 WinAPI 回调必须是STDCALL(整个 WinAPI 是 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.

其他常见情况可能是当您存储指向某些外部函数的指针时,即创建指向 WinAPI 函数的指针,其类型定义必须用 STDCALL 标记。

And below is an example showing how does the compiler do it:

下面是一个例子,展示了编译器是如何做到的:

/* 1. calling function in C++ */
i = Function(x, y, z);

/* 2. function body in C++ */
int Function(int a, int b, int c) { return a + b + c; }

CDECL:

CDECL:

/* 1. calling CDECL 'Function' in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then a copy of 'y', then a copy of 'x'
call (jump to function body, after function is finished it will jump back here, the address where to jump back is in registers)
move contents of register A to 'i' variable
pop all from the stack that we have pushed (copy of x, y and z)

/* 2. CDECL 'Function' body in pseudo-assembler */
/* Now copies of 'a', 'b' and 'c' variables are pushed onto the stack */
copy 'a' (from stack) to register A
copy 'b' (from stack) to register B
add A and B, store result in A
copy 'c' (from stack) to register B
add A and B, store result in A
jump back to caller code (a, b and c still on the stack, the result is in register A)

STDCALL:

标准呼叫:

/* 1. calling STDCALL in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then a copy of 'y', then a copy of 'x'
call
move contents of register A to 'i' variable

/* 2. STDCALL 'Function' body in pseaudo-assembler */
pop 'a' from stack to register A
pop 'b' from stack to register B
add A and B, store result in A
pop 'c' from stack to register B
add A and B, store result in A
jump back to caller code (a, b and c are no more on the stack, result in register A)

回答by Lee Hamilton

I noticed a posting that say that it does not matter if you call a __stdcallfrom a __cdeclor visa versa. It does.

我注意到一个帖子说,如果你__stdcall从 a调用a__cdecl或反之亦然,这并不重要。确实如此。

The reason: with __cdeclthe 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 __cdeclfunction with a __stdcall, the stack is not cleaned up at all, so eventually when the __cdecluses a stacked based reference for arguments or return address will use the old data at the current stack pointer. If you call a __stdcallfunction from a __cdecl, the __stdcallfunction cleans up the arguments on the stack, and then the __cdeclfunction does it again, possibly removing the calling functions return information.

原因: __cdecl传递给被调用函数__stdcall的参数被调用函数从堆栈中删除,在 中,被调用函数从堆栈中删除参数。如果您__cdecl使用 a调用函数__stdcall,则堆栈根本不会被清理,因此最终当__cdecl使用基于堆栈的参数引用或返回地址时,将使用当前堆栈指针处的旧数据。如果您__stdcall从 a调用函数__cdecl,该__stdcall函数会清除堆栈上的参数,然后该__cdecl函数再次执行此操作,可能会删除调用函数的返回信息。

The Microsoft convention for C tries to circumvent this by mangling the names. A __cdeclfunction is prefixed with an underscore. A __stdcallfunction prefixes with an underscore and suffixed with an at sign “@” and the number of bytes to be removed. Eg __cdeclf(x) is linked as _f, __stdcall f(int x)is linked as _f@4where sizeof(int)is 4 bytes)

Microsoft 的 C 约定试图通过修改名称来规避这一点。甲__cdecl函数的前缀以下划线。甲__stdcall待去除以下划线和后缀at符号“@”和字节数功能前缀。例如__cdeclf(x) 链接为_f__stdcall f(int x)链接为_f@4wheresizeof(int)是 4 个字节)

If you manage to get past the linker, enjoy the debugging mess.

如果您设法通过链接器,请享受调试混乱。

回答by golem

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 retinstruction (ret 12would 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.

我想改进@adf88 的回答。我觉得 STDCALL 的伪代码并没有反映它在现实中的发生方式。'a'、'b' 和 'c' 不会从函数体的堆栈中弹出。相反,它们由ret指令(ret 12将在这种情况下使用)弹出,该指令一次性跳转回调用者,同时从堆栈中弹出 'a'、'b' 和 'c'。

Here is my version corrected according to my understanding:

这是我根据我的理解更正的版本:

STDCALL:

标准呼叫:

/* 1. calling STDCALL in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then copy of 'y', then copy of 'x'
call
move contents of register A to 'i' variable

/* 2. STDCALL 'Function' body in pseaudo-assembler */ copy 'a' (from stack) to register A copy 'b' (from stack) to register B add A and B, store result in A copy 'c' (from stack) to register B add A and B, store result in A jump back to caller code and at the same time pop 'a', 'b' and 'c' off the stack (a, b and c are removed from the stack in this step, result in register A)

回答by Puppy

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.

它在函数类型中指定。当你有一个函数指针时,如果没有明确的 stdcall,它被假定为 cdecl。这意味着如果你得到一个 stdcall 指针和一个 cdecl 指针,你就不能交换它们。这两种函数类型可以毫无问题地相互调用,它只是在您期望另一种类型时获得一种类型。至于速度,他们都扮演着同样的角色,只是在一个稍微不同的地方,真的无关紧要。

回答by sellibitze

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++.

这些东西是特定于编译器和平台的。C 和 C++ 标准都没有说明除extern "C"C++之外的调用约定。

how does a caller know if it should free up the stack ?

调用者如何知道是否应该释放堆栈?

The caller knows the calling convention of the function and handles the call accordingly.

调用者知道函数的调用约定并相应地处理调用。

At the call site, does the caller know if the function being called is a cdecl or a stdcall function ?

在调用站点,调用者是否知道被调用的函数是 cdecl 还是 stdcall 函数?

Yes.

是的。

How does it work ?

它是如何工作的 ?

It is part of the function declaration.

它是函数声明的一部分。

How does the caller know if it should free up the stack or not ?

调用者如何知道它是否应该释放堆栈?

The caller knows the calling conventions and can act accordingly.

调用者知道调用约定并可以相应地采取行动。

Or is it the linkers responsibility ?

还是链接器的责任?

No, the calling convention is part of a function's declaration so the compiler knows everything it needs to know.

不,调用约定是函数声明的一部分,因此编译器知道它需要知道的一切。

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 ?

如果声明为 stdcall 的函数调用函数(调用约定为 cdecl),或者反过来,这是否不合适?

No. Why should it?

不,为什么要这样做?

In general, can we say that which call will be faster - cdecl or stdcall ?

一般来说,我们可以说哪个调用会更快 - cdecl 还是 stdcall ?

I don't know. Test it.

我不知道。测试一下。

回答by sharptooth

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.

您不应该注意到这些约定之间的性能差异。如果这成为一个问题,您通常需要减少调用次数 - 例如,更改算法。

回答by jpalecek

a) When a cdecl function is called by the caller, how does a caller know if it should free up the stack?

a) 当调用者调用 cdecl 函数时,调用者如何知道它是否应该释放堆栈?

The cdeclmodifier is part of the function prototype (or function pointer type etc.) so the caller get the info from there and acts accordingly.

cdecl改性剂是函数原型的一部分(或函数指针类型等),使主叫方获得从那里的信息和相应地动作。

b) 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?

b) 如果声明为 stdcall 的函数调用函数(调用约定为 cdecl),或者反过来,这是否不合适?

No, it's fine.

不,还好。

c) In general, can we say that which call will be faster - cdecl or stdcall?

c) 一般来说,我们可以说哪个调用会更快——cdecl 还是 stdcall?

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 stdcallis faster and generates smaller code because it allows to combine popping the arguments with popping the locals, but OTOH with cdecl, you can do the same thing, too, if you're clever.

总的来说,我不会发表任何此类声明。区别很重要,例如。当您想使用 va_arg 函数时。从理论上讲,它可能stdcall更快并生成更小的代码,因为它允许将弹出参数与弹出本地参数结合起来,但是cdecl如果你很聪明,你也可以做同样的事情。

The calling conventions that aim to be faster usually do some register-passing.

旨在提高速度的调用约定通常会进行一些寄存器传递。

回答by doron

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.

调用约定与 C/C++ 编程语言无关,而是关于编译器如何实现给定语言的细节。如果您始终使用相同的编译器,则无需担心调用约定。

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.

但是,有时我们希望由不同编译器编译的二进制代码能够正确互操作。当我们这样做时,我们需要定义一个叫做应用程序二进制接口 (ABI) 的东西。ABI 定义了编译器如何将 C/C++ 源代码转换为机器代码。这将包括调用约定、名称修改和 v 表布局。cdelc 和 stdcall 是 x86 平台上常用的两种不同的调用约定。

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.

通过将有关调用约定的信息放入源头文件中,编译器将知道需要生成哪些代码才能与给定的可执行文件正确互操作。