有什么影响的外部"C"C++?
-
22-07-2019 - |
题
这到底是什么把 extern "C"
C++编码做什么?
例如:
extern "C" {
void foo();
}
解决方案
extern "C" 使 C++ 中的函数名称具有“C”链接(编译器不会破坏名称),以便客户端 C 代码可以使用仅包含“C”兼容头文件链接到(即使用)您的函数你的函数的声明。您的函数定义包含在二进制格式中(由 C++ 编译器编译),然后客户端“C”链接器将使用“C”名称链接到该二进制格式。
由于 C++ 具有函数名称重载,而 C 没有,因此 C++ 编译器不能仅使用函数名称作为链接到的唯一 id,因此它会通过添加有关参数的信息来破坏名称。C 编译器不需要修改名称,因为不能重载 C 中的函数名称。当您在 C++ 中声明函数具有 extern "C" 链接时,C++ 编译器不会将参数/参数类型信息添加到用于链接的名称中。
正如您所知,您可以显式指定每个单独的声明/定义的“C”链接,或者使用块对一系列声明/定义进行分组以具有特定的链接:
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
如果您关心技术细节,它们列在 C++03 标准的 7.5 节中,这里是一个简短的摘要(重点是 extern "C"):
- extern "C" 是链接规范
- 每个编译器都是 必需的 提供“C”连接
- 链接规范应仅出现在名称空间范围内
所有函数类型、函数名称和变量名称都具有语言链接请参阅理查德的评论: 只有具有外部链接的函数名和变量名才具有语言链接- 具有不同语言链接的两个函数类型是不同的类型,即使其他方面相同
- 联动规格嵌套,里面的一个决定了最终的联动
- 类成员的 extern "C" 被忽略
- 至多一个具有特定名称的函数可以具有“C”链接(无论命名空间如何)
extern "C" 强制函数具有外部链接(不能使其静态)请参阅理查德的评论: 'extern "C"' 内的 'static' 有效;如此声明的实体具有内部链接,因此不具有语言链接- 从 C++ 到其他语言定义的对象以及从其他语言到 C++ 定义的对象的链接是实现定义的且依赖于语言。只有两种语言实现的对象布局策略足够相似才能实现这种链接
其他提示
只是想增加一点信息,因为我还没有看到它尚未公布。
您会经常看到代码的C头文件,像这样:
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
这是什么实现的是,它可以让你使用C头文件,你的C ++代码,因为宏“__cplusplus”将被定义。但你可以的也的仍使用旧的C代码,其中宏的不是的定义,所以它不会看到使用它的独特的C ++构建。
虽然,我也看到C ++代码,例如:
extern "C" {
#include "legacy_C_header.h"
}
我想象完成同样的事情。
不知道哪种方式更好,但我已经看到了这两者。
在每个C ++程序,所有的非静态功能在二进制文件作为符号表示。这些符号是特殊的文本字符串,用于唯一标识该程序的功能。
在C,符号名是相同的函数名。这是可能的,因为在C中没有两个非静态函数可以具有相同的名称。
由于C ++允许重载和具有C不许多功能 - 如类,成员函数,异常规范 - 这是不能够简单地使用函数名作为符号名称。为了解决这个问题,C ++使用所谓的名称重整,其将函数名和所有必需的信息(像的参数的数目和尺寸)成仅由编译器和链接器处理一些奇怪的前瞻性字符串。
因此,如果你指定一个函数是外部C,编译器不执行名称与它重整和它可以直接 使用它的符号名称作为函数名访问。
此同时使用dlsym()
和dlopen()
调用这样的功能来得心应手。
反编译一个 g++
生成的二进制文件看看发生了什么
主程序
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 编译 极低频 输出:
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"
两者都在调用时:
- 来自 C++ 的 C:告诉
g++
期望由以下方式生成未损坏的符号gcc
- 来自 C 的 C++:告诉
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
为了完整起见以及对于新手来说,另请参阅: 如何在C++项目中使用C源文件?
从 C++ 调用 C 非常简单:每个 C 函数只有一个可能的非重整符号,因此不需要额外的工作。
主程序
#include <cassert>
#include "c.h"
int main() {
assert(f() == 1);
}
ch.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
抄送
#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 公开 C++ 函数重载。
主程序
#include <assert.h>
#include "cpp.h"
int main(void) {
assert(f_int(1) == 2);
assert(f_float(1.0) == 3);
return 0;
}
程序文件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
程序文件
#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 中测试。
C++见函数名创建一个面向对象的语言,从程序的语言
最编程语言不是建立在现有的编程语言。C++是建立在顶的C,此外它是一个面向对象编程语言建立一个程序编程语言,并为这个原因,有C++的表情喜欢 extern "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
).
海湾合作委员会-o printMe printMe.c&&./printMe;
1错误。PrintMe定义超过一次。
C++编译器的将汇编上述的例子。它不关心 printMe
定义的两倍。
g++-o printMe printMe.c&&./printMe;
这是因为一个C++编译器隐含地重新命名(见)的功能基于他们的参数。在C,这一要素没有得到支持。然而,当C++是建立在C、语言设计是面向对象,并需要支持创造能力不同类别的方法(功能)的相同的名称,并为替代方法(方法的压倒一切的)基于不同的参数。
extern "C"
说"不轧C功能的名字"
然而,想象我们有一个遗留C的文件,名为"父母。c", include
s函数名来自其他遗留C的文件,"父母。h","孩子。h",等等。如果遗产"的父母。c"文件通过一个C++编译器,那么功能名称将会出错,并且他们将不再相匹配功能的指定的名称中的"父母。h","孩子。h",等等-所以能在这些外部文件还将需要错位。重整能跨越一个复杂的C节目,这些有很多的依赖性,可能导致破码;所以它可能是方便提供一个关键字可以告诉C++编译器不要损坏功能名称。
的 extern "C"
关键字告诉C++编译器不轧(重新命名)C功能名称。例的使用情况: extern "C" void printMe(int a);
它改变联系的一个函数,在这样一种方式的功能是可调用从C。在实践中意味着功能的名字是不是 错位.
不任何C-头可通过在外部的“C”仅仅包裹与C兼容++。当标识符用C C-头冲突++关键字C ++编译器会抱怨。
例如,我看到下面的代码无法在一克++:结果
extern "C" {
struct method {
int virtual;
};
}
有点儿是有道理的,但是东西移植C代码到C ++时要记住。
它通知C ++编译器来查找的那些功能的名称在链接时C型,因为在C和C ++编译函数的名称是在链接阶段不同。
外部的“C”是指由C ++编译器识别并通知编译器该注意到的功能是(或将要)用C风格编译。所以,虽然联,它由C链接到的功能的正确版本。
我使用的“外部的‘C’”之前,DLL(动态链接库)文件,以使等main()函数“导出”,因此它可以在稍后从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);
}
extern "C"
是用于为呼叫C函数强>在 .cpp的源文件强>连杆规范。我们可以的呼叫C函数,写变量,及包括标头即可。功能是在实体的extern声明和它的定义之外。语法是
<强>类型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)
{
//
//
}
这个答案是针对不耐烦/有最后期限的人的,下面只是部分/简单的解释:
- 在 C++ 中,您可以通过重载在类中具有相同的名称(例如,因为它们都是相同的名称,因此无法从 dll 中按原样导出等)。这些问题的解决方案是将它们转换为不同的字符串(称为符号) ),符号记录了函数的名称,也记录了参数,因此每个函数即使具有相同的名称,也可以被唯一标识(也称为名称修改)
- 在C中,你没有重载,函数名称是唯一的(因此,不需要用于唯一标识函数名称的单独字符串,因此符号就是函数名称本身)
所以
在 C++ 中,名称修饰唯一标识每个函数
在 C 中,即使没有名称修饰,每个函数也能唯一标识
改变C++的行为,即指定名称修改 不应该 对于特定功能发生,您可以使用 外部“C” 在函数名称之前,无论出于何种原因,例如从 dll 中导出具有特定名称的函数以供其客户端使用。
阅读其他答案,以获得更详细/更正确的答案。
当混合C和C ++(即,从C ++主叫C函数;和b。调用C从C ++函数)时,C ++名称重整原因联的问题。从技术上讲,当调用函数必须使用相应的编译器已经被编译成二进制(最有可能的,一个* .A库文件)这个问题只发生。
因此,我们需要利用外部的“C”,以禁用名称用C ++压延