【问题标题】:In C++, is it safe/portable to use static member function pointer for C API callbacks?在 C++ 中,将静态成员函数指针用于 C API 回调是否安全/可移植?
【发布时间】:2011-01-05 07:08:40
【问题描述】:

在 C++ 中,将静态成员函数指针用于 C API 回调是否安全/可移植?静态成员函数的ABI和C函数一样吗?

【问题讨论】:

标签: c++ callback portability


【解决方案1】:

根据 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++ 程序中)也必须如此。

【讨论】:

  • 感谢您的链接 - 仍然是 IMO 应该注意的是,在实践中所有编译器(我使用过的所有...)似乎都提供了不可移植的解决方案 - 例如调用约定声明 -解决问题。
  • @peterchen:这些问题可以使用extern "C" 轻松解决,还是我遗漏了什么?
  • “按照 C++ 标准”?标准的哪一部分是这样说的?
  • @Roger:在讨论“语言链接”时,7.5/3 说“每个实现都应提供与用 C 编程语言编写的函数的链接”,这意味着必须支持 extern "C"
  • 您引用的内容并不是说使用静态方法不安全。
【解决方案2】:

在解决其他问题时搜索和几次休息后,我找到了一个清晰简洁的答案(无论如何都是标准的):

通过函数类型具有与被调用函数定义的函数类型的语言链接不同的语言链接的表达式调用函数是未定义的。 [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); }

【讨论】:

    【解决方案3】:

    对于我所知道的所有 Windows C++ 编译器,答案都是肯定的,但语言标准中没有任何东西可以保证这一点。但是,我不会让这阻止您,这是使用 C++ 实现回调的一种非常常见的方式 - 但是您可能会发现您需要将静态函数声明为 WINAPI。这取自我自己的旧线程库:

    class Thread {
       ...
       static DWORD WINAPI ThreadFunction( void * args );
    };
    

    这是 Windows 线程 API 使用的回调。

    【讨论】:

    • 我丢失了链接,但 GCC(在最新版本中)也对静态方法和 C 函数使用相同的调用约定,因此 gcc 用户也很安全。
    • 我真的不明白这一点。我的意思是,如果函数是静态成员无论如何,为什么不首先通过非成员函数来安全地进行操作并做到便携呢?
    • jalf:因为你仍然只有安全的错觉,因为你在实施时心血来潮。显然它是一个 tiny 更便携(我仍然没有听到这会影响哪些编译器),但是,我相信你知道,这与标准所保证的不同.为什么要费力解决那些甚至在您的实施中不存在并且您预计在未来 5 年内不会影响您的问题?
    • @Roger:“好痛苦!”如果您在头文件中调用将静态方法声明向上移动两行并在其前面加上 extern "C" 前缀会非常痛苦,那么编码一定是偏头痛。
    • 它破坏了什么编译器。在我的上一份工作中,我在 25 个编译器/操作系统/硬件配置上编译了 ACE/TAO 和一些公司代码。每个配置都内置了 8 个(调试/发布 - 单多线程 - 共享/静态库)风格。在这 200 个版本中,我在 3 个版本中发现了问题(我在可以调试的三个版本中遇到了可重现的问题),它可能影响了其他版本,但我花了很长时间才发现问题并修复。确切的编译器/版本让我无法理解(那是 5 年前),但我认为它是一个较旧的 Sun 编译器和一个较旧的 AIX 编译器,我可能是错的。它们是旧版本的编译器
    【解决方案4】:

    C 或 C++ 标准均未涵盖 ABI,尽管 C++ 确实通过 extern "C" 为您提供“语言链接”。因此,ABI 基本上是特定于编译器/平台的。这两个标准都将很多很多事情留给实施,这就是其中之一。

    因此,编写 100% 可移植的代码(或切换编译器)几乎是不可能的,但允许供应商和用户在其特定产品中具有相当大的灵活性。这种灵活性允许更多的空间和时间效率的程序,在标准委员会不必提前预料到的方式。


    据我了解,ISO 的规则不允许标准的频率超过每 10 年一次(但可以有各种出版物,例如 C++ 的 TC1 和 TR1)。另外还有一个想法(我不确定这是否来自 ISO,是否来自 C 委员会,甚至来自其他地方)来“提炼”/标准化现有实践而不是进入左侧领域,并且有 许多现有的做法,其中一些是冲突的。

    【讨论】:

    • 好吧,我不同意 Martin 的观点,多年的 Windows 编程经验似乎支持了这一点。正如您所观察到的,没有标准的 ABI,因此使用 C 函数也不能保证。
    • 如果你只使用 Windows 编译器,你会没事的......直到你将你的代码移植到 Android 或 Linux 或 Mac 或其他任何东西上,然后你可能会发现你的“工作”代码没有'不工作。使用 extern C 是最安全的——它并不需要太多额外的工作。
    • 尼尔:没错。你能做的最好的就是说“确保你按照编译器的要求去做”。
    • Emile:不能,只能在命名空间范围内,这似乎是他坚持不使用静态成员的原因。
    • 是的 - ABI 是特定于平台的。为特定平台编译的带有 extern "C" 函数声明的 C++ 程序将期望该函数使用该平台的 C ABI。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-02
    • 2018-01-02
    相关资源
    最近更新 更多