外部"C"在C++中有什么影响?

Lit*_*rum 1511 c c++ name-mangling linkage extern-c

究竟什么extern "C"放入C++代码呢?

例如:

extern "C" {
   void foo();
}
Run Code Online (Sandbox Code Playgroud)

Fai*_*ali 1462

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;
}
Run Code Online (Sandbox Code Playgroud)

如果您关心技术细节,它们列在C++ 03标准的7.5节中,这里是一个简短的摘要(重点是extern"C"):

  • extern"C"是一个链接规范
  • 每个编译器都需要提供"C"链接
  • 链接规范只应在命名空间范围内发生
  • 所有函数类型,函数名和变量名都有语言链接 请参阅Richard的注释:只有具有外部链接的函数名和变量名具有语言链接
  • 具有不同语言链接的两种函数类型是不同的类型,即使它们是相同的
  • 连接规格嵌套,内部确定最终的连接
  • 类成员忽略extern"C"
  • 最多一个具有特定名称的函数可以具有"C"链接(无论命名空间如何)
  • extern"C"强制函数具有外部链接(不能使其静止) 请参阅Richard的注释: 'static'在'extern'中"C"'有效; 如此声明的实体具有内部链接,因此没有语言链接
  • 从C++到在其他语言中定义的对象以及从其他语言在C++中定义的对象的链接是实现定义的和语言相关的.只有在两种语言实现的对象布局策略足够相似的情况下才能实现这种联系

  • @Faisal:不要尝试链接使用不同C++编译器构建的代码,即使交叉引用都是'extern"C"'.类的布局或用于处理异常的机制或用于确保变量在使用前初始化的机制或其他此类差异之间通常存在差异,此外,您可能需要两个单独的C++运行时支持库(一个用于每个编译器). (54认同)
  • C编译器不使用c ++所做的修改.因此,如果您想从c ++程序调用ac接口,则必须明确声明c接口为"extern c". (18认同)
  • '所有函数类型,函数名和变量名都有语言链接'也是不正确的.只有具有外部链接的函数名和变量名才具有语言链接. (14认同)
  • 'extern"C"强制一个函数有外部链接(不能使它静止)'是不正确的.'extern'内部'static'有效; 如此声明的实体具有内部链接,因此没有语言链接. (8认同)
  • 注意`extern"C"{int i; }是一个定义.这可能不是你想要的,在"void g(char);"的非定义旁边.要使它成为非定义,你需要`extern"C"{extern int i; }`.在另一方面,没有括号的一个声明语法确实作出声明的非定义:`的extern"C" INT I;`相同`的extern"C" {的extern INT I; }` (6认同)
  • @Leffler - 谢谢,你说得好点.我不是故意鼓励使用extern"C"来使用不同的C++编译器.相反,我希望建议如果你没有写一些需要由另一个C++编译器链接的东西,你可能不需要extern"C". (5认同)
  • 没有提到召集会议?没有提到标准要求在函数指针上使用`extern"C"`修饰符来调用C连接函数(尽管在实践中很多实现都不关心)? (5认同)
  • @Litherum - 如果您不打算通过不同的C++编译器或任何C编译器或某些动态语言(ruby,python等)链接到您正在开发扩展的代码 - 您不需要extern"C ".它没有给你买任何东西. (2认同)
  • 理查德实际上错了.语言链接影响调用约定,标准没有说明是否可以使用未特别声明为指向`extern"C"`类型的函数指针来调用`extern"C"函数.函数类型DO具有语言链接.也就是说,我从未见过一个利用标准允许的灵活性的实现在这个领域. (2认同)

Unc*_*lby 298

只是想添加一些信息,因为我还没有看到它发布.

您经常会在C标头中看到代码,如下所示:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif
Run Code Online (Sandbox Code Playgroud)

这实现了它允许您将C头文件与C++代码一起使用,因为将定义宏"__cplusplus".但是你可以仍然使用旧的C代码,其中宏使用NOT定义,所以它不会看到独特的C++构建.

虽然,我也看过C++代码,例如:

extern "C" {
#include "legacy_C_header.h"
}
Run Code Online (Sandbox Code Playgroud)

我想象的完成了同样的事情.

不确定哪种方式更好,但我已经看到了两种方式.

  • @Anne:那不对,第一个也没关系.它被C编译器忽略,并且与C++中的第二个具有相同的效果.编译器在包含标头之前或之后是否遇到`extern"C"`时不会在意.当它到达编译器时,它只是一个长的预处理文本流. (17认同)
  • 有一个明显的区别.对于前者,如果使用普通的gcc编译器编译此文件,它将生成一个不损坏函数名的对象.然后,如果您使用链接器链接C和C++对象,它将找不到这些函数.您需要在第二个代码块中包含带有extern关键字的"遗留标头"文件. (10认同)
  • @Anne:C++编译器也会查找未编码的名称,因为它在标题中看到了`extern"C"`.效果很好,多次使用这种技术. (7认同)
  • @Anne,不,我认为你已经受到源中其他一些错误的影响,因为你所描述的是错误的.至少在过去17年的任何时候,没有任何版本的`g ++`对任何目标都有这个错误.第一个例子的重点是,无论你是使用C还是C++编译器,都不会对`extern"C"`块中的名称进行名称修改. (7认同)
  • "哪一个更好" - 当然,第一个变体更好:它允许在C和C++代码中直接包含标题,无需任何进一步的要求.第二种方法是C头的解决方法,作者忘记了C++警卫(没问题,但是,如果之后添加了这些,嵌套的extern"C"声明是可接受的......). (6认同)
  • 为什么说 C 是“遗产”? (2认同)
  • @JL2210 是啊,当**你**变老的时候就这么说!!“1.形容词:表示或涉及已被取代但由于广泛使用而难以取代的软件或硬件。” 它只是*也*是一个*事实*,它是许多现代编程语言的基础(不一定*伟大*,请注意)如果*也*不*古老*,您就不可能成为现代语言的基础。 (2认同)

Cir*_*四事件 218

反编译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(); }
Run Code Online (Sandbox Code Playgroud)

使用GCC 4.8 Linux ELF输出编译:

g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o
Run Code Online (Sandbox Code Playgroud)

反编译符号表:

     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
Run Code Online (Sandbox Code Playgroud)

输出包含:

$ c++filt _Z1fv
f()
$ c++filt _Z1hv
h()
$ c++filt _Z1gv
g()
Run Code Online (Sandbox Code Playgroud)

解释

我们看到:

  • efeg存储在与代码中名称相同的符号中

  • 其他符号被破坏了.让我们解开他们:

    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) { }
    }
    
    Run Code Online (Sandbox Code Playgroud)

结论:以下两种符号类型都没有被破坏:

  • 定义
  • 声明但未定义(Ndx = UND),在链接或运行时从另一个目标文件提供

所以你extern "C"在打电话时都需要两个:

  • 来自C++的C:告诉g++我期望生成的无符号符号gcc
  • 来自C的C++:告诉g++生成gcc要使用的未编码符号

在extern C中不起作用的事情

很明显,任何需要名称修改的C++特性都无法在内部工作extern C:

#include <cassert>

#include "c.h"

int main() {
    assert(f() == 1);
}
Run Code Online (Sandbox Code Playgroud)

来自C++示例的最小可运行C

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

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

main.cpp中

#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
Run Code Online (Sandbox Code Playgroud)

CH

#include "c.h"

int f(void) { return 1; }
Run Code Online (Sandbox Code Playgroud)

CC

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
Run Code Online (Sandbox Code Playgroud)

跑:

main.cpp:6: undefined reference to `f()'
Run Code Online (Sandbox Code Playgroud)

没有extern "C"链接失败:

#include <assert.h>

#include "cpp.h"

int main(void) {
    assert(f_int(1) == 2);
    assert(f_float(1.0) == 3);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

因为g++期望找到一个受损的f,gcc没有产生.

GitHub上的示例.

来自C示例的最小可运行C++

从中调用C++有点困难:我们必须手动创建我们想要公开的每个函数的非破坏版本.

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

main.c中

#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
Run Code Online (Sandbox Code Playgroud)

cpp.h

#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 Code Online (Sandbox Code Playgroud)

cpp.cpp

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
Run Code Online (Sandbox Code Playgroud)

跑:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'
Run Code Online (Sandbox Code Playgroud)

没有extern "C"它失败了:

void f() {}
void g();

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

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }
Run Code Online (Sandbox Code Playgroud)

因为g++生成了gcc无法找到的错位符号.

GitHub上的示例.

在Ubuntu 18.04中测试过.

  • 最好的答案,因为你1)明确提到`extern"C"{`帮助你从C++程序中调用**unmangled C函数**,以及来自C程序内的**unmangled C++函数**,其他答案不要不要那么明显,*和*2)因为你展示了每个的不同例子.谢谢! (15认同)
  • 我非常喜欢这个答案 (2认同)
  • 给出最佳答案,因为它显示了如何从c调用重载函数 (2认同)

sud*_*03r 199

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

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

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

因此,如果您将函数指定为extern C,则编译器不会对其执行名称修改,并且可以使用其符号名称作为函数名称直接访问它.

这在使用dlsym()dlopen()调用此类函数时非常方便.

  • @错误:是的。在一般情况下,在仅给出头文件的情况下 dlopen() C++ 共享库并选择要加载的正确函数基本上是不可能的。(在 x86 上,有一个以 Itanium ABI 形式发布的名称重整规范,我知道的所有 x86 编译器都使用该规范来重整 C++ 函数名称,但该语言中没有任何要求这样做。) (3认同)

tfm*_*gue 44

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;
}
Run Code Online (Sandbox Code Playgroud)

AC编译器不会编译上面的例子,因为相同的函数printMe定义了两次(即使它们具有不同的参数int avs char a).

gcc -o printMe printMe.c && ./printMe;
1错误.PrintMe定义不止一次.

C++编译器将编译上面的示例.它并不关心printMe定义两次.

g ++ -o printMe printMe.c && ./printMe;

这是因为C++编译器根据其参数隐式重命名(mangles)函数.在C中,不支持此功能.但是,当C++是基于C构建的时,该语言被设计为面向对象的,并且需要支持使用相同名称的方法(函数)创建不同类的能力,并且基于不同的方法覆盖方法(方法重写)参数.

Extern说"不要破坏功能名称"

但是,假设我们有一个名为"parent.c" extern "C"的遗留C文件,它来自其他遗留C文件的函数名称,"parent.h","child.h"等.如果运行旧版"parent.c"文件通过C++编译器,函数名称将被破坏,它们将不再匹配"parent.h","child.h"等中指定的函数名称 - 因此这些外部文件中的函数名称也需要被破坏了.在复杂的C程序中管理函数名称,那些具有大量依赖性的程序可能导致代码损坏; 因此提供一个可以告诉C++编译器不会破坏函数名的关键字可能会很方便.

include关键字告诉C++编译器不要破坏(重命名)函数名称.用法示例:extern "C"


Emp*_*ian 27

它改变函数的链接,使得函数可以从C调用.实际上,这意味着函数名称不会被修改.

  • Mangled是一般使用的术语......不要相信我曾经见过'装饰'这个含义. (3认同)
  • 微软(至少部分)使用[decorated](https://docs.microsoft.com/en-us/cpp/build/reference/decorated-names?view=vs-2019)而不是在他们的文档中进行破坏。他们甚至将他们的工具命名为“undname”。 (3认同)

San*_*ens 25

通过仅包装extern"C",不能使任何C-header与C++兼容.当C-header中的标识符与C++关键字冲突时,C++编译器会抱怨这一点.

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

extern "C" {
struct method {
    int virtual;
};
}
Run Code Online (Sandbox Code Playgroud)

Kinda有意义,但在将C代码移植到C++时要记住这一点.

  • `extern"C"`表示使用C链接,如其他答案所述.它并不意味着"将内容编译为C"或任何东西.`int virtual;`在C++中无效,指定不同的链接不会改变它. (12认同)
  • ...或模式通常,任何包含语法错误的代码都不会编译。 (2认同)
  • @ValentinHeinitz自然,尽管在C中使用“虚拟”作为标识符不是语法错误。我只想指出,您不能通过在外部加上“ C”来自动使用C ++中的* any * C头。 (2认同)

Mar*_*off 19

它通知C++编译器在链接时以C风格查找这些函数的名称,因为在C和C++中编译的函数的名称在链接阶段是不同的.


小智 12

extern"C"意味着被C++编译器识别,并通知编译器所提到的函数是(或将)以C风格编译的.因此,在链接时,它从C链接到正确的函数版本.


小智 7

我使用'extern"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);
}
Run Code Online (Sandbox Code Playgroud)

可执行程序

#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);
}
Run Code Online (Sandbox Code Playgroud)

  • 虚假.`extern"C"`和`__declspec(dllexport)`是无关的.前者控制符号装饰,后者负责创建导出条目.您也可以使用C++名称修饰导出符号.除了完全忽略了这个问题的要点之外,代码示例中还有其他错误.首先,从您的DLL导出的`main`不会声明返回值.或者就此而言,召集大会.导入时,属性是一个随机调用约定(`WINAPI`),并使用错误的符号进行32位构建(应该是`_main`或`_main @ 0`).对不起,-1. (3认同)
  • 这只是重复,你不知道,你在做什么,但这样做似乎对你有用,对于一些未公开的目标平台列表。你没有解决我在之前的评论中提出的问题。由于大错特错,这仍然是一个反对票(还有更多,不适合在一条评论中)。 (2认同)
  • 在 Stack Overflow 上发布答案有点暗示您知道自己在做什么。这是预期的。至于您尝试 *“防止运行时堆栈损坏”*:您的函数签名指定了类型为 `void*` 的返回值,但您的实现不返回任何内容。真的会飞的很好... (2认同)
  • 如果你实现了一些 ** 看起来** 工作的东西,纯粹靠运气,那么你显然 **不** 知道你在做什么(你的 *“工作”* 样本属于那个类别)。这是未定义的行为,看起来有效是未定义行为的有效形式。它仍然未定义。如果您将来更加勤奋,我将不胜感激。部分原因可能是删除这个建议的答案。 (2认同)
  • 您正在将不返回任何内容的函数重新解释为返回指针的函数。很幸运,x86 对不匹配的函数签名非常宽容,尤其是整数类型的返回值。您的代码只是巧合。如果你不同意,你需要解释为什么你的代码可靠地工作。 (2认同)

Yog*_*H T 5

extern "C"是一个链接规范,用于调用Cpp源文件中的C函数.我们可以调用C函数,编写变量,并包含头文件.函数在extern实体中声明,并在外部定义.语法是

类型1:

extern "language" function-prototype
Run Code Online (Sandbox Code Playgroud)

类型2:

extern "language"
{
     function-prototype
};
Run Code Online (Sandbox Code Playgroud)

例如:

#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)
{
    //
    //
}
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

724660 次

最近记录:

5 年,10 月 前