2009-09-04 90 views
25

我写了一个C++函数,我需要从C程序调用。为了使它可以从C中调用,我在功能声明上指定了extern "C"。然后我编译了C++代码,但编译器(Dignus Systems/C++)为该函数生成了一个mangled name。所以,它显然没有兑现extern "C"仅在函数声明中需要extern“C”吗?

要解决此问题,我将extern "C"添加到函数定义。在此之后,编译器生成可从C中调用的函数名称。从技术上讲,extern "C"只需要在函数声明中指定。这是正确的吗? (C++ FAQ Lite就是一个很好的例子。)你是否也应该在函数定义中指定它?

下面就来演示这样的例子:

/* ---------- */ 
/* "foo.h" */ 
/* ---------- */ 

#ifdef __cplusplus 
extern "C" { 
#endif 

/* Function declaration */ 
void foo(int); 

#ifdef __cplusplus 
} 
#endif 

/* ---------- */ 
/* "foo.cpp" */ 
/* ---------- */ 

#include "foo.h" 

/* Function definition */ 
extern "C"    // <---- Is this needed? 
void foo(int i) { 
    // do something... 
} 

我的问题可能是不正确编码的东西的结果,或者我可能已经找到了编译器错误。无论如何,我想咨询一下stackoverflow,以确保我知道哪个技术上是“正确”的方式。

+4

如果您在示例代码中显示foo.c中的外部“C”,您确定实际上获得了foo的篡改吗?或者这只是其他更复杂的代码中发生的事情?我经常将这个问题看作是忘记在foo.c中包含foo.h的一个症状。 – 2009-09-04 19:01:26

+0

@布鲁克斯摩西:这是一个很好的观点。在我的实际代码中,比这个“foo”示例稍微复杂一点,我在“cpp”源文件中包含头文件。 是什么让我觉得编译器改变名字是这样的:如果我没有在函数定义中包含'extern“C”',那么编译器列表显示外部符号“foo_FPFPCc_v”。当包含“extern”C“'时,列表显示外部符号”foo“。 – bporter 2009-09-04 19:41:58

+1

@bporter - 尝试使用简化示例的编译器可能会很有趣。如果它显示相同的行为,那么您可能需要向供应商发送便条。如果它没有显示相同的内容,那么你应该跟踪你真正的构建中发生了什么,因为这会表明错误的头部被引入(或者其他的东西使得函数声明被遗漏)。 – 2009-09-04 19:53:24

回答

29

只要声明具有它并且已经在定义的编译中看到,就不应该在函数定义上需要'extern "C"'。该标准具体规定了(7.5/5联动规格):

可以在看到明确的联动规格后声明没有联动规格的功能;前面声明中明确指定的链接不受此类函数声明的影响。

不过,我一般不把“extern "C"”上把定义为好,因为它实际上是在与外部的“C”键的功能。很多人讨厌不必要的时候,多余的东西在声明上(比如把virtual放在方法覆盖上),但我不是其中之一。

+0

@Michael Burr - 感谢您的回复。听起来像'extern'C''在定义中是不需要的,只要它是在前面的声明中指定的。就像你提到的那样,我可以继续在两个地方指定它。 (事实上​​,除非我发现我的问题不是编译器错误,否则我可能需要,如果它看起来像编译器错误,那么我会将它报告给供应商,以便他们可以查看它。) – bporter 2009-09-04 20:23:27

+0

我认为我是唯一一个在方法上覆盖'virtual'的人:-) – Mawg 2017-03-16 08:38:41

0

它应该在两者之间。编译器需要知道在编译调用站点(它可能只看到一个声明)时使用C符号名称和调用约定,并且编译器还需要知道生成C符号名称并在编译时使用C调用约定函数定义本身(可能看不到任何其他声明)。

现在,如果您有一个可从定义存在的翻译单元中看到的extern-C声明,那么您可能能够从定义中离开extern-C,但我没有当然知道。

+3

在所描述的情况下这是不正确的,其中foo.h包含在foo.c中,并且“脱离”会误导使用标准所需的行为。如果在foo.c中包含foo.h - 正如人们总是应该做的那样,以便编译器可以检查该声明实际上是否准确! - 那么就没有必要(除了读者可能更清楚) - 将外部“C”放在foo.c中的定义上。 – 2009-09-04 18:58:03

1

编辑:
好像我误解了这个问题。 不管怎么说,我想:


// foo.cpp 
/* Function definition */ 

#include "foo.h" 

void foo(int i) { 
//do stuff 
} 
void test(int i) 
{ 
// do stuff 
} 

// foo.h 
#ifdef __cplusplus 
extern "C" { 
#endif 

/* Function declaration */ 
void foo(int); 

#ifdef __cplusplus 
} 
#endif 

void test(int); 

使用命令nm查看从编译的文件中的符号:


linuxuser$ nm foo.o 
00000006 T _Z4testi 
     U __gxx_personality_v0 
00000000 T foo 

这清楚地表明,函数声明为extern“C”这个名字是不是错位和定义时不需要外部“C”关键字。
如果需要每个C库代码都不用extern,那么C将在C++程序中无法使用。

+0

@Neeraj - 感谢你的例子。这也是我期望在我的案例中看到的。看来我的编译器会损坏“foo”函数名称,除非我在声明和定义中指定了'extern“C”'。我认为你和其他一些海报是正确的,只要在先前的声明中指定了'extern'C''就不需要定义。 – bporter 2009-09-04 20:10:44

0

围绕定义的extern "C"不是必需的。你可以把它放在宣言中。一个音符在你的榜样......

#ifdef __cplusplus 
extern "C" { 
#endif 

/* Function declaration */ 
void foo(int); 

#ifdef __cplusplus 
} 
#endif 

你的代码是寻找预处理器宏“__cplusplus”。

虽然它通常实现,但根据您的编译器,这可能会或可能不会被定义。在你的例子中,你也在声明周围使用extern "C",但是你在不是检查“__cplusplus”宏,这就是为什么我怀疑它一旦你这样做的原因。

参见下面的评论 - 标准C++需要由预处理器中定义的宏__cplusplus

+0

@whitej - “__cplusplus”宏恰好由我的特定编译器定义,但你说得对,它不是“标准”。另外,我在头文件中使用这个宏,因为这是我声明函数的地方(并且这是大多数人希望了解该接口的地方),它允许我在C和C++文件中包含头文件。 C编译器不支持“extern”C“',所以这个宏只在适当的时候插入'extern”C“'。请注意,如果指定了“extern”C“',则在”foo.cpp“中不需要该宏,因为”foo.cpp“将始终由C++编译器编译。 – bporter 2009-09-04 20:59:42

+4

编译C++模块时需要定义标准所需的“'__cplusplus'”宏 - 使用它绝对没有问题。 – 2009-09-04 22:45:49

+1

尽管头文件中经常需要“__cplusplus”(因为它们可能包含在C或C++模块中),但它通常不需要在.c或.cpp文件中,因为它们通常被设计为编译为C或C++ ,但不是两者都有(当然也有例外,但通常不会)。 – 2009-09-04 22:48:58

1

刚刚遇到这种情况......不愉快的经历。

以下是以我c文件中的一个声明:

void unused_isr(void) {} 
void ADC_IRQHandler(void)  __attribute__ ((weak, alias("unused_isr"))); 

接下来,在cpp文件中,我定义的地方:

void ADC_IRQHandler(void) {                     
    ... 
} 

我忘了向前声明更改为:

void ADC_IRQHandler(void); 

我花了一段时间才想出来我在AD转换方面做得很好,但是我没有给定义添加“extern C”!

extern "C" void ADC_IRQHandler(void) {                     
    ... 
} 

只是我的两分钱为什么它在某些情况下可能有用,有习惯将它添加到定义中。

1

我想这里需要澄清一下,因为我刚刚有一个类似的问题,并花了一段时间才弄清楚我的脑海中,只有布鲁克斯摩西触及这一点,我认为这需要说明更清楚地...

总之,头文件可能会让你失望,所有编译器看到的都是cpp文件,如果头文件没有包含在你的cpp(我通常会看到)的extern“C”中,那么extern“C”将需要位于cpp文件的某处(无论是在定义中还是其他声明中),因此CXX编译器可以知道使用C链接进行编译,编译器不关心头部,只有链接器。