对象和 void 指针在 extern C 声明中是否可以交换?

Are object and void pointer exchangeable in extern C declarations?

我有一个 extern "C" 接口到一些动态加载的代码。对于该代码,一些对象是不透明句柄,表示为 void*。界面如下所示:

extern "C" {
  void* get_foo() { Foo* foo =  /* create and remember object */; return foo; }
  int foo_get_bar(void* Foo) { return ((Foo*)foo)->bar(); }
}

这种模式几乎是跨 C-API、AFAIK 与 C++ 对象交互的标准方式。但是我想知道我是否可以省略那个指针转换?

生成代码的调用者,并且只链接到上面的接口(它有自己的函数声明)。所以它有效地生成了这样的代码:

void foo_get_bar(void*);
void* get_foo();

int do_something() { return foo_get_bar(get_foo()) };

我们可以假设调用者正确使用了代码(即,它没有传递错误的指针)。但当然它没有 Foo 指针的概念。

现在我可以将界面更改为(更简单的)变体:

extern "C" {
  int foo_get_bar(Foo* Foo) { return foo->bar(); }
}

或者 void*Foo* 在链接器级别 之间是否存在细微差别(例如,大小可能不匹配?)。

编辑:我可能对 C 中的调用者代码造成了一些混淆。没有 C 代码。没有 C 编译器,因此不涉及 C 类型检查器。生成调用者代码并使用 C-API。因此,要使用不透明结构,我需要知道 C-API 在编译后如何表示该指针。

实际上,指针 sizes/representations 在通用平台 x86 和 ARM 上不应更改,因此您可以使用 void *.


然而,原则上,标准不保证 2 个不同引用类型的指针将具有相同的指针表示形式和相同的宽度,除了中提到的情况 C11 6.2.5p28:

  • 指向 void 的指针与指向字符类型的指针具有相同的表示和对齐要求
  • 指向兼容类型的指针将具有相同的表示和对齐要求,尽管对齐
  • 指向结构的所有指针在它们之间将具有相同的表示和对齐要求
  • 所有指向 unions 的指针在它们之间将具有相同的表示和对齐要求

因此,在 C 中表示指向 C++ class 的指针的最简洁方法是使用指向不完整结构的指针:

typedef struct Foo Foo;
void foo_get_bar(Foo *);
Foo *get_foo(void);

另一个优点是,如果您尝试错误地使用 Bar *,编译器会报错。


P.S.: 请注意 C.

中的

我发现这个问题有点令人困惑,但如果我理解正确的话,这很好。在任何现代平台上,指向任何对象的指针的大小都是相同的(谢天谢地,我们已经摆脱了 __near__far__middling 的疯狂)。

所以我可能会在声明 get_foo():

的头文件中引入一些类型安全的概念
// Foo.h
struct Foo;

extern "C"
{
    Foo* get_foo ();
    int foo_get_bar (Foo* Foo);
}

而在 class Foo 的实现中,大概是 C++,我们可能有:

// foo.cpp
struct Foo
{
    int get_bar () { return 42; }
};

Foo* get_foo () { return new Foo (); }
int foo_get_bar (Foo *foo) { return foo->get_bar (); }

偷偷摸摸,但合法。您只需将 Foo 声明为 struct 而不是 foo.cpp 中的 class,这意味着如果您想要 privateprotected,您必须明确这么说。

如果这不合胃口,那么你可以在foo.h中这样做:

#ifdef __cplusplus
    class Foo;
#else
    struct Foo;
#endif

然后将 Foo 声明为 class 而不是 foo.c 中的 struct。选择你的毒药。

Live demo.