将具有C语言链接的函数指针分配给具有C++语言链接的函数指针,反之亦然。

3

这段代码合法吗?

extern "C" typedef void (ft_blah_c)();
/*extern "C++"*/ typedef void (ft_blah_cpp)();

extern "C" void fn_blah_c() {}
/*extern "C++"*/ void fn_blah_cpp() {}

ft_blah_c *g_Blah_c = fn_blah_cpp; // <--- ?
ft_blah_cpp *g_Blah_cpp = fn_blah_c; // <--- ?

我有一段类似的编程代码,它可以顺利编译和执行(使用MSVC 2010),没有任何问题。


@WhozCraig 看起来你把存储和链接混淆了。 - Shadows In Rain
C 和 C++ 对于 void fn() 使用相同的调用规约吗?我认为两种标准都没有说明。具体平台的文档可能会有介绍。 - Bo Persson
4
“7.5 链接说明”规定,具有不同语言链接的两种类型即使在其他方面完全相同,也是不同的类型。编译器应该以此为基础拒绝赋值操作。但它并没有这样做。 - Martin York
@LokiAstari 谢谢!我以前不知道这个,也毫无疑问地利用了跳过它的编译器。一定很幸运。非常感谢您提供的信息。 - WhozCraig
2个回答

4
一般来说,这样做是不起作用的。问题在于当您直接调用 fn_blah_cfn_blah_cpp 时,编译器会“知道”该函数和使用的调用约定,但如果将它们存储在函数指针中,则编译器只能看到该指针,并且只能使用函数指针的类型来确定如何传递参数和返回类型。
如果在您的环境中 C 和 C++ 的调用约定相同,则可以正常工作(这可能是您的编译器允许它的原因),但一般情况下会失败。

1
虽然链接是类型的一部分,但我发现大多数编译器不会检查,即使它们不兼容也会允许赋值,并在调用时出现非常好的错误。这里可能有帮助的另一件事是没有传递参数,也没有使用返回值,因此这是最简单的调用,并且只需要两个约定使用相同的指令指针返回地址位置(可能在任何地方都是真实的)。 - Martin York
所以我很好奇,因为我没有看到调用约定的标准(它们甚至在那里吗??)当前默认约定是否会被使用,除非在函数声明中指定了它?而且,当分配使用特定非默认约定的函数地址时,我已经提出了必须指定非默认调用约定的函数指针。通常,如果指针没有使用相同的约定前缀进行声明,编译器会捕获这样的赋值吗?例如,MSVC允许将约定附加到函数指针。 - WhozCraig
@ShadowsInRain:正如David所说。如果编译器对两种语言使用相同的调用约定,它将起作用。但是标准中没有关于此的保证,您不应该依赖它。 - Martin York
@LokiAstari,你指出了不同链接的不同类型,我甚至不想费心去搞清楚。我刚读完C++11规范的7.5节,第一段就很清楚地写着这个。非常感谢你指出来。 - WhozCraig
所以,这是C ++的一个事实标准之一。另一个是,具有单个字节成员的结构体大小为一个字节。可能有数十亿行的代码依赖于这些事实标准。 - Cheers and hth. - Alf
显示剩余3条评论

3
不,这是不合法的;函数类型之间的转换不能是隐式的。
显式地将函数类型进行强制转换是合法的:
ft_blah_c *g_Blah_c = reinterpret_cast<ft_blah_c>(fn_blah_cpp);
ft_blah_cpp *g_Blah_cpp = reinterpret_cast<ft_blah_cpp>(fn_blah_c);

然而,通过不同类型的函数指针调用函数实际上是未定义行为。在调用函数之前,必须将其转换回原始类型。
这背后的原因超出了标准的范围,即调用函数的机器指令可以根据函数的语言链接而异。这被称为'调用约定',涉及许多细节,如参数和返回值传递约定,函数序言和结尾等。
例如,具有C语言链接的函数可能希望在堆栈上找到其参数,而具有C ++语言链接的函数可能希望在寄存器中传递参数。如果调用代码不知道正确的链接方式,则会将参数数据放置在一个位置,而函数将在另一个位置查找并仅读取垃圾数据,从而无法正常操作。

你知道有哪些编译器可以实现使用不同的调用约定吗? - Jed
我不确定,但我认为您可以使VC++对C和C++函数使用不同的调用约定。 - bames53
查看 cppreference唯一区分“C”和“C++”语言链接的现代编译器是Oracle Studio,其他编译器不允许仅在语言链接上有所不同的重载,包括 C++ 标准所需的重载集合(std::qsort、std::bsearch、std::signal、std::atexit 和 std::at_quick_exit)。 - Ayxan Haqverdili

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