对象和 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
,这意味着如果您想要 private
或 protected
,您必须明确这么说。
如果这不合胃口,那么你可以在foo.h中这样做:
#ifdef __cplusplus
class Foo;
#else
struct Foo;
#endif
然后将 Foo
声明为 class
而不是 foo.c
中的 struct
。选择你的毒药。
我有一个 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
,这意味着如果您想要 private
或 protected
,您必须明确这么说。
如果这不合胃口,那么你可以在foo.h中这样做:
#ifdef __cplusplus
class Foo;
#else
struct Foo;
#endif
然后将 Foo
声明为 class
而不是 foo.c
中的 struct
。选择你的毒药。