【发布时间】:2011-01-05 07:08:40
【问题描述】:
在 C++ 中,将静态成员函数指针用于 C API 回调是否安全/可移植?静态成员函数的ABI和C函数一样吗?
【问题讨论】:
-
有人告诉我一些英特尔编译器不能使用静态成员函数:mail.gnome.org/archives/gtk-list/2011-March/msg00085.html
标签: c++ callback portability
在 C++ 中,将静态成员函数指针用于 C API 回调是否安全/可移植?静态成员函数的ABI和C函数一样吗?
【问题讨论】:
标签: c++ callback portability
根据 C++ 标准,它是不安全的。如this SO posting中所述:
在 C++ 中实现的 C 回调函数必须是 extern "C"。它似乎在类中作为静态函数工作,因为类静态函数通常使用与 C 函数相同的调用约定。但是,这样做是一个等待发生的错误(参见下面的 cmets),所以请不要 - 改为使用 extern "C" 包装器。
根据Martin York 在该答案中提出的 cmets 的说法,在某些平台上尝试这样做存在实际问题。
让您的 C ABI 回调extern "C"。
编辑:从标准中添加一些支持引用(强调我的):
3.5“程序与联动”:
在所有类型调整后(在此期间 typedef (7.1.3) 被其定义替换),引用给定对象或函数的所有声明指定的类型应相同,除了数组对象的声明可以指定因存在或不存在主要数组绑定(8.3.4)而不同的数组类型。在类型标识上违反此规则不需要诊断。 [3.5/10]
[注意:可以使用链接规范 (7.5) 来实现与非 C++ 声明的链接。 ] [3.5/11]
和
7.5《联动规范》:
... 具有不同语言链接的两个函数类型是不同的类型,即使它们在其他方面相同。 [7.5/1]
因此,如果进行回调的代码使用 C 语言绑定进行回调,那么回调目标(在 C++ 程序中)也必须如此。
【讨论】:
extern "C" 轻松解决,还是我遗漏了什么?
extern "C"。
在解决其他问题时搜索和几次休息后,我找到了一个清晰简洁的答案(无论如何都是标准的):
通过函数类型具有与被调用函数定义的函数类型的语言链接不同的语言链接的表达式调用函数是未定义的。 [5.2.2/1]
我仍然坚持认为,使用 C++ 标准中的文本来定义用 C 编译器编译的 C 库的行为,在基本层面上是有问题的,而且这种跨语言互操作性的工作原理是非常特定于实现的;但是,这是我认为任何一个标准(目前)都希望定义这种交互的最接近的标准。
特别是,这是未定义的行为(并且没有使用 C 库,因此不会出现问题):
void call(void (*pf)()) { pf(); } // pf() is the UB
extern "C" void f();
int main() { call(f); }
// though I'm unsure if a diagnostic is required for call(f)
Comeau 确实在call(f) 上给出了诊断(尽管即使不需要诊断也可以这样做)。
这不是未定义的行为,它展示了如何在函数指针类型中包含语言链接(通过 typedef):
extern "C" typedef void F();
void call(F* pf) { pf(); }
extern "C" void f();
int main() { call(f); }
或者可以写成:
extern "C" {
typedef void F();
void f();
}
void call(F* pf) { pf(); }
int main() { call(f); }
【讨论】:
对于我所知道的所有 Windows C++ 编译器,答案都是肯定的,但语言标准中没有任何东西可以保证这一点。但是,我不会让这阻止您,这是使用 C++ 实现回调的一种非常常见的方式 - 但是您可能会发现您需要将静态函数声明为 WINAPI。这取自我自己的旧线程库:
class Thread {
...
static DWORD WINAPI ThreadFunction( void * args );
};
这是 Windows 线程 API 使用的回调。
【讨论】:
C 或 C++ 标准均未涵盖 ABI,尽管 C++ 确实通过 extern "C" 为您提供“语言链接”。因此,ABI 基本上是特定于编译器/平台的。这两个标准都将很多很多事情留给实施,这就是其中之一。
因此,编写 100% 可移植的代码(或切换编译器)几乎是不可能的,但允许供应商和用户在其特定产品中具有相当大的灵活性。这种灵活性允许更多的空间和时间效率的程序,在标准委员会不必提前预料到的方式。
据我了解,ISO 的规则不允许标准的频率超过每 10 年一次(但可以有各种出版物,例如 C++ 的 TC1 和 TR1)。另外还有一个想法(我不确定这是否来自 ISO,是否来自 C 委员会,甚至来自其他地方)来“提炼”/标准化现有实践而不是进入左侧领域,并且有 许多现有的做法,其中一些是冲突的。
【讨论】:
extern "C" 函数声明的 C++ 程序将期望该函数使用该平台的 C ABI。