将extern "C"
放入C ++代码到底是做什么的?
例如:
extern "C" {
void foo();
}
答案 0 :(得分:1382)
extern“C”使得C ++中的函数名称具有“C”链接(编译器不会破坏名称),以便客户端C代码可以使用“C”兼容头文件链接到(即使用)您的函数仅包含函数的声明。您的函数定义包含在二进制格式(由C ++编译器编译)中,客户端“C”链接器将使用“C”名称链接到该格式。
由于C ++有函数名称的重载而C没有,因此C ++编译器不能只使用函数名作为链接的唯一id,因此它通过添加有关参数的信息来破坏名称。 AC编译器不需要破坏名称,因为您不能在C中重载函数名。当您声明函数在C ++中具有extern“C”链接时,C ++编译器不会将参数/参数类型信息添加到用于的名称键。
您知道,您可以明确指定每个声明/定义的“C”链接,或使用块将一系列声明/定义分组以具有特定的链接:
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
如果您关心技术问题,它们列在C ++ 03标准的7.5节中,这里是一个简短的摘要(重点是外部“C”):
答案 1 :(得分:279)
只是想添加一些信息,因为我还没有看到它发布。
你会经常看到C标题中的代码如下:
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
这实现了它允许您将C头文件与C ++代码一起使用,因为将定义宏“__cplusplus”。但是还仍然可以将它与遗留的C代码一起使用,其中宏 NOT 已定义,因此它不会看到唯一的C ++构造。
虽然,我也看过C ++代码,如:
extern "C" {
#include "legacy_C_header.h"
}
我想象的完成了同样的事情。
不确定哪种方式更好,但我已经看到了两种方式。
答案 2 :(得分:195)
在每个C ++程序中,所有非静态函数都在二进制文件中表示为符号。这些符号是特殊的文本字符串,用于唯一标识程序中的函数。
在C中,符号名称与函数名称相同。这是可能的,因为在C中没有两个非静态函数可以具有相同的名称。
因为C ++允许重载并且具有C不具备的许多功能 - 比如类,成员函数,异常规范 - 所以不可能简单地使用函数名作为符号名。为了解决这个问题,C ++使用了所谓的名称修改,它将函数名称和所有必要信息(如参数的数量和大小)转换为仅由编译器和链接器处理的奇怪字符串。
因此,如果您指定一个函数为extern C,编译器不会使用它执行名称修改,它可以直接 使用其符号名称作为函数名称进行访问。
使用dlsym()
和dlopen()
来调用此类函数时,这很方便。
答案 3 :(得分:183)
反编译g++
生成的二进制文件,看看发生了什么
的main.cpp
void f() {}
void g();
extern "C" {
void ef() {}
void eg();
}
/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }
使用GCC 4.8 Linux ELF输出编译:
g++ -c main.cpp
反编译符号表:
readelf -s main.o
输出包含:
Num: Value Size Type Bind Vis Ndx Name
8: 0000000000000000 6 FUNC GLOBAL DEFAULT 1 _Z1fv
9: 0000000000000006 6 FUNC GLOBAL DEFAULT 1 ef
10: 000000000000000c 16 FUNC GLOBAL DEFAULT 1 _Z1hv
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND eg
<强>解释强>
我们看到了:
ef
和eg
存储在与代码中名称相同的符号中
其他符号被破坏了。让我们解开他们:
$ c++filt _Z1fv
f()
$ c++filt _Z1hv
h()
$ c++filt _Z1gv
g()
结论:以下两种符号类型都不损坏:
Ndx = UND
),在链接或运行时从另一个目标文件提供因此,在致电:
时,您将需要extern "C"
g++
期望由gcc
g++
为gcc
生成未使用的符号在extern C中无效的事情
很明显,任何需要名称修改的C ++功能都无法在extern 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
为了完整性和新手,请参阅:How to use C source files in a C++ project?
从C ++调用C非常简单:每个C函数只有一个可能的非破坏符号,因此不需要额外的工作。
的main.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++. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif
#endif
C.C
#include "c.h"
int f(void) { return 1; }
执行命令
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
如果没有extern "C"
,则链接会失败:
main.cpp:6: undefined reference to `f()'
因为g++
期望找到一个错误的f
,gcc
没有产生。
来自C示例的最小可运行C ++
从中调用C ++有点困难:我们必须手动创建我们想要公开的每个函数的非破坏版本。
这里我们将说明如何将C ++函数重载暴露给C。
的main.c
#include <assert.h>
#include "cpp.h"
int main(void) {
assert(f_int(1) == 2);
assert(f_float(1.0) == 3);
return 0;
}
cpp.h
#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);
}
执行命令
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
没有extern "C"
它失败了:
main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'
因为g++
生成了gcc
无法找到的错位符号。
在Ubuntu 18.04中测试过。
答案 4 :(得分:39)
大多数编程语言不是在现有编程语言的基础上构建的。 C ++是在C语言之上构建的,而且它是一种使用过程编程语言构建的面向对象的编程语言,因此有像extern "C"
这样的C ++表达式向后兼容C语言。
让我们看看下面的例子:
#include <stdio.h>
// Two functions are defined with the same name
// but have different parameters
void printMe(int a) {
printf("int: %i\n", a);
}
void printMe(char a) {
printf("char: %c\n", a);
}
int main() {
printMe("a");
printMe(1);
return 0;
}
C编译器不会编译上面的示例,因为相同的函数printMe
被定义了两次(即使它们具有不同的参数int a
vs char a
)。
gcc -o printMe printMe.c&amp;&amp; ./printMe;
1错误。 PrintMe定义不止一次。
C ++编译器将编译上面的示例。它并不关心printMe
被定义两次。
g ++ -o printMe printMe.c&amp;&amp; ./printMe;
这是因为C ++编译器根据其参数隐式重命名(mangles)函数。在C中,不支持此功能。但是,当C ++是基于C构建的时,该语言被设计为面向对象的,并且需要支持使用相同名称的方法(函数)创建不同类的能力,并覆盖方法(method overriding)基于不同的参数。
extern "C"
说“不要破坏C函数名称”但是,假设我们有一个名为“parent.c”的遗留C文件,include
来自其他遗留C文件的函数名称,“parent.h”,“child.h”等。如果遗留下来的话“parent.c”文件通过C ++编译器运行,然后函数名称将被破坏,它们将不再匹配“parent.h”,“child.h”等中指定的函数名称 - 所以函数名称在那些外部文件中也需要进行修改。在复杂的C程序中管理函数名称,那些具有大量依赖性的程序可能会导致代码损坏;所以提供一个可以告诉C ++编译器不要破坏函数名的关键字可能会很方便。
extern "C"
关键字告诉C ++编译器不要破坏(重命名)C函数名称。用法示例:extern "C" void printMe(int a);
答案 5 :(得分:26)
它改变函数的链接,使得函数可以从C调用。实际上,这意味着函数名称不是mangled。
答案 6 :(得分:25)
只需包装extern“C”,就不能使任何C-header与C ++兼容。当C-header中的标识符与C ++关键字冲突时,C ++编译器会抱怨这一点。
例如,我看到以下代码在g ++中失败:
extern "C" {
struct method {
int virtual;
};
}
有点有道理,但在将C代码移植到C ++时要记住这一点。
答案 7 :(得分:19)
它通知C ++编译器在链接时以C风格查找这些函数的名称,因为在C和C ++中编译的函数的名称在链接阶段是不同的。
答案 8 :(得分:12)
extern“C”意味着由C ++编译器识别,并通知编译器所提到的函数是(或将)以C风格编译的。因此,在链接时,它链接到C的正确版本的函数。
答案 9 :(得分:6)
我使用了&#34; extern&#34; C&#34;&#39;之前为dll(动态链接库)文件制作等main()函数&#34;可导出&#34;所以它可以在以后用于dll的另一个可执行文件中。 也许我以前使用它的一个例子很有用。
DLL
#include <string.h>
#include <windows.h>
using namespace std;
#define DLL extern "C" __declspec(dllexport)
//I defined DLL for dllexport function
DLL main ()
{
MessageBox(NULL,"Hi from DLL","DLL",MB_OK);
}
EXE
#include <string.h>
#include <windows.h>
using namespace std;
typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll
Function mainDLLFunc;//make a variable for function placeholder
int main()
{
char winDir[MAX_PATH];//will hold path of above dll
GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe
strcat(winDir,"\\exmple.dll");//concentrate dll name with path
HINSTANCE DLL = LoadLibrary(winDir);//load example dll
if(DLL==NULL)
{
FreeLibrary((HMODULE)DLL);//if load fails exit
return 0;
}
mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main");
//defined variable is used to assign a function from dll
//GetProcAddress is used to locate function with pre defined extern name "DLL"
//and matcing function name
if(mainDLLFunc==NULL)
{
FreeLibrary((HMODULE)DLL);//if it fails exit
return 0;
}
mainDLLFunc();//run exported function
FreeLibrary((HMODULE)DLL);
}
答案 10 :(得分:5)
extern "C"
是一个链接规范,用于调用C函数 Cpp源文件。我们可以调用C函数,写变量,&amp;包含标题。函数在extern实体和&amp;中声明。它在外面定义。语法是
类型1:
extern "language" function-prototype
类型2:
extern "language"
{
function-prototype
};
<强>例如强>
#include<iostream>
using namespace std;
extern "C"
{
#include<stdio.h> // Include C Header
int n; // Declare a Variable
void func(int,int); // Declare a function (function prototype)
}
int main()
{
func(int a, int b); // Calling function . . .
return 0;
}
// Function definition . . .
void func(int m, int n)
{
//
//
}
答案 11 :(得分:2)
这个答案是针对急躁的人/有最后期限的,下面仅是部分/简单的解释:
所以
在C ++中,通过名称修饰唯一地标识每个函数
在C语言中,即使没有名称,也无法唯一标识每个函数的身份
要更改C ++的行为,即要为特定功能指定名称改写 ,可以在功能前使用 extern“ C” 名称,无论出于何种原因,例如从dll导出具有特定名称的函数以供其客户端使用。
阅读其他答案,以获得更详细/更正确的答案。
答案 12 :(得分:1)
当混合使用C和C ++(即,从C ++调用C函数;以及b。从C调用C ++函数)时,C ++名称修改会导致链接问题。从技术上讲,只有当被调用函数已经使用相应的编译器编译成二进制文件(很可能是一个* .a库文件)时才会出现此问题。
所以我们需要使用extern&#34; C&#34;在C ++中禁用名称修改。
答案 13 :(得分:0)
在不与其他好的答案冲突的情况下,我将添加一些示例。
C ++编译器的确切作用是:它会在编译过程中破坏名称,因此我们需要告诉编译器专门进行 treat C
实现。
制作C ++类并添加extern "C"
时,是在告诉C ++编译器我们正在使用C调用约定。
原因(我们正在从C ++调用C实现):要么我们想从C ++调用C函数,要么从C调用C ++函数(C ++类...在C中不起作用)。
答案 14 :(得分:0)
由C编译器编译的函数void f()和由C ++编译器编译的具有相同名称的函数void f()是不同的函数。如果您使用C编写了该函数,然后尝试从C ++调用它,则链接器将查找C ++函数,但找不到C函数。
extern“ C”告诉C ++编译器您有一个由C编译器编译的函数。一旦告诉它它是由C编译器编译的,则C ++编译器将知道如何正确调用它。
它还允许C ++编译器以C编译器可以调用它的方式来编译C ++函数。该函数正式是C函数,但是由于它是由C ++编译器编译的,因此它可以使用所有C ++功能并具有所有C ++关键字。
答案 15 :(得分:-1)
请参阅下面的链接,该链接是 geeks for geeks 解释 extern "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); }