【问题标题】:What is the calling convention for extern "C" in C++? [duplicate]C++ 中 extern "C" 的调用约定是什么? [复制]
【发布时间】:2019-07-21 23:51:07
【问题描述】:

标题确实准确地描述了我的要求。

extern "C" int foo( int bar ) { return bar; }

从我的测试来看,它似乎不是__cdecl__stdcall__fastcall,显然也不是__thiscall

什么是约定,它是如何运作的?

谢谢。

【问题讨论】:

  • 我的超能力表明编译器只是将它视为一个内联函数,无论它在哪里使用。您是否已通过调用它的程序集来查看参数是如何传入和传出堆栈的?
  • extern "C" 与 __cdecl 不同。它实际上与生成的 linker 符号的类型有关。 C++ 确实进行了名称修改(这就是通过将类型和函数名称混合在一起来完成重载的方式)...但是 extern "C" 告诉 C++ 编译器不执行名称修改
  • 你为什么要问?这些双下划线可憎仅适用于 32 位 x86 Windows。
  • 没有调用约定。它取决于您使用的编译器和平台。

标签: c++ c windows


【解决方案1】:

让我们看看使用 32 位 Visual Studio 项目的 Debug 构建生成的程序集(默认设置):

这是我的程序:

extern "C" int func1(int x);
extern "C" int __stdcall func2(int x);
extern "C" int __cdecl func3(int x);

int main()
{
    int x = 0;
    func1(1);
    func2(2);
    func3(2);
    return 0;
}

其中func1func2func3 在单独的源文件中定义,以限制自动内联的可能性。

让我们看看生成的 main 汇编代码:

    func1(1);
002117E8  push        1  
002117EA  call        _func1 (0211159h)  
002117EF  add         esp,4  
    func2(2);
002117F2  push        2  
002117F4  call        _func2@4 (0211131h)  
    func3(3);
002117F9  push        3  
002117FB  call        _func3 (021107Dh)  
00211800  add         esp,4  

对于 func1 和 func3,它是相同的签名。参数被压入堆栈,调用函数调用,然后堆栈寄存器(esp)被调整回(弹出)到它的先前地址 - 正如_cdecl 调用约定所期望的那样。在 __cdecl 调用约定中,调用者负责在函数调用完成后将堆栈指针恢复到其原始地址。

func2调用后,没有堆栈指针调整。与声明的 __stdcall 调用约定一致。在 __stdcall 调用中,编译后的函数负责弹回堆栈指针。检查 func1func2 的程序集显示 func1 以:

00211881  ret    // return, no stack adjustment

而 func2 以这个程序集结束:

002118E1  ret         4   // return and pop 4 bytes from stack

现在,在您断定“无链接属性”意味着“__cdecl”之前,请记住 Visual Studio 项目具有以下设置:

让我们将调用约定设置更改为 __stdcall,看看生成的程序集是什么样子的:

    func1(1);
003417E8  push        1  
003417EA  call        _func1@4 (034120Dh)  
    func2(2);
003417EF  push        2  
003417F1  call        _func2@4 (0341131h)  
    func3(3);
003417F6  push        3  
003417F8  call        _func3 (034107Dh)  
003417FD  add         esp,4  

在调用 func1 后 main 突然没有弹出参数 - 因此func1 假定项目设置的默认调用约定。从技术上讲,这就是您的答案。

在某些环境中,__stdcall 作为默认值是常态。例如驱动程序开发...

【讨论】:

  • 非常有用的答案.. 谢谢!
【解决方案2】:

extern "C" 确定的只是名称修饰。其他一切都取决于平台。

我只能假设您正在 x86-64 / win64 目标上进行测试?

如果是这样,那么所有这些调用约定都不再存在:
对于 Win64,请参阅 https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2017
搜索“x86-64 System V ABI”以获取其他所有内容。

忽略所有指定调用约定的尝试,并使用统一的。

对于 x86,它又是依赖于平台的默认设置,因此(过去)最好为具有多个选项的平台明确指定调用约定。

【讨论】:

    【解决方案3】:

    x86 Disassembly/Calling Conventions 上的 wikibook 声明 this 关于 C++ 中 extern "C" 的调用约定:

    外部“C”
    在 C++ 源文件中,放置在 extern "C" 块中的函数保证不会被破坏。当库是用 C++ 编写的并且需要在不被破坏的情况下导出函数时,经常会这样做。尽管该程序是用 C++ 编写并使用 C++ 编译器编译的,但某些函数可能因此不会被破坏,并且将使用一种普通的 C 调用约定(通常是 CDECL)

    而普通的 C 调用约定是 CDECL、STDCALL 和 FASTCALL。

    【讨论】:

      猜你喜欢
      • 2012-09-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-20
      • 1970-01-01
      • 1970-01-01
      • 2013-10-12
      相关资源
      最近更新 更多