What is the effect of extern “C” in C++?
将EDOCX1 0的代码放入C++代码中究竟做了什么?
例如:
1 2 3 | extern"C" { void foo(); } |
ExtEnter"C"使C++中的函数名具有"C"链接(编译器不修改名称),以便客户端C代码可以链接到(即使用)您的函数,使用一个"C"兼容的头文件,该文件只包含函数的声明。您的函数定义以二进制格式(由C++编译器编译),客户端C链接器将链接到使用"C"名称。
由于C++具有函数名的超载,C没有,C++编译器不能只使用函数名作为唯一的ID来链接,所以它通过添加关于参数的信息来修改名称。C编译器不需要修改名称,因为当您在C++中声明一个函数具有"外部""C"链接时,不能在C.中重载函数名,C++编译器不将参数/参数类型信息添加到用于链接的名称。
正如您所知道的,您可以显式地指定到每个单独的声明/定义的"C"链接,或者使用块将声明/定义序列分组以具有特定链接:
1 2 3 4 5 6 | extern"C" void foo(int); extern"C" { void g(char); int i; } |
如果你关心技术性,它们被列在C++ 03标准的第7.5节中,这里是一个简短的摘要(着重于外部的C):
- 外部"C"是一个链接规范
- 每个编译器都需要提供"C"链接
- 链接规范只能出现在命名空间范围内。
所有函数类型、函数名和变量名都有语言链接参见Richard的注释:只有具有外部链接的函数名和变量名才有语言链接- 具有不同语言链接的两个函数类型是不同的类型,即使在其他方面相同
- 连杆规格嵌套,内部决定最终连杆
- 类成员忽略外部"c"
- 最多一个具有特定名称的函数可以具有"c"链接(不考虑命名空间)
extern"c"forces a function to have external linkage(c an not make it static)see Richard's comment:'static'inside'extern"c"is valid;an entity so declared has internal linkage,and so does not have a language linkage- 从C++到其他语言定义的对象以及从其他语言中用C++定义的对象的链接是实现定义和语言依赖的。只有当两种语言实现的对象布局策略足够相似时,才能实现这种链接。
只是想添加一些信息,因为我还没有看到它发布。
您经常会在C头中看到这样的代码:
1 2 3 4 5 6 7 8 9 | #ifdef __cplusplus extern"C" { #endif // all of your legacy C code here #ifdef __cplusplus } #endif |
实现的是,它允许您使用C++代码使用C头文件,因为宏定义为"y*cPLUS PLUS"。但是,您还可以将它与您的遗留C代码一起使用,宏没有被定义,因此它不会看到唯一的C++构造。
虽然,我也见过C++代码,例如:
1 2 3 | extern"C" { #include"legacy_C_header.h" } |
我想,它的作用是一样的。
不知道哪条路更好,但我都看过。
在每个C++程序中,所有非静态函数都以二进制文件的形式表示为符号。这些符号是唯一标识程序中函数的特殊文本字符串。
在C语言中,符号名与函数名相同。这是可能的,因为在C中没有两个非静态函数可以具有相同的名称。
因为C++允许重载,并且有许多C不喜欢类、成员函数、异常规范的特性,所以不可能简单地使用函数名作为符号名。为了解决这个问题,C++使用所谓的名字过滤,它将函数名和所有必要的信息(如参数的数目和大小)转换成仅由编译器和链接器处理的一些奇怪的字符串。
因此,如果您指定一个函数为extern c,编译器不会用它执行名称管理,它可以直接使用其符号名作为函数名访问。
这在使用
对生成的
主CPP
1 2 3 4 5 6 7 8 9 10 | void f() {} void g(); extern"C" { void ef() {} void eg(); } /* Prevent g and eg from being optimized away. */ void h() { g(); eg(); } |
使用GCC4.8 Linux ELF输出编译:
1 | g++ -c main.cpp |
反编译符号表:
1 | readelf -s main.o |
输出包括:
1 2 3 4 5 6 | 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 存储在与代码同名的符号中。其他的符号被破坏了。让我们解开它们:
1
2
3
4
5
6$ c++filt _Z1fv
f()
$ c++filt _Z1hv
h()
$ c++filt _Z1gv
g()
结论:以下两种符号类型均未损坏:
- 定义
- 声明但未定义(
Ndx = UND ),在链接或运行时从另一个对象文件提供
因此,您在拨打以下电话时需要同时使用
- C从C++中告诉EDCOX1×0,以期待EDCOX1×6产生的未加密符号。
- C中的C++:告诉EDCOX1×0 }以生成EDCOX1(6)使用的未加密符号。
在外部C中不起作用的东西
很明显,任何需要名称修改的C++特性在EDOCX1 9中都不起作用:
1 2 3 4 5 6 7 8 9 10 | 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
为了完整性和NeWB,在C++项目中如何使用C源文件?
从C++调用C非常简单:每个C函数只有一个可能的非标记符号,因此不需要额外的工作。
主CPP
1 2 3 4 5 6 7 | #include <cassert> #include"c.h" int main() { assert(f() == 1); } |
C.H
1 2 3 4 5 6 7 8 9 10 11 12 13 | #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
1 2 3 | #include"c.h" int f(void) { return 1; } |
运行:
1 2 3 4 | 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 |
如果没有
1 | main.cpp:6: undefined reference to `f()' |
因为
Github上的示例。
C实例中的最小可运行C++
调用C++是有点困难的:我们必须手动创建我们想要公开的每个函数的非破坏版本。
这里说明了如何将C++函数重载暴露为C。
主C
1 2 3 4 5 6 7 8 9 | #include #include"cpp.h" int main(void) { assert(f_int(1) == 2); assert(f_float(1.0) == 3); return 0; } |
CPP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #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); } |
运行:
1 2 3 4 | 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 |
如果没有
1 2 | main.c:6: undefined reference to `f_int' main.c:7: undefined reference to `f_float' |
因为
Github上的示例。
在Ubuntu 18.04中测试。
从程序语言创建面向对象的语言
大多数编程语言并没有在现有编程语言的顶端建立。C++is built on-top of C,and furtherre is an object-oriented programming language built from a procedural programming language,and for reason there C+++keywords like EDOCX1>0
Let's look at the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <stdio.h> // Two functions are defined with the same name // but have different parameters void printMe(int a) { printf("int: %i ", a); } void printMe(char a) { printf("char: %c ", a); } int main() { printMe("a"); printMe(1); return 0; } |
a C编译器将不编译上述例子,因为同一函数
gcc -o printMe printMe.c && ./printMe;
1 error. PrintMe is defined more than once.
a C+compiler will compile the above example.它不在乎是确定的两个。
BLCK1/
这是因为一个C++编译隐含重命名(芒果)函数基于它们的参数。在C类中,这一特征没有得到支持。然而,当C++被建成在C上时,语言的设计是客观的,并且需要支持以不同参数为基础的创建不同类别(功能)的能力。
Extern says"Don't mangle function names"
然而,想象一下,如果遗产"Parent.c"File is run through a C++compiler,那么功能名称将被删除,而功能名称将不再与功能名称"Parent.h","Parent.h","Child.h"等名称匹配。UNCCtion names in those external files would also need to be mangled.芒果函数的名称跨越复杂的C程序,其中有许多依赖性,可以引导破译代码;因此,它可能适合于提供一个关键词,它可以告诉C++编译器而不是芒果函数的名称。
关键词告诉C++编译器,而不是芒果(重命名)函数的名称。使用范例:EDOCX1&7
它改变函数的链接,使函数可以从C调用。在实践中,这意味着函数名不会损坏。
没有任何C头可以通过只在外部"C"中封装而与C++兼容。当C标头中的标识符与C++关键字冲突时,C++编译器会抱怨这一点。
例如,我看到以下代码在G++中失败:
1 2 3 4 5 | extern"C" { struct method { int virtual; }; } |
有点有意义,但是在将C代码移植到C++时要记住一些东西。
它通知C++编译器在链接时以C样式查找这些函数的名称,因为在链接阶段,C和C++中编译的函数的名称不同。
外部C是指由C++编译器识别并通知编译器所指出的函数是(或将)以C方式编译的。这样,当链接时,它从C链接到正确版本的函数。
我在前面使用了"extern"c",将dll(动态链接库)文件设置为等。main()函数"exportable",以便以后在dll的另一个可执行文件中使用。也许一个我曾经使用它的例子是有用的。
动态链接库
1 2 3 4 5 6 7 8 9 10 11 | #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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #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); } |
类型1:
1 | extern"language" function-prototype |
类型2:
1 2 3 4 | extern"language" { function-prototype }; |
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #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) { // // } |
This answer is for the insext/have deadlines to meet to,only a part/simple explanation is below:
- In C++,you can have same name in class via overloading(example,since they are all same name can't exported as-is from DLL,etc).These problems is converted to different strings(called symbols),symbols accounts the name of function,also the examination,so each of these functions even with same name,can be uniqueIdentified(also called,name mangling)
- 在C中,你没有过载,函数名称是独一无二的(因此,不需要一个函数名称独一无二的分隔字符串,所以符号是函数名称本身)。
So在C++中,名称芒果单一的识别每个功能在C中,即使没有名字曼格林独特的身份每个功能
为了改变C++的行为,具体地说,MANGLING的名称不应发生某个特定功能,在函数名称之前,您可以使用"C"以外的函数,以任何理由,比如从DLL导出一个特定名称的函数,以供客户使用。
阅读其他答案,以便提供更详细/更正确的答案。
When mixing C and C+(I.E.,A.Calling C function from C++;and B.Calling C+++function from C),the C+++name mangling causes linking problems.Technically speaking,this issue happens only when the callee functions have already compiled into binary(most likely,a*a library file)using the corresponding compiler.
所以我们需要使用外部"C"来解读"C+"中的名字。