int f(float x){}
#ifdef __cplusplus
/////////////////////////////////////////////////
// these f overloads are visible only to C++ compilers.
// A C compiler would not accept two functions with the same
// name.
int f(int x){}
int f(void){}
void g(void){}
/////////////////////////////////////////////////
#endif
#ifdef __cplusplus
/////////////////////////////////////////////////
// this part is visible only to C++ compilers
extern "C" int h(float){}
/////////////////////////////////////////////////
#endif
#ifdef __cplusplus
///////////////////////////////////////////////////
// This part, including an opening brace,
// is only visible to C++ compilers
extern "C"
{ // everything in this block are treated as
// C declarations/definitions.
/////////////////////////////////////////////////
#endif
/////////////////////////////////////////////////
// this part is visible to both C and C++ compilers
int i(void){}
/////////////////////////////////////////////////
#ifdef __cplusplus
/////////////////////////////////////////////////
// This brace is again only visible to C++ compilers
// (which have seen the opening brace above as well).
// C compilers would be confused by a closing brace out of nowhere
// because they did not see the opening brace.
} // closes the extern "C" block
/////////////////////////////////////////////////
#endif
$ gcc -c -o ccompiled.o overloaded-funcs.c && nm --defined-only ccompiled.o
0000000000000000 b .bss
0000000000000000 d .data
0000000000000000 p .pdata
0000000000000000 r .rdata$zzz
0000000000000000 t .text
0000000000000000 r .xdata
0000000000000000 T f
000000000000000b T i
$ g++ -E overloaded-funcs.c
# 1 "overloaded-funcs.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "overloaded-funcs.c"
int f(float x){}
int f(int x){}
int f(void){}
void g(void){}
# 20 "overloaded-funcs.c"
extern "C" int h(float){}
# 30 "overloaded-funcs.c"
extern "C"
{
# 40 "overloaded-funcs.c"
int i(void){}
# 50 "overloaded-funcs.c"
}
相关的代码行再次得到了很好的强调 .
然后我将其编译为C并检查目标文件中的名称
$ g++ -c -o cppcompiled.o overloaded-funcs.c && nm --defined-only cppcompiled.o
0000000000000000 b .bss
0000000000000000 d .data
0000000000000000 p .pdata
0000000000000000 r .rdata$zzz
0000000000000000 t .text
0000000000000000 r .xdata
0000000000000000 T _Z1ff
000000000000000b T _Z1fi
0000000000000014 T _Z1fv
000000000000001a T _Z1gv
0000000000000020 T h
000000000000002b T i
函数名称变得复杂得多 - "mangled" . 我们仍然可以在名称中间看到"actual"名称 f 和 g ,前缀为_Z1 . 显然,参数类型只是编码为"actual" name部分后面的void,int和float的单个字母v,i和f .
请注意,返回值不是生成的函数名称的一部分,这意味着链接器通常不知道它(通常没有除目标文件之外的其他信息) . 这与语言规则一致,即返回值不考虑重载解析(并且不可能有两个同名的函数,它们只在返回值上有所不同) . 就链接器而言, f 的重载版本是完全不相关的函数 .
我们还可以看到声明为 extern "C" 的函数具有旧的C名称( h 和 i ) . 另一个翻译单元中的C代码可以声明这些函数并使用它们,链接器将找到该符号( i 或 h ),解析依赖关系并将函数的代码添加到可执行文件中 . 但是,这样的C代码不能链接到函数 f ,因为只要链接器可以看到我们的目标文件中不存在这样的函数 .
2 回答
我仍然不确定哪个部分有问题,所以我会解释两者 .
extern "C"
告诉编译器函数是C函数 . 区别主要在于两种语言内部命名/识别函数的方式; C在函数名中有参数类型"mangled",它在内部用于查找正确的函数(请记住,重载函数是链接器 - 这可能是C不可知的 - 正常的,不同命名的函数) . 如果编译器是C编译器,#ifdef
/#endif
对只是跳过extern "C"
(和结束大括号) . 这是必要的,因为extern "C"
不是C语言的一部分,有点矛盾,编译器会发出错误 .我在this paper找到了g和VC名称的解释(这是8年之久,所以细节可能已经改变了,但一般概念已经很好了) .
我用cygwin gcc / g进行了快速测试 . 考虑以下文件overloaded-funcs.c:
首先,我使用gcc的-E选项查看预处理器输出,以了解实际编译器看到的内容 .
就语言而言,将忽略以
#
开头的行 . 代表真实"code"的行很好地突出了语法 . 我们可以看到预处理器从输入到实际编译器之间消除了#ifdef __cplusplus
和相应的#endif
之间的所有内容 .然后我实际编译了源文件并使用gnu程序“nm”(根据其手册页“列出来自目标文件的符号”)检查了生成的目标文件 .
右列列出了此目标文件包含的名称 . 我们对其中定义的函数感兴趣 . 第一列是函数地址(偏移量),第二列中的字母表示找到符号的“段” . 函数定义位于“文本”部分 . 我们清楚地看到函数名称f和i . 这是链接器或加载器可以找到它们的名称 .
现在我使用C编译器进行预处理 . 编译器定义了保留字
__cplusplus
,这使得Cray编译器的#defined out可见:相关的代码行再次得到了很好的强调 .
然后我将其编译为C并检查目标文件中的名称
函数名称变得复杂得多 - "mangled" . 我们仍然可以在名称中间看到"actual"名称
f
和g
,前缀为_Z1 . 显然,参数类型只是编码为"actual" name部分后面的void,int和float的单个字母v,i和f .请注意,返回值不是生成的函数名称的一部分,这意味着链接器通常不知道它(通常没有除目标文件之外的其他信息) . 这与语言规则一致,即返回值不考虑重载解析(并且不可能有两个同名的函数,它们只在返回值上有所不同) . 就链接器而言,
f
的重载版本是完全不相关的函数 .我们还可以看到声明为
extern "C"
的函数具有旧的C名称(h
和i
) . 另一个翻译单元中的C代码可以声明这些函数并使用它们,链接器将找到该符号(i
或h
),解析依赖关系并将函数的代码添加到可执行文件中 . 但是,这样的C代码不能链接到函数f
,因为只要链接器可以看到我们的目标文件中不存在这样的函数 .很明显,C更安全 . 如果使用错误的参数类型声明C函数,则链接器可以快速地链接到期望完全不同的参数的实现 . 它无法知道函数的期望 . 在C中,链接器根本找不到具有不同参数的实现,因为它们的类型是在损坏的名称中编码的 .
如果已经定义了__cplusplus,那么它就是C代码我们想要
并关闭它
在末尾 . 我希望我已正确解码你的消息 .