为啥 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) 所有函数类型、具有外部链接的函数名和具有外部链接的变量名都有一个语言链接

请注意,语言链接的属性适用于两种完全不同的实体:typesnames

一个函数在其类型中有一个通常不可见的信息位,用于标识它符合哪个 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 在对象上不可用,而是作为静态成员变量可用

为啥我不能只用前向声明 C++ 声明一个类的静态成员?

C++中static函数类外定义的时候为啥不写static?

为啥 C++11 不支持匿名结构,而 C11 支持?

c++中静态成员变量和静态成员函数(笔试经历)

C ++创建具有非静态成员函数的线程