4

我有一个用于各种C 和 C++代码(通过extern "C")的 C 结构。

#ifdef __cplusplus
extern "C" {
#endif

  typedef struct A A;
  struct A {
    /*some members*/
  };

#ifdef __cplusplus
}
#endif

分配、初始化和释放是由我控制下的单独成员函数完成的,但我不控制对成员的访问,因为它们在任何地方都可以访问。

问题是,我无法更改struct整个系统中大量使用的标头中的 ' 定义,但我仍然想扩展类型并添加一些成员。由于这必须同时编译为 C++ 和 C,因此我不能简单地创建派生类型struct B : public A。所以我的想法是将这种类型添加到 cpp 文件中:

#ifdef __cplusplus
extern "C" {
#endif

  typedef struct B B;
  struct B {
    A parent; // <---- public inheritance
    /*some members*/
  };

#ifdef __cplusplus
}
#endif

现在,我可以修改 cpp 文件中的所有函数,但我仍然必须A*在编译单元之外分发和接受,因为没人知道是什么B

所以,我想知道是否有一种理智且定义明确的方式来做到这一点。我可以简单地将我B*的 sA*s来回转换还是必须显式转换它们:

A* static_cast_A(B* b) {
  return &(b->parent);
}
B* static_cast_B(A* a) {
  B* const b = 0;
  unsigned const ptrdiff = (unsigned)((void*)(&(b->parent)));
  return (B*)(((void*)a)-ptrdiff);
}

这是否还有其他问题,或者我应该完全不同吗?

4

2 回答 2

4

只要您不引入虚拟表(通过添加虚拟方法或析构函数)struct B并且parent该结构的成员是第一个元素,那么 case Bto是安全的A

转换AB也是安全的,但前提是原始指针实际上指向B而不是指向其他东西,例如只有A结构本身,显然。

例如,如果 whenparent不是 struct 的第一个成员,B则必须使用container_of在 Linux 内核中看到的宏。它适用于成员指针偏移量(即可以在此处找到一些很好的解释)。

此外,默认情况下,两个结构将具有相同的对齐方式。如果您调整其中一个结构对齐方式,我不确定编译器将如何放置AB我想这取决于编译器,但应该很容易弄清楚。

如果B有一个虚拟表,您必须了解编译器内部结构才能正确转换指针。例如,GCC在 x86_64 上添加了 8 个字节的偏移量,因此您必须在转换时手动偏移它。但这在虚拟继承(虚拟基类)的情况下不起作用,要解决你必须求助于使用 RTTI 等的问题......但这超出了你的问题范围,我建议你不要走那条路。

希望能帮助到你。

于 2012-09-16T01:35:08.417 回答
2

对齐要求不应该引起任何问题,这确实是在 C 中伪造继承的默认方式。

但是,您static_cast_B()的不可移植(特别是void *算术和转换unsigned,以防万一sizeof (void *) > sizeof (unsigned)- 后者仍然可以工作,但有点代码味道)。

我建议改用以下代码:

#include <stddef.h>

B* static_cast_B(A* a) {
  return (B*)((char*)a - offsetof(B, parent));
}
于 2012-09-16T08:05:57.197 回答