将外部“C”放入C++代码中具体做什么?

例如:

extern "C" {
   void foo();
}

当前回答

在每个C++程序中,所有非静态函数都在二进制文件中表示为符号。这些符号是唯一标识程序中函数的特殊文本字符串。

在C中,符号名与函数名相同。这是可能的,因为在C中没有两个非静态函数可以具有相同的名称。

因为C++允许重载,并且具有许多C不具备的特性——比如类、成员函数、异常规范——所以不可能简单地使用函数名作为符号名。为了解决这个问题,C++使用了所谓的名称篡改,它将函数名和所有必要的信息(如参数的数量和大小)转换为一些只有编译器和链接器才能处理的奇怪字符串。

因此,如果将函数指定为extern C,编译器不会对其执行名称修改,而是可以直接使用其符号名作为函数名进行访问。

这在使用dlsym()和dlopen()调用此类函数时很方便。

其他回答

分解一个g++生成的二进制文件,看看发生了什么

主.cpp

void f() {}
void g();

extern "C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

编译并反汇编生成的ELF输出:

g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o

输出包含:

     8: 0000000000000000     7 FUNC    GLOBAL DEFAULT    1 _Z1fv
     9: 0000000000000007     7 FUNC    GLOBAL DEFAULT    1 ef
    10: 000000000000000e    17 FUNC    GLOBAL DEFAULT    1 _Z1hv
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
    13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

理解

我们看到:

ef和eg存储在与代码中同名的符号中其他符号都被弄乱了。让我们解开它们:$c++过滤器_Z1fvf()$c++过滤器_Z1hvh()$c++过滤器_Z1gvg()

结论:以下两种符号类型均未受损:

定义已声明但未定义(Ndx=UND),将在链接或运行时从另一个对象文件提供

因此,在调用以下两个函数时,都需要外部“C”:

C++中的C:告诉g++期望gcc生成的未成文法的符号来自C的C++:告诉g++生成未成文法的符号供gcc使用

在extern C中不起作用的东西

很明显,任何需要更改名称的C++特性都不会在外部C中工作:

extern "C" {
    // Overloading.
    // error: declaration of C function ‘void f(int)’ conflicts with
    void f();
    void f(int i);

    // Templates.
    // error: template with C linkage
    template <class C> void f(C i) { }
}

C++示例中的最小可运行C

为了完整性和新手,请参阅:如何在C++项目中使用C源文件?

从C++调用C非常简单:每个C函数只有一个可能的非损坏符号,因此不需要额外的工作。

主.cpp

#include <cassert>

#include "c.h"

int main() {
    assert(f() == 1);
}

c.h

#ifndef C_H
#define C_H

/* This ifdef allows the header to be used from both C and C++ 
 * because C does not know what this extern "C" thing is. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

c.c

#include "c.h"

int f(void) { return 1; }

Run:

g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out

如果没有外部“C”,链接将失败:

main.cpp:6: undefined reference to `f()'

因为g++希望找到一个损坏的f,而gcc并没有产生。

GitHub上的示例。

C示例中的最小可运行C++

从C调用C++有点困难:我们必须手动创建要公开的每个函数的未损坏版本。

这里我们说明如何将C++函数重载暴露给C。

主.c

#include <assert.h>

#include "cpp.h"

int main(void) {
    assert(f_int(1) == 2);
    assert(f_float(1.0) == 3);
    return 0;
}

每小时

#ifndef CPP_H
#define CPP_H

#ifdef __cplusplus
// C cannot see these overloaded prototypes, or else it would get confused.
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif

#endif

cpp.cpp

#include "cpp.h"

int f(int i) {
    return i + 1;
}

int f(float i) {
    return i + 2;
}

int f_int(int i) {
    return f(i);
}

int f_float(float i) {
    return f(i);
}

Run:

gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out

如果没有外部“C”,则失败:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

因为g++生成了gcc找不到的损坏符号。

GitHub上的示例。

当我包含c++中的c头时,外部“c”在哪里?

C++版本的C头文件(如csdio)可能依赖于#pragma GCC system_headerhttps://gcc.gnu.org/onlinedocs/cpp/System-Headers.html提到:“在一些目标上,例如RS/6000AIX,当编译为C++时,GCC隐式地用‘extern‘C‘块包围所有系统头”,但我没有完全确认。像/usr/include/unistd.h这样的POSIX头包含在:我需要一个外部“C”块来包含标准的POSIX C头吗?通过__BEGIN_DECLS,在Ubuntu 20.04上再现__BEGIN_DECLS通过#include<features.h>包含。

在Ubuntu 18.04中测试。

extern“C”是指由C++编译器识别,并通知编译器注意到的函数是(或将)以C样式编译的,因此在链接时,它链接到来自C的函数的正确版本。

在每个C++程序中,所有非静态函数都在二进制文件中表示为符号。这些符号是唯一标识程序中函数的特殊文本字符串。

在C中,符号名与函数名相同。这是可能的,因为在C中没有两个非静态函数可以具有相同的名称。

因为C++允许重载,并且具有许多C不具备的特性——比如类、成员函数、异常规范——所以不可能简单地使用函数名作为符号名。为了解决这个问题,C++使用了所谓的名称篡改,它将函数名和所有必要的信息(如参数的数量和大小)转换为一些只有编译器和链接器才能处理的奇怪字符串。

因此,如果将函数指定为extern C,编译器不会对其执行名称修改,而是可以直接使用其符号名作为函数名进行访问。

这在使用dlsym()和dlopen()调用此类函数时很方便。

仅仅通过在外部“C”中包装,并不能使任何C标头与C++兼容。当C标头中的标识符与C++关键字冲突时,C++编译器会对此进行投诉。

例如,我看到以下代码在g++中失败:

extern "C" {
struct method {
    int virtual;
};
}

Kinda是有道理的,但在将C代码移植到C++时需要记住。

请参阅下面的链接,该链接是Geeks for Geeks对外部“C”用法的解释。从下面的页面添加重要信息。

考虑函数f()的以下声明

int  f (void) { return 1; }
int  f (int)  { return 0; }
void g (void) { int i = f(), j = f(0); }

C++编译器可能会将上述名称更改为以下名称(来源:Wiki)

int  __f_v (void) { return 1; }
int  __f_i (int)  { return 0; }
void __g_v (void) { int i = __f_v(), j = __f_i(0); }

https://www.geeksforgeeks.org/extern-c-in-c/