外部“C”是不是应包含 C++ 函数的声明或定义?
Posted
技术标签:
【中文标题】外部“C”是不是应包含 C++ 函数的声明或定义?【英文标题】:Shall external "C" enclose the declaration or definition of a C++ function?外部“C”是否应包含 C++ 函数的声明或定义? 【发布时间】:2016-06-20 16:23:01 【问题描述】:我在一个 cpp 文件中看到 external "C" ...
包含了几个函数的定义。
从https://isocpp.org/wiki/faq/mixing-c-and-cpp,我猜想在cpp 文件中使用extern "C"
的目的是为了使封闭的C++ 函数可以在C 程序中使用。
链接中的示例显示extern "C"
仅包含 C++ 函数的声明,而不包含它们的定义
只需声明 C++ 函数 extern "C"(在您的 C++ 代码中)并调用 它(来自您的 C 或 C++ 代码)。例如:
// C++ code: extern "C" void f(int); void f(int i) // ...
我一开始提到的cpp文件看起来是这样的:
// C++ code:
extern "C"
void f(int i)
// ...
void g(int i)
// ...
extern "C"
应包含 C++ 函数的声明或定义吗?
如果有,为什么?
【问题讨论】:
@Alex 它并没有真正更改 ABI,只是名称修改。 请记住,定义也是一个声明,[dcl.link] 7.5/6 可能会很有趣。 在第一种情况下,第二个函数 f 没有使用 C 命名链接。您可以通过转储生成对象的符号来检查这一点。 @xryl669 如果没有,你有一个编译器错误。 @πάνταῥεῖ` “只是名称 mangling” 7.5/1 “特定语言链接可能与表示具有外部链接的对象和函数名称的特定形式相关联(mangling---n.m.),或使用特定的调用约定(ABI---n.m.)”。 “具有不同语言链接的两种函数类型是不同的类型,即使它们在其他方面是相同的。” 5.2.2 “通过函数类型具有与被调用函数定义的函数类型的语言链接不同的语言链接的表达式调用函数未定义”。 【参考方案1】:它应该将声明包含在头文件中,并且只要翻译单元是使用 c++ 编译器编译的,并且只要在其中没有看到声明,就应该包含定义。 在 c++ 代码中两者都做永远不会错。
如果使用 c 编译器来编译函数定义,则没有必要(或者我应该说是错误的语法,请参阅下面的注释)。
extern "C"
范围控制纯 c 符号链接用于内部所有内容。否则将应用c++ name mangling。
注意:
由于 extern "C"
这不是有效的 c 语法,要使其与 c 编译器一起使用,您需要在 #ifdef
中使用它:
MyHeader.h
:
#ifdef __cplusplus
extern "C"
#endif
// ... c style function name declarations
void foo(int i);
#ifdef __cplusplus
// extern "C"
#endif
extern "C"
作用域的使用实际上是双重的:
将 C++ 代码导出到 C
如果上面是用 c 编译器编译的,它会显示为一个普通的 c 函数声明。如果使用 c++ 编译器编译,则应用 extern
关键字,并且 c++ 名称修改将被禁止。
关于定义,该函数可以使用其定义中的任何 c++ 特性:
extern "C"
void foo(int x)
std::vector v(x);
// ... blah, more c++ stuff
请注意,此处未包含声明。这可以用作一种技术,当您想要覆盖从库中为weak linkage 公开的函数时特别有用。
如果包含MyHeader.h
,则可以省略extern "C"
范围。
从 C++ 导入 C 代码
如果在 c++ 编译器中看到上述声明,则再次抑制 c++ 名称修改,并且任何对 foo()
的调用引用都将由链接器使用纯 c 函数符号名称解析:
#include "MyHeader.h"
class MyClass
public:
void bar(int y)
// Use foo() as plain c function:
foo(y);
;
foo()
函数实现由使用 c 编译器创建的目标文件(或存档)提供。
【讨论】:
谢谢。#ifdef
是在C程序中调用C++代码时使用,还是在C++程序中调用C代码时使用?
@Tim #ifdef
指的是 c 编译器,如前所述,c 不理解该语法。 extern
的用途要么是将c编译器编译的代码导入c++,要么将c++编译器编译的代码导出到c。
谢谢。只是一个不相关的问题,C++ 编译器是否可以识别#ifdef
?换句话说,C++ 编译器是否也使用 C 编译器中使用的指令?
@Tim Sure 该指令将在 c++ 编译器中评估为 true exclusively。不要将 c 预处理器与编译器语言变体混淆。
"plain c name mangling is used for everything inside" -- 更好的说法是“C 语言链接”,因为标准允许不同的调用约定以及名称修饰。跨度>
【参考方案2】:
[dcl.link]/5:
除了具有 C++ 链接的函数外,函数声明没有 链接规范不应在第一个链接之前 该功能的规范。函数可以在没有 a 的情况下声明 显式链接规范之后的链接规范 见过;前面声明中明确指定的链接是 不受此类函数声明的影响。
就功能的语言链接而言,两个版本都很好。重要的是函数的第一个声明必须有extern "C"
。
【讨论】:
所以这实际上意味着,如果之前在extern "C"
范围内声明了一个函数,那么无论它是编译为c++ 还是c 代码,定义都会引用这个声明?`【参考方案3】:
最好两者都包含。
当我们在 C++ 中链接 C 代码时,确保符号不会被破坏。我们使用 extern "C" 块。
每当一些代码放在外部“C”块中时,C++ 编译器都会确保函数名称未被破坏,即编译器生成一个名称不变的二进制文件,就像 C 编译器所做的那样。
Mangling由于C++支持函数重载,所以基本上可以有多个同名函数。因此,为了在生成目标代码时区分不同的函数——它通过添加有关参数的信息来更改名称。向函数名称添加附加信息的技术称为名称修饰。
由于 C 不支持函数重载。所以我们在 C++ 中链接 C 代码时使用 extern 'C' 块。
【讨论】:
【参考方案4】:您应该同时附上声明和定义。 “C”和“C++”函数以不同的名称导出。在 cpp 中需要在目标文件 extern "C"
中生成正确的“C”外部名称,否则将使用 C++ name mangling 导出函数。您还需要将这些extern "C"
和相应的 包含在头文件中的
#ifdef __cplusplus
和#endif
中,这将被C 项目#included 以避免C 编译错误
【讨论】:
extern "C"
在定义之前不是必需的;只有在声明之前才有必要。如果定义在声明之前(根据最佳实践;不是绝对要求),则修改将调整为声明。
@anatolyg 我最近刚刚修复了一些项目中的链接器错误,正是由这个问题引起的 - 函数是从具有 C+ 名称修改的库中导出的,因此不能在纯 C 项目中使用。尽管相同的头文件(带有 "extern "C" )被用于 DLL 编译【参考方案5】:
在same document 中,它显示了一个代码示例,该代码示例在声明中包含extern "C"
,但在定义中没有。
如果您的定义“看到”声明(即,声明在翻译单元中的定义之前),您不需要在定义上需要extern "C"
。但它不会受到伤害 - 编译器会默默地忽略它。
这是常见问题解答中给出的代码示例:
// This is C++ code
// Declare f(int,char,float) using extern "C":
extern "C" void f(int i, char c, float x);
// ...
// Define f(int,char,float) in some C++ module:
void f(int i, char c, float x)
// ...
如果您出于任何原因决定不在定义之前包含声明,您必须提供extern "C"
修饰符:
// This is C++ code
// Define f(int,char,float) in some C++ module:
extern "C" void f(int i, char c, float x)
// ...
但是,这违反了 C 和 C++ 的大多数样式准则。
【讨论】:
以上是关于外部“C”是不是应包含 C++ 函数的声明或定义?的主要内容,如果未能解决你的问题,请参考以下文章
C++ Visual Studio Windows:如何在 dll 中声明但不定义外部函数