stdcall 和 cdecl

发布于 2024-09-12 09:57:45 字数 331 浏览 8 评论 0原文

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

  1. 当调用 cdecl 函数时,调用者如何 知道是否应该释放堆栈?在呼叫站点,是否 调用者知道被调用的函数是 cdecl 还是 stdcall 功能 ?它是如何运作的?调用者如何知道是否应该 是否释放堆栈?还是链接者的责任?
  2. 如果一个声明为 stdcall 的函数调用一个函数(其中 有一个调用约定为 cdecl),或者反过来,会 这会不合适吗?
  3. 一般来说,我们可以说哪个调用会更快 - cdecl 或 标准调用?

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

  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 ?

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

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

发布评论

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

评论(9

意中人 2024-09-19 09:57:45

Raymond Chen 很好地概述了 __stdcall 和 < code>__cdecl 会

(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();
}

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

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);

这么多代码示例犯了这个错误,这一点都不好笑。它应该是这样的:

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

但是,假设程序员不忽略编译器错误,编译器将生成正确清理堆栈所需的代码,因为它知道所涉及函数的调用约定。

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

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

嘿咻 2024-09-19 09:57:45

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

你问的是哪个更快。没有人。您应该尽可能地使用本机调用约定。仅当使用需要使用特定约定的外部库时,只有在没有出路的情况下才更改约定。

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

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

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

下面是一个示例,显示编译器如何执行此操作:

/* 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:

/* 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)

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:

/* 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:

/* 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)
墟烟 2024-09-19 09:57:45

我注意到一个帖子说,如果您从 __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 where sizeof(int) is 4 bytes)

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

茶花眉 2024-09-19 09:57:45

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

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

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 '函数' 体 */ 将“a”(从堆栈)复制到寄存器 A 将“b”(从堆栈)复制到寄存器 B A 和 B 相加,结果存入 A 将“c”(从堆栈)复制到寄存器 B A 和 B 相加,结果存入 A 跳回调用者代码,同时将 'a'、'b' 和 'c' 从堆栈中弹出(a、b 和 c 在这一步中从堆栈中删除,结果在寄存器 A 中

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:

/* 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)

渡你暖光 2024-09-19 09:57:45

它在函数类型中指定。当您有一个函数指针时,如果没有显式 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.

梨涡 2024-09-19 09:57:45

调用者和被调用者需要在调用时使用相同的约定 - 这是它可靠工作的唯一方法。调用者和被调用者都遵循预定义的协议 - 例如,谁需要清理堆栈。如果约定不匹配,您的程序会遇到未定义的行为 - 可能会严重崩溃。

这仅是每个调用站点所必需的 - 调用代码本身可以是具有任何调用约定的函数。

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

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.

ㄟ。诗瑗 2024-09-19 09:57:45

这些东西是特定于编译器和平台的。除了 C++ 中的 extern "C" 之外,C 和 C++ 标准都没有提及任何有关调用约定的内容。

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

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

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

是的。

它是如何工作的?

它是函数声明的一部分。

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

调用者知道调用约定并可以采取相应的操作。

或者是链接器的责任?

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

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

不,为什么要这么做?

一般来说,我们可以说哪个调用更快 - cdecl 或 stdcall 吗?

我不知道。测试一下。

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

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 ?

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 ?

No. Why should it?

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

I don't know. Test it.

愿得七秒忆 2024-09-19 09:57:45

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

cdecl 修饰符是函数原型(或函数指针类型等)的一部分,因此调用者可以从那里获取信息并采取相应的操作。

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

不,没关系。

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

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

旨在更快的调用约定通常会进行一些寄存器传递。

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

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.

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?

No, it's fine.

c) In general, can we say that which call will be faster - cdecl or 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 stdcall is 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.

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

笨笨の傻瓜 2024-09-19 09:57:45

调用约定与 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.

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