【问题标题】:What kinds of C++ functions can be placed in a C function pointer?哪些类型的 C++ 函数可以放在 C 函数指针中?
【发布时间】:2016-08-24 19:45:24
【问题描述】:

我有一个 C 库,它使用函数指针结构进行回调。回调将从 C 代码中调用。

extern "C" {
typedef struct callbacks_t {
    void (*foo) (const char*);
    int  (*bar) (int);  
} callbacks_t;
}// extern C

我可以安全地将哪些类型的 C++ 函数放置在要从 C 库调用的函数指针中?静态成员函数?完全指定的模板函数?非捕获 Lambda?

g++ 似乎让我可以使用以上所有方法,但我质疑 C 和 C++ 函数使用不同调用约定和语言绑定时的安全性。

【问题讨论】:

  • “但我质疑 C 和 C++ 函数使用不同的调用约定和语言绑定时的安全性” 当所有内容都一致编译时,没有任何区别目标环境。
  • 静态成员函数、专用模板函数和非捕获 lambda 不能如此正式地给出 extern "C" 调用约定,这是不好的。但实际上它取决于编译器。作为实践中的问题,主要问题不是它不起作用,而是一些编译器会产生关于形式的愚蠢警告。
  • @Cheersandhth.-Alf,没关系。 C 库应该能够毫无问题地调用名称被 C++ 编译器修改的函数。
  • @RSahu:这不是关于名称修改,而是关于调用约定。
  • @RSahu:我从来没有听说过。据报道(当这在 clc++ 中讨论过一次时)发出愚蠢警告的一个编译器是针对 Solaris 的,我相信。所以我认为它与有符号整数的补码或符号和大小表示的可能性属于同一类问题。

标签: c++ function-pointers calling-convention language-binding extern-c


【解决方案1】:

我有一个 C 库,它使用函数指针结构进行回调。回调将从 C 代码中调用。

C 库只能理解 C。所以你只能传回 C 明确支持和理解的东西。

由于在 C++ 中没有为任何函数类型定义调用约定,因此您不能显式地传回 C++ 函数。您只能传回 C 函数(使用 extern "C" 显式声明的函数)并保证它们是兼容的。

就像所有未定义的行为一样,这似乎可以工作(比如传回普通的 C++ 函数或静态成员)。但就像所有未定义的行为一样,它允许工作。你只是不能保证它实际上是正确的或可移植的。

extern "C" {
    typedef struct callbacks_t {
        void (*foo) (const char*);
        int  (*bar) (int);  
    } callbacks_t;

    // Any functions you define in here.
    // You can set as value to foo and bar.

}// extern C

我可以将哪些类型的 C++ 函数安全地放置在要从 C 库调用的函数指针中?

静态成员函数?

没有。但这恰好适用于很多平台。这是一个常见的错误,会在某些平台上咬人。

完全指定的模板函数?

没有。

非捕获 Lambda?

没有。

g++ 似乎让我可以使用以上所有功能,

是的。但它假设您传递给使用 C++ 编译器构建的对象(这是合法的)。因为 C++ 代码将使用正确的调用约定。问题是当您将这些东西传递给 C 库时(想到 pthreads)。

【讨论】:

  • 几年前,您提到遇到过一个 C++ 编译器,它对静态成员的调用约定不同于 C 调用约定:stackoverflow.com/questions/1738313/… 我知道那是很久以前的事了,但我认为您可能记得的任何细节都会非常有趣。
  • 这是我在 VERITAS 工作期间(4 个工作前)。我负责在 25 个编译器/操作系统变体上构建软件的构建系统。不幸的是,我不记得是哪一个了。但这种经历基本上是让我远离任何接近未指定或未定义行为的原因。
【解决方案2】:

一般来说,除非你使用 cast,否则你应该信任 g++。

虽然您提到的任何函数类型都不能导出以在 C 中使用,但这不是您要问的。您在询问可以将哪些函数作为函数指针传递。

要回答你能通过什么,我认为理解你不能通过什么更有建设性。您不能传递任何需要在参数列表中未明确说明的附加参数。

所以,没有非静态方法。他们需要一个隐含的“this”。 C不会知道通过它。话又说回来,编译器不会让你。

不捕获 lambda。它们需要一个带有实际 lambda 主体的隐式参数。

您可以传递的是不需要隐式上下文的函数指针。事实上,您继续列出它们:

  • 函数指针。无论是标准函数还是模板都没有关系,只要模板完全解析即可。这不是问题。您编写的任何导致函数指针的语法都将自动完全解析模板。
  • 非捕获 lambda。这是 C++11 在引入 lambda 时引入的一种特殊解决方法。由于可以这样做,因此编译器会进行显式转换以使其发生。
  • 静态方法。由于它们是静态的,因此它们不会通过隐式 this,所以它们没问题。

最后一个需要扩展。许多 C 回调机制得到一个函数指针和一个 void* opaq。以下是在 C++ 类中使用它们的标准且相当安全:

class Something {
  void callback() {
    // Body goes here
  }

  static void exported_callback(void *opaq) {
    static_cast<Something*>(opaq)->callback();
  }
}

然后做:

Something something;

register_callback(Something::exported_callback, &something);

编辑添加: 这样做的唯一原因是,当没有传递隐式参数时,C++ 调用约定和 C 调用约定是相同的。名称修饰存在差异,但在传递函数指针时并不相关,因为名称修饰的唯一目的是让链接器找到正确的函数地址。

如果您尝试过使用回调预期的技巧,例如 stdcall 或 pascal 调用约定,那么这个方案将一败涂地。

然而,这并不是静态方法、lambda 和模板函数所独有的。在这种情况下,即使是标准功能也会失败。

可悲的是,当你定义一个指向 stdcall 类型的函数指针时,gcc 会忽略你:

#define stdcall __attribute__((stdcall))
typedef stdcall void (*callback_type)(void *);

结果:

test.cpp:2:45: warning: ‘stdcall’ attribute ignored [-Wattributes]
 typedef stdcall void (*callback_type)(void *);

【讨论】:

  • 这是一个非常实用的视图。这并没有错。但在正式的情况下,不能将调用约定与明确定义的行为混合在一起。我想你也应该提到正式的。值得了解。
  • 这都是错误的。您不能通过函数指针将任何 C++ 函数传递给 C 代码。这是因为标准未指定两种语言的调用约定。唯一可以从 C++ 传递到 C 并保证它有效的是 C 函数(使用 extern "C" 显式声明)。它会不会中断,可能不像所有未定义的行为它可能看起来在您的平台上完美运行。
  • @LokiAstari 这是我期待和害怕的答案。我继续为所有转发到我的 C++ 函数的回调写了一点 extern "C" 垫片。 C++ 函数在 shim 中内联,因此没有开销,只是这样做有些麻烦。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-14
  • 2015-03-02
  • 2015-10-23
  • 1970-01-01
  • 2023-03-31
相关资源
最近更新 更多