在extern C声明中对象和空指针可以互换吗?

时间:2018-07-06 07:09:17

标签: c++ pointers linker void-pointers

我对某些动态加载的代码有一个外部的“ 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*之间在链接器级别上有细微差别(例如,大小不匹配吗?)。

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

2 个答案:

答案 0 :(得分:2)

我发现这个问题有点困惑,但是如果我理解正确的话,那很好。在任何现代平台上,指向任何事物的指针的大小都是相同的(幸运的是,我们已经__near__far__middling的疯狂消失了。)

因此,我可能会在声明get_foo()的头文件中引入类似类型安全的概念:

// Foo.h
struct Foo;

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

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 (); }

鬼S,但合法。您只需要在struct中将Foo声明为class而不是foo.cpp,这意味着如果您想要事物privateprotected,则必须明确这样说。

如果那不是可口的,那么您可以在foo.h中这样做:

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

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

Live demo

答案 1 :(得分:1)

实际上,在普通平台x86和ARM上指针的大小/表示形式不应更改,因此您可能不愿意使用void *


但是,原则上,标准不保证引用类型不同的2个指针具有相同的指针表示和相同的宽度,除了 C11 6.2.5p28

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

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

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

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


P.S .:请注意,C中的get_foo() does not mean the same as get_foo(void)