C语言中的extern "C"函数是一种单独的类型吗?

18

从C++11草案第7.5条(第1段):

即使两个函数类型在其他方面完全相同,如果它们的语言链接不同,则它们是不同的类型。

因此,我可以基于语言链接进行重载:

extern "C" typedef void (*c_function)();
typedef void (*cpp_function)();

void call_fun(c_function f)
{
}
void call_fun(cpp_function f)
{
}

extern "C" void my_c()
{
}
void my_cpp()
{
}
int main()
{
    call_fun(my_c);
    call_fun(my_cpp);
}

然而,使用GCC 4.7.1时,这个示例代码会产生错误信息:

test.cpp: In function 'void call_fun(cpp_function)':
test.cpp:7:6: error: redefinition of 'void call_fun(cpp_function)'
test.cpp:4:6: error: 'void call_fun(c_function)' previously defined here

使用 CLang++ :

test.cpp:7:6: error: redefinition of 'call_fun'
void call_fun(cpp_function f)
     ^
test.cpp:4:6: note: previous definition is here
void call_fun(c_function f)
     ^

现在有以下问题:

  • 我对标准的理解正确吗?这段代码是有效的吗?

  • 任何人知道这些编译器中的错误是故意为了兼容性而这样做的,还是说它们只是单纯的bug?


仅供记录:C++03标准在同一段落中有完全相同的句子,因此这不是编译器尚未支持的C++11功能的问题。 - Gorpik
请参考 http://stackoverflow.com/a/10643935/1463922。确保 C 和 C++ 的调用约定匹配。 - PiotrNycz
3个回答

10

代码显然是有效的。G++(以及其他一些编译器)对于将链接整合到类型中有点宽松(委婉地说)。


根据OP提供的引用,这似乎是正确的,除非标准的某个角落添加了对该引用的例外。无论如何,我认为James更清楚,我相信他的话 :) - Alok Save
1
@Als 这里有一个经典例子展示了为什么它很重要:当把一个静态成员函数传递给pthread_create(或者CreateThread)的时候。根据标准规定,这是不合法的(因为 pthread_create 需要一个 extern "C",而一个成员不能是 extern"C"),但是 g++ 和 VC++ 都支持这种用法。 - James Kanze
谢谢。问题是我有一些代码,它依赖于GCC的这种行为。当然它能工作,但让我感到有点不舒服,因为它似乎完全不可移植。我的问题是这种放松是否是有意的,因此我可以将其视为“GCC扩展”。 - rodrigo
@JamesKanze:我不确定CreateThread需要一个extern "C"函数。据我所知,当它从C++代码调用时,它需要一个extern "C++"函数。 - MSalters
1
@MSalters 我对 CreateThread 不是很确定;微软并不是很清楚(但是,微软不认为 extern "C" 是类型的一部分)。我假设该函数可以从 C 中调用,但这并不意味着太多;MS 使用了许多扩展来定义调用约定,而 CreateThread 可能是使用其中一个定义的。由于我们正在使用实现定义的扩展,因此允许或不允许的内容是实现定义的。 - James Kanze
显示剩余2条评论

8
这是gcc中已知的一个bug,他们记录下来这个bug不符合标准,因为它阻止了“C++98一致性问题”的超级bug。

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=2316

检查创建日期。

在末尾有一些关于引入修复措施的实际讨论。所以你最后一个问题的答案是“两者都是”:这是一个错误 并且 这个错误故意被保留以保持兼容性。

其他存在相同问题的编译器可能独立地产生了错误,但我认为更可能的是它们也知道这是错误的,但希望与gcc保持bug兼容性。


有点麻烦,因为我正在使用指向转换为指向extern "C"函数的模板函数的指针。我不知道有什么方法可以创建一个extern "C"模板函数,所以我想现在将我的代码保留为它原来的样子... - rodrigo
嗯,听起来很令人恼火。我猜你可以使用(最多)一个extern "C"包装函数来调用每个C API,只要C链接函数接受用户数据指针即可。你可以通过将C++链接函数指针存储在用户数据中,并从那里调用它来将其传递给C链接函数。这有点像“如果不确定,就增加更多间接性”的做法。 - Steve Jessop
@Steve_Jessop:谢谢,我有类似的想法,但是这违背了我使用模板而不是具有虚函数和静态成员函数的调度器对象时所追求的零开销目标... 哎呀! - rodrigo
@rodrigo:是的,我想你可能不会太喜欢增加一个函数的想法,更不用说可能需要为用户数据分配额外的内存,这取决于涉及的所有内容的生命周期... - Steve Jessop

1

就算是这样,这段代码在VS2012的默认设置下也无法编译通过:

(8) error C2084: function 'void call_fun(c_function)' already has a body
(4) see previous definition of 'call_fun'
(19) error C3861: 'call_fun': identifier not found
(20) error C3861: 'call_fun': identifier not found

但是在线的Coumeau C++编译器没有任何警告地接受它! - rodrigo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接