Decompile a g++ generated binary to see what is going on
输入:
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 a.cpp
反编译符号表:
readelf -s a.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
#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
#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);
}
extern "C" 是一个链接规范,用于 Cpp source files 中的 call C functions . 我们可以 call C functions, write Variables, & include headers . 函数在extern实体中声明,并在外部定义 . 语法是
Type 1:
extern "language" function-prototype
Type 2:
extern "language"
{
function-prototype
};
eg:
#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)
{
//
//
}
#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 ) .
#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);
}
可执行程序
#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);
}
13 回答
extern“C”使得C中的函数名称具有“C”链接(编译器不会破坏名称),以便客户端C代码可以使用仅包含“C”兼容头文件来链接(即使用)您的函数 . 声明你的功能 . 您的函数定义包含在二进制格式(由C编译器编译)中,客户端“C”链接器将使用“C”名称链接到该格式 .
由于C具有函数名称的重载而C没有,因此C编译器不能仅使用函数名作为链接的唯一ID,因此它通过添加有关参数的信息来破坏名称 . AC编译器不需要破坏名称,因为您不能在C中重载函数名 . 当您声明函数在C中具有extern“C”链接时,C编译器不会将参数/参数类型信息添加到用于的名称连锁 .
您知道,您可以明确指定每个单独的声明/定义的“C”链接,或使用块将一系列声明/定义分组以具有特定的链接:
如果您关心技术问题,它们列在C 03标准的7.5节中,这里是一个简短的总结(重点是外部“C”):
extern "C"是一个链接规范
每个编译器都需要提供"C"链接
链接规范仅在命名空间范围内发生
所有函数类型,函数名和变量名都有语言链接 See Richard's Comment: 只有具有外部链接的函数名和变量名具有语言链接
具有不同语言联系的两种函数类型是不同的类型,即使它们是相同的
连接规范嵌套,内部确定最终的连接
类成员将忽略
extern "C"
最多一个具有特定名称的函数可以具有"C"链接(无论命名空间如何)
extern“C”强制函数具有外部链接(不能使其静态) See Richard's comment: 'static'内'extern 70333 '有效;如此声明的实体具有内部链接,因此没有语言链接
从C到其他语言中定义的对象以及从其他语言在C中定义的对象的链接是实现定义的和语言相关的 . 只有在两种语言实现的对象布局策略足够相似的情况下才能实现这种联系
Decompile a g++ generated binary to see what is going on
输入:
使用GCC 4.8 Linux ELF输出编译:
反编译符号表:
输出包含:
Interpretation
我们看到:
ef
和eg
存储在与代码中名称相同的符号中其他符号被破坏了 . 让我们解开他们:
结论:以下两种符号类型都没有被破坏:
已定义
声明但未定义(
Ndx = UND
),在链接或运行时从另一个目标文件提供所以你在调用时都需要
extern "C"
:来自C的
g++
期望gcc
产生的未编码符号来自C的
g++
为gcc
生成未编码的符号Things that do not work in extern C
很明显,任何需要名称修改的C功能都不会在
extern C
内部进行:Minimal runnable C from C++ example
为了完整性和那里的新闻 .
从C调用C非常简单:每个C函数只有一个可能的非破坏符号,因此不需要额外的工作 .
main.cpp中:
c.h:
C.C:
跑:
如果没有
extern "C"
,链接将失败:因为
g++
期望找到一个受损的f
,其中gcc
没有产生 .Example on GitHub .
Minimal runnable C++ from C example
调用C来有点困难:我们必须手动创建我们想要公开的每个函数的非破坏版本 .
这里我们说明如何向C公开C函数重载 .
main.c中:
cpp.h:
cpp.cpp:
跑:
没有
extern "C"
它失败了:因为
g++
生成了gcc
无法找到的错位符号 .Example on GitHub .
当混合C和C时(即a . 从C调用C函数; b . 从C调用C函数),C名称修改会导致链接问题 . 从技术上讲,只有当被调用函数已经使用相应的编译器编译成二进制文件(很可能是* .a库文件)时才会出现此问题 .
因此我们需要使用extern“C”来禁用C中的名称修改 .
没有任何C-header将使用extern“C”进行编译 . 当C-header中的标识符与C关键字冲突时,C编译器会抱怨这一点 .
例如,我看到以下代码在g中失败:
有点有道理,但在将C代码移植到C时要记住这一点 .
只是想添加一些信息,因为我还没有看到它发布 .
您经常会在C标头中看到代码,如下所示:
这实现了它允许您将C头文件与C代码一起使用,因为将定义宏"__cplusplus" . 但是您仍然可以将它与未定义宏的遗留C代码一起使用,因此它不会看到唯一的C构造 .
虽然,我也看过C代码如:
我想象的完成了同样的事情 .
不知道哪种方式更好,但我已经看到了两种方式 .
它改变了函数的链接,使得函数可以从C调用 . 实际上,这意味着函数名称不是mangled .
extern "C"
是一个链接规范,用于 Cpp source files 中的 call C functions . 我们可以 call C functions, write Variables, & include headers . 函数在extern实体中声明,并在外部定义 . 语法是Type 1:
Type 2:
eg:
这个答案是针对不耐烦/有最后期限的,只有一部分/简单的解释如下:
在C中,你可以通过重载在类中具有相同的名称(例如,因为它们都是同名的,不能像dll一样导出等)这些问题的解决方案是将它们转换为不同的字符串(称为符号),符号表示函数的名称,也包括参数,因此每个函数即使具有相同的名称,也可以唯一标识(也称为名称修改)
在C中,您没有重载,函数名称是唯一的(因此,不需要用于唯一标识函数名的单独字符串,因此符号本身就是函数名)
所以
在C中,名称为每个函数的唯一标识
在C中,即使没有名称,每个函数都会对其进行唯一标识
要更改C的行为,也就是说,为了指定特定函数不应该发生名称修改,可以在函数名之前使用extern "C",无论出于何种原因,例如从dll导出具有特定名称的函数,由客户使用 .
阅读其他答案,了解更详细/更正确的答案 .
它通知C编译器在链接时以C风格查找这些函数的名称,因为在C和C中编译的函数的名称在链接阶段是不同的 .
在每个C程序中,所有非静态函数都在二进制文件中表示为符号 . 这些符号是特殊的文本字符串,用于唯一标识程序中的函数 .
在C中,符号名称与函数名称相同 . 这是可能的,因为在C中没有两个非静态函数可以具有相同的名称 .
因为C允许重载并且具有C不具有的许多功能 - 例如类,成员函数,异常规范 - 所以不可能简单地使用函数名作为符号名 . 为了解决这个问题,C使用了所谓的名称修改,它将函数名称和所有必要信息(如参数的数量和大小)转换为仅由编译器和链接器处理的奇怪字符串 .
因此,如果您将函数指定为extern C,则编译器不会对其执行名称修改,并且可以使用其符号名称作为函数名称直接访问它 .
使用
dlsym()
和dlopen()
来调用这些函数时,这很方便 .C修改函数名称以从过程语言创建面向对象的语言
大多数编程语言都不是一种使用过程编程语言构建的面向对象编程语言,因此有一些C关键字,如
extern
,它们向后兼容C语言 .我们来看下面的例子:
C编译器不会编译上面的示例,因为相同的函数
printMe
被定义了两次(即使它们具有不同的参数int a
vschar a
) .C编译器将编译上面的示例 . 它并不关心
printMe
被定义两次 .这是因为C编译器根据其参数隐式重命名(mangles)函数 . 在C中,不支持此功能 . 但是,当C构建在C语言之上时,语言被设计为面向对象的,并且需要支持使用相同名称的方法(函数)创建不同类的能力,并根据不同的参数覆盖方法(method overriding) .
外部说“不要破坏功能名称”
但是,假设我们有一个名为"parent.c"的遗留C文件
include
来自其他遗留C文件的函数名称,"parent.h","child.h"等 . 如果遗留"parent.c"文件是通过C编译器运行的,那么函数名称将被破坏,并且它们会被破坏将不再匹配"parent.h","child.h"等中指定的函数名称 - 因此这些外部文件中的函数名称也需要进行修改 . 在复杂的C程序中管理函数名称,具有很多依赖,可能导致代码损坏;所以提供一个可以告诉C编译器不会破坏函数名的关键字可能会很方便 .extern
关键字告诉C编译器不要破坏(重命名)函数名称 . 用法示例:extern void printMe(int a);
extern“C”意味着被C编译器识别并通知编译器所提到的函数是(或将)以C风格编译的 . 因此,在链接时,它从C链接到正确的函数版本 .
我使用'extern“C”'之前为dll(动态链接库)文件制作等main()函数“可导出”,以便稍后可以在dll的另一个可执行文件中使用它 . 也许我以前使用它的例子很有用 .
DLL
可执行程序