在C ++中,为C API回调使用静态成员函数指针是安全/可移植的吗?静态成员函数的ABI是否与C函数相同?
答案 0 :(得分:20)
根据C ++标准,这是不安全的。如this SO posting中所述:
用C ++实现的C回调函数必须是extern“C”。它似乎在类中作为静态函数工作,因为类静态函数通常使用与C函数相同的调用约定。但是,这样做是一个等待发生的错误(请参阅下面的评论),所以请不要 - 通过外部“C”包装来代替。
根据Martin York在该答案中所做的评论,在某些平台上尝试这样做存在现实问题。
进行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 ++程序中)也是如此。
答案 1 :(得分:6)
在攻击其他问题后搜索并休息几次后,我找到了一个清晰简洁的答案(无论如何都是标准的):
通过表达式调用函数,该表达式的函数类型具有与被调用函数定义的函数类型的语言链接不同的语言链接,这是未定义的。 [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); }
答案 2 :(得分:5)
对于我所知道的所有Windows C ++编译器,答案是肯定的,但语言标准中没有任何内容可以保证这一点。但是,我不会让你停止,这是使用C ++实现回调的一种非常常见的方法 - 你可能会发现你需要将静态函数声明为WINAPI。这取自我自己的旧线程库:
class Thread {
...
static DWORD WINAPI ThreadFunction( void * args );
};
这是te Windows线程API的回调使用。
答案 3 :(得分:3)
ABI不受C或C ++标准的约束,即使C ++确实通过extern "C"
为您提供了“语言链接”。因此,ABI基本上是编译器/平台特定的。这两个标准都有很多很多东西需要实现,而这就是其中之一。
因此,编写100%可移植代码或交换编译器很难实现,但允许供应商和用户在其特定产品中具有相当大的灵活性。这种灵活性允许更多空间和时间有效的计划,这些方式不必由标准委员会提前预见。
据我了解,ISO的规则不允许标准每10年更多一次(但可以有各种出版物,例如C ++的TC1和TR1)。另外还有一个想法(我不确定这是来自ISO,是从C委员会,还是从其他地方转移)来“提炼”/标准化现有的做法而不是进入左侧领域,并且有< em>许多现有的做法,其中一些是冲突的。