【问题标题】:Is extern "C" only required on the function declaration?仅在函数声明中需要 extern "C" 吗?
【发布时间】:2009-09-04 18:30:02
【问题描述】:

我编写了一个需要从 C 程序调用的 C++ 函数。为了使它可以从 C 中调用,我在函数 declaration 上指定了extern "C"。然后我编译了 C++ 代码,但编译器 (Dignus Systems/C++) 为函数生成了一个mangled name。所以,它显然不尊重extern "C"

为了解决这个问题,我将extern "C" 添加到函数定义。在此之后,编译器生成了一个可从 C 调用的函数名。

从技术上讲,extern "C" 只需要在函数声明中指定。这是正确的吗? (C++ FAQ 有一个很好的例子。)你是否也应该在函数定义中指定它?

这里有一个例子来证明这一点:

/* ---------- */
/* "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,以确保我知道哪种方式在技术上是“正确”的方式。

【问题讨论】:

  • 如果您在显示的示例代码中省略了 foo.c 中的 extern "C",您确定您确实得到了 foo 的修改吗?或者这只是在其他更复杂的代码中发生的事情?我经常将此问题视为忘记在 foo.c 中包含 foo.h 的症状。
  • @Brooks Moses:这很好。在我的实际代码中,它比这个“foo”示例稍微复杂一些,我在“cpp”源文件中包含了头文件。是什么让我认为编译器正在修改名称:如果我没有在函数定义中包含“extern“C””,那么编译器列表会显示外部符号“foo_FPFPCc_v”。当包含 'extern "C"' 时,列表显示外部符号 "foo"。
  • @bporter - 尝试一下编译器对您的简化示例所做的事情可能会很有趣。如果它显示相同的行为,那么您可能需要向供应商发送注释。如果它没有显示相同的内容,那么您应该跟踪实际构建中发生的情况,因为这表明引入了错误的标头(或其他原因导致函数声明被遗漏)。跨度>
  • @Michael Burr - 这是一个很好的观点,这可能是一个构建问题(例如,包含其他一些标题等)。我会尝试一下,如果我学到任何可能对其他人有用的新东西,我会在这里发表评论。谢谢!

标签: c++ c extern extern-c


【解决方案1】:

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

在看到显式链接规范后,可以在没有链接规范的情况下声明函数;前面声明中明确指定的链接不受此类函数声明的影响。

不过,我通常也会在定义上加上“extern "C"”,因为它实际上是一个带有外部“C”链接的函数。很多人讨厌在声明中出现不必要的冗余内容(例如将 virtual 放在方法覆盖上),但我不是其中之一。

【讨论】:

  • @Michael Burr - 感谢您的回复。听起来定义中不需要“外部“C””,只要它在先前的声明中指定即可。就像您提到的那样,我可能会继续在两个地方都指定它。 (事实上​​,除非我发现我的问题不是编译器错误,否则我可能需要这样做。如果它看起来确实像编译器错误,那么我会将其报告给供应商,以便他们进行调查。)
  • 我认为我是唯一一个将 virtual 放在方法覆盖上的人 :-)
【解决方案2】:

编辑:
好像我误解了这个问题。 不管怎样,我试过了:


// 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" 的函数名称没有被破坏,并且在定义时不需要 extern "C" 关键字。
如果它需要每个没有使用 extern "C" 编写的 C 库代码,在 C++ 程序中将无法使用。

【讨论】:

  • @Neeraj - 感谢您的示例。这也是我期望在我的案例中看到的。除非我在声明和定义中指定“extern“C””,否则我的编译器似乎会破坏“foo”函数名。我认为您和其他一些发帖人是正确的,只要在先前的声明中指定了“外部“C””,定义中就不需要它。
【解决方案3】:

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

在我的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) {                                                                                  
    ...
}

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

【讨论】:

    【解决方案4】:

    我认为这需要在这里澄清一下,因为我刚刚遇到了类似的问题,我花了一段时间才弄清楚这一点,只有 Brooks Moses 正确地谈到了这一点,我认为需要进一步说明明明……

    总而言之,头文件可能会让你失望,编译器看到的只是 cpp 文件,如果头文件没有包含在 extern "C" 中回到你的 cpp(我通常看到),那么 extern "C"将需要在 cpp 文件中的某个位置(在定义中或另一个声明中),以便 CXX 编译器可以知道如何使用 C 链接,编译器不关心标头,只关心链接器。

    【讨论】:

      【解决方案5】:

      定义周围的extern "C" 不是必需的。你可以把它放在声明周围。您的示例中的一个注释...

      #ifdef __cplusplus
      extern "C" {
      #endif
      
      /* Function declaration */
      void foo(int);
      
      #ifdef __cplusplus
      }
      #endif
      

      您的代码正在寻找预处理器宏“__cplusplus”。

      虽然它通常被实现,但取决于您的编译器,这可能会或可能不会被定义。在您的示例中,您还在声明周围使用了extern "C",但是您没有检查“__cplusplus”宏,这就是为什么我怀疑它在您这样做后会起作用的原因。

      请参阅下面的 cmets — 标准 C++ 要求由预处理器定义 __cplusplus 宏。

      【讨论】:

      • @whitej - "__cplusplus" 宏恰好是由我的特定编译器定义的,但你说得对,它不是“标准的”。另外,我在头文件中使用了这个宏,因为这是我声明函数的地方(也是大多数人希望了解接口的地方),它允许我在 C 和 C++ 文件中包含头文件。 C 编译器不支持'extern "C"',所以这个宏只在合适的时候插入'extern "C"'。请注意,如果指定了 'extern "C"',则 "foo.cpp" 中不需要宏,因为 "foo.cpp" 将始终由 C++ 编译器编译。
      • 标准要求在编译 C++ 模块时定义“__cplusplus”宏 - 使用它绝对没有问题。
      • 虽然 "__cplusplus" 通常在标头中是必需的(因为它们可能包含在 C 或 C++ 模块中),但在 .c 或 .cpp 文件中通常不需要,因为它们通常是设计的编译为 C 或 C++,但不能同时编译(当然也有例外,但通常不会)。
      • @Michael Burr - 我一直认为“__cplusplus”是理所当然的,但我没有意识到它是标准要求的。感谢您澄清这一点。
      【解决方案6】:

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

      现在,如果您有一个可从定义所在的翻译单元中看到的 extern-C 声明,您也许可以不使用定义中的 extern-C,但我不知道那是肯定的。

      【讨论】:

      • 这在所描述的情况下是不正确的,其中 foo.h 包含在 foo.c 中,并且“逍遥法外”会误导使用标准要求的行为。如果在 foo.c 中包含 foo.h - 无论如何都应该这样做,以便编译器可以检查声明实际上是否准确! -- 那么就没有必要(除了读者可能清楚) -- 将 extern "C" 放在 foo.c 中的定义上。
      猜你喜欢
      • 2019-07-14
      • 2019-04-17
      • 2020-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-12
      相关资源
      最近更新 更多