外部"C"应该包含C++函数的声明还是定义?

15

我在一个cpp文件中看到external "C" {...}括起来了几个函数的定义。

根据https://isocpp.org/wiki/faq/mixing-c-and-cpp,我猜测在cpp文件中使用extern "C"的目的是让这些被包含的C++函数可以在C程序中使用。

该链接中的示例表明,extern "C"仅用于封装C++函数的声明,而非其定义。

 

只需在C++代码中声明C++函数为 extern "C",然后从C或C++代码中调用它。例如:

    // C++ code:
    extern "C" void f(int);
    void f(int i)
    {
        // ...
    }

我在开头提到的 cpp 文件看起来是这样的:

    // C++ code:
    extern "C" {

    void f(int i)
    {
        // ...
    }

    void g(int i)
    {
        // ...
    }

    }

在C++函数的声明或定义中,是否应该使用extern "C"? 如果是,为什么?


1
@Alex 它并没有真正的 "改变 ABI",只是改变了名称混淆。 - πάντα ῥεῖ
请记住定义也是声明,可能会对您有所帮助 [dcl.link] 7.5/6 - Revolver_Ocelot
在第一种情况下,第二个函数f没有使用C命名链接。您可以通过转储生成对象的符号来检查这一点。 - xryl669
2
如果不行,那么你可能遇到了编译器的 bug。 - T.C.
@πάνταῥεῖ` "只是名称修饰" 7.5/1 "特定的语言链接可能与用于表示具有外部链接的对象和函数名称的特定形式(名称修饰-n.m.)或特定的调用约定(ABI-n.m.)相关联。即使它们在其他方面相同,具有不同语言链接的两种函数类型也是不同的类型。" 5.2.2 "通过表达式调用具有与被调用函数定义的函数类型的语言链接不同的语言链接的函数是未定义的。" - n. m.
5个回答

16
它应该将声明包含在头文件中,而定义应该在使用c++编译器编译翻译单元时被包含,并且只要在那里没有看到声明。
在c++代码中两者都不会出错。
如果使用c编译器编译函数定义,则不需要(或者更好地说是错误的语法,请参见下面的注释)。 extern "C" {}作用域控制内部使用纯c符号链接。否则,c++名称修饰将被应用。
注意:
由于extern "C" {}不是有效的C语法,为了使其能够与C编译器一起使用,您需要将其放置在#ifdef内部。

MyHeader.h:

 #ifdef __cplusplus
 extern "C" {
 #endif

 // ... c style function name declarations
 void foo(int i);

 #ifdef __cplusplus
 } // extern "C"
 #endif

extern "C" {} 作用实际上是双重的:


将C++代码导出为C代码

如果使用C编译器编译上述代码,它将作为普通的C函数声明。如果使用C++编译器编译,则应用extern关键字并且C++名称修饰将被抑制。

关于定义,该函数可以在其定义内部使用任何C++特性:

 extern "C" {
     void foo(int x) {
         std::vector v(x);
         // ... blah, more c++ stuff
     }
 }

注意这里没有包含声明。这可以作为一种技巧,特别适用于您想要覆盖从库中公开的函数弱链接的情况。

如果包括MyHeader.h,则可以省略extern "C" {}作用域。


从C++导入C代码

如果在C++编译器中看到上述声明,再次抑制C++名称修饰,并且使用普通的C函数符号名称,链接器将解析任何对foo()的调用引用:

  #include "MyHeader.h"
  class MyClass {
  public:
       void bar(int y) {
           // Use foo() as plain c function:
           foo(y);
       }
  };

foo()函数的实现是由使用C编译器创建的对象文件(或存档文件)提供的。


谢谢。在C程序中调用C++代码或在C++程序中调用C代码时,是否使用#ifdef - Tim
@Tim,“#ifdef”是指C编译器,正如所提到的,C不理解那种语法。使用“extern”的目的要么是将使用C编译器编译的代码导入到C++中,要么是将使用C++编译器编译的代码导出到C中。 - πάντα ῥεῖ
@Tim 更新了答案,我希望这能澄清你对 extern "C" {} 的疑惑。 - πάντα ῥεῖ
“应该将定义括起来”需要引用。 - n. m.
1
“plain c name mangling is used for everything inside” -- 最好说“C语言链接”,因为标准允许调用约定不同,仅限于名称混淆。 - Steve Jessop
显示剩余5条评论

8

[dcl.link]/5:

除了具有C++链接的函数外,没有链接说明符的函数声明不得在该函数的第一个链接说明符之前。在看到显式链接说明符后,可以声明一个不带链接说明符的函数;早期声明中明确指定的链接不会受到这样的函数声明的影响。

就函数的语言链接而言,这两个版本都是可以的。重要的部分是函数的第一次声明必须带有 extern "C"


2
这实际上意味着,如果一个函数在 extern "C" {} 范围内被声明过,那么定义将会引用这个声明,无论它是编译为 C++ 还是 C 代码。 - πάντα ῥεῖ

3

更好的做法应该包括两者。

为了确保在将C代码链接到C++中时,符号不会被篡改。我们使用extern "C"块。

无论何时将一些代码放入extern“C”块中,C++编译器都会确保函数名称未被篡改,即编译器生成一个二进制文件,其名称未更改,就像C编译器所做的那样。

名称重整 因为C++支持函数重载,所以基本上可能会有多个同名函数。因此,在生成对象代码时区分不同的函数 - 它通过添加有关参数的信息来更改名称。将额外信息添加到函数名称的技术称为名称重整。

由于C不支持函数重载。因此,我们在将C代码链接到C++中时使用extern 'C'块。


1
你应该同时包含声明和定义。 "C" 和 "C ++" 函数使用不同的名称进行导出。为了在对象文件中产生正确的 "C" 外部名称,需要在 cpp 中使用 extern "C" ,否则函数将被导出为 C++ name mangling 。你还需要将这些 extern "C" { 和相应的 } 包含在头文件中的 #ifdef __cplusplus#endif 中,这个头文件将被一个 C 项目 #include,以避免 C 编译错误。

在定义之前不需要使用extern "C",只有在声明之前才需要。如果定义在声明之前(根据最佳实践;不是绝对要求),名称修饰将会调整为声明。 - anatolyg
@anatolyg 我最近刚刚修复了一个项目中的链接器错误,这个错误正是由于函数从带有C++名称重整的库中导出,因此无法在纯C项目中使用。尽管相同的头文件(带有“extern C”)用于DLL编译。 - mvidelgauz

-2

同一文档中,它展示了一个代码示例,在声明中有extern "C",但在定义中没有。

如果你的定义“看到”了声明(也就是说,在翻译单元中,声明在定义之前),你不需要在定义中使用extern "C"。但这样做也不会有任何影响——编译器会默默地忽略它。

以下是FAQ中给出的代码示例:

// This is C++ code
// Declare f(int,char,float) using extern "C":
extern "C" void f(int i, char c, float x);
// ...
// Define f(int,char,float) in some C++ module:
void f(int i, char c, float x)
{
  // ...
}

如果出于任何原因,您决定在定义之前不包含声明,则必须提供extern "C"修饰符:

// This is C++ code
// Define f(int,char,float) in some C++ module:
extern "C" void f(int i, char c, float x)
{
  // ...
}

然而,这与C和C++的大多数样式指南相违背。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接