为啥 C++11 不支持在静态成员函数上声明 extern "C"?
Posted
技术标签:
【中文标题】为啥 C++11 不支持在静态成员函数上声明 extern "C"?【英文标题】:Why does C++11 not support declaring extern "C" on a static member function?为什么 C++11 不支持在静态成员函数上声明 extern "C"? 【发布时间】:2013-01-18 08:44:46 【问题描述】:假设我有一个包含声明为void g(void (*callback)());
的函数的C 库下面的代码优雅但非法:
struct A
// error C2159: more than one storage class specified (VC++ Nov 2012 CTP)
static extern "C" void callback()
;
g(A::callback);
为什么 C++11 不支持这个?
【问题讨论】:
你试过没有extern "C"
吗?
@xmllmx 如果g
需要extern "C" void (*)()
,则g(&A::callback)
是非法的,需要编译器诊断。然而,许多编译器并没有强制执行这一点(因此在这方面被破坏了)。
但是C函数没有按名称调用回调,它只看到一个指针。因此,名称修饰不是问题。请先尝试再询问。
@JoachimPileborg Name mangling 不是问题。调用约定是。名称修饰是调用约定的一部分,但不是全部。我实际上使用了一个编译器,其中 C 和 C++ 使用不同的调用约定(调用者在 C 中清理堆栈,在 C++ 中被调用者)。
@claptrap 不,我们在 SO 上有几个委员会成员,我们公开了委员会会议的所有前后邮件。因此无需猜测。
【参考方案1】:
这是一个特别令人困惑的话题。让我们攻击§7.5“链接规范”[dcl.link]。
1) 所有函数类型、具有外部链接的函数名和具有外部链接的变量名都有一个语言链接。
请注意,语言链接的属性适用于两种完全不同的实体:types 和 names。
一个函数在其类型中有一个通常不可见的信息位,用于标识它符合哪个 ABI:C 调用约定、Pascal、Fortran,所有这些都可能被指定以不同的方式使用堆栈,因此通过指针调用它们需要知道不可见的语言标签。
来自另一种语言的变量或函数的名称可以通过 C++ 语法访问,或者从引用 C++ 声明的另一种语言访问。但并不是每一种语言都能与 C++ 的命名方案和 OO 模型相匹配。所以这个方案中的接口不包括类。
因为这些东西是分开管理的,所以它的类型(调用约定)和名称(链接器符号)可能有不同的链接。
4) 联动规范嵌套。当链接规范嵌套时,最里面的一个确定语言 连锁。链接规范不建立范围。 linkage-specification 应仅出现在命名空间范围 (3.3) 中。在 linkage-specification 中,指定的语言链接适用于在 linkage-specification 中声明的所有函数声明符、具有外部链接的函数名称和具有外部链接的变量名称的函数类型。 在确定类成员名称的语言链接和类成员函数的函数类型时忽略 C 语言链接。
extern "C"
影响所有函数声明,包括指针和引用,成员函数除外。由于函数只能在命名空间或成员中定义,因此 C 函数只能在命名空间范围内定义。
标准在这里举了一个例子:
extern "C" typedef void FUNC_c();
class C
// the name of the function mf1 and the member
// function’s type have C++ language linkage; the
// parameter has type pointer to C function
void mf1(FUNC_c*);
// the name of the function mf2 and the member
// function’s type have C++ language linkage
FUNC_c mf2;
// the name of the data member q has C++ language
// linkage and the data member’s type is pointer to
// C function
static FUNC_c* q;
;
不过,您可以使用typedef
模拟您想要的行为。从第 7.5/4 节中的另一个示例,
extern "C" typedef void FUNC();
// the name f2 has C++ language linkage and the
// function’s type has C language linkage
FUNC f2;
将这些示例与您的示例相结合,您可以拥有
extern "C" typedef void callback_t();
callback_t A_callback; // declare function with C++ name and C type
struct A
static callback_t &callback; // not a member function
;
// in source file:
// definition matches semantics of declaration, although not syntax
void A_callback() ...
// define static member reference
callback_t &A::callback = A_callback;
g(A::callback); // call syntax is emulated
在实践中,它很少有所作为。只要您不尝试传递或返回非 POD C++ 类类型,C 和 C++ 在大多数平台上都使用兼容的调用约定(有关例外情况,请参阅本页上 Jonathan Wakely 的 cmets)。这是 C++ 的一个较少实现的功能,因为术语重载和概念上的区别从微妙到学术令人困惑。
【讨论】:
我想知道如果您让编译器确定类主体之外的函数类型以具有 C 语言链接,然后使用该类型来声明函数:extern "C" typedef void x(); struct A static x f; ;
,会发生什么情况。规范是否定义了这种行为?我目前很着急,无法检查。
同样的规则也适用于静态成员函数,但这不会给mf3
C 语言链接。请注意,具有 C 语言链接的是 mf2
的 参数,而不是 mf2
本身的类型。即extern "C"
块内的类的静态成员函数仍然具有 C++ 语言链接。
@JohannesSchaub-litb,我之前尝试过,EDG(它确实尊重函数的语言链接)在声明成员函数时忽略 typedef 的语言链接,即f
是@987654332 @ 就像它被正常声明一样。
Solaris CC 5.9(警告语言链接不匹配但仍然编译它们)与 EDG 一致,静态成员函数没有获得 C 语言链接,但它被声明了。 (我的第一次测试是错误的,所以我认为它没有,但我修复了它)
@JonathanWakely 感谢您再次引起我的注意。我匆匆回答这个问题,忽略了一部分句子。那些编译器确实是合规的,这个答案的要点是错误的。【参考方案2】:
首先,函数声明是合法的。 extern "C"
,
但是,对于类成员,它会被忽略,因此如果 g
期望
extern "C" void (*)()
,你不能通过callback
。
至于为什么会这样,我怀疑原来主要是
正交性问题:对于一个类没有意义
成员函数一般为extern "C"
,且正交性
(或者根本没有人考虑静态成员的情况)意味着
这也适用于静态成员函数,虽然
让他们成为extern "C"
会很有用。今天
(即C++11),改变规则会有问题,
因为它可能会破坏现有代码。恕我直言,改变会
可以接受,因为它会破坏的代码量是
可能非常小,并且损坏会导致编译时间
错误——不是运行时语义的改变——所以很容易
检测并修复。尽管如此,据我所知,没有人制造
一个改变这个的提议,所以它没有被改变。
【讨论】:
extern "C"
对班级成员来说是非法的。 7.1.1/6:extern 说明符只能应用于变量和函数的名称。 extern 说明符不能用于类成员或函数参数的声明中。 从 C++98 开始也是非法的。
@Jon 我的陈述基于 §7.5/4:“在确定类成员名称的语言链接和类成员函数的函数类型时,将忽略 C 语言链接。” §7.1.1/6 没有说明链接说明符; §7.1.1 完全是关于存储类说明符,而不是链接说明符。
@Jon 不需要。在很多情况下,标准并不明显。
但是,7.5/4 还说“链接规范只能出现在命名空间范围内”——这不是禁止在类声明中使用它吗?
@JamesKanze 不,extern "C"
只允许在命名空间范围内使用。 7.5/4:“链接规范应仅出现在命名空间范围内 (3.3)”您在同一段中的引用是指当类出现在 extern "C"
块内时。以上是关于为啥 C++11 不支持在静态成员函数上声明 extern "C"?的主要内容,如果未能解决你的问题,请参考以下文章
C++ 为啥 Traits 在对象上不可用,而是作为静态成员变量可用