C/C++ 结构内存布局等效

C/C++ Struct memory layout equivalency

考虑以下 C 结构和 C++ 结构声明:

extern "C" { // if this matters
typedef struct Rect1 {
  int x, y;
  int w, h;
} Rect1;
}

struct Vector {
  int x;
  int y;
}

struct Rect2 {
  Vector pos;
  Vector size;
}

是的,它们将永远相同。 您可以在此处 运行 尝试以下示例 cpp.sh 它运行如您所愿。

  // Example program
#include <iostream>
#include <string>

typedef struct Rect1 {
  int x, y;
  int w, h;
} Rect1;

struct Vector {
  int x;
  int y;
};

struct Rect2 {
  Vector pos;
  Vector size;
};


struct Rect3 {
  Rect3():
   pos(),
   size()
  {}
  Vector pos;
  Vector size;
};


int main()
{

  Rect1 r1;
  r1.x = 1;
  r1.y = 2;
  r1.w = 3;
  r1.h = 4;
  Rect2* r2 = reinterpret_cast<Rect2*>(&r1);
  std::cout << r2->pos.x << std::endl;
  std::cout << r2->pos.y << std::endl;
  std::cout << r2->size.x << std::endl;
  std::cout << r2->size.y << std::endl;


  Rect3* r3 = reinterpret_cast<Rect3*>(&r1);
  std::cout << r3->pos.x << std::endl;
  std::cout << r3->pos.y << std::endl;
  std::cout << r3->size.x << std::endl;
  std::cout << r3->size.y << std::endl;
}
  • 我会这么认为,但我也认为 Rect2::posRect2::size 之间可以(合法地)填充。因此,为了确保,我会将特定于编译器的属性添加到 "pack" 字段,从而保证所有 int 都是相邻且紧凑的。这不是关于 C 与 C++ 的关系,更多的是关于在使用两种语言进行编译时您可能会使用两个 "different" 编译器这一事实,即使这些编译器来自一个供应商。
  • 使用reinterpret_cast将指向一种类型的指针转​​换为指向另一种类型的指针,您很可能违反了"strict aliasing"规则。假设你之后取消引用指针,在这种情况下你会这样做。
  • 添加构造函数不会改变布局(尽管它会使 class 成为非 POD),但在两个字段之间添加访问说明符 private 可能会改变布局(实际上,不仅在理论上)。

Are the memory layouts of Rect1 and Rect2 objects always identical?

是的。只要满足某些明显的要求,就可以保证它们是相同的。这些明显的要求是关于目标 platform/architecture 在对齐和字长方面相同。换句话说,如果您愚蠢到为不同的目标平台(例如,32 位与 64 位)编译 C 和 C++ 代码并尝试混合使用它们,那么您就会遇到麻烦,否则,您不必担心,C++ 编译器基本上需要生成与在 C 中相同的内存布局,并且 ABI 对于给定的字长和对齐在 C 中是固定的。

Specifically, can I safely reinterpret_cast from Rect2* to Rect1* and assume that all four int values in the Rect2 object are matched one on one to the four ints in Rect1?

是的。这是第一个答案的结果。

Does it make a difference if I change Rect2 to a non-POD type, e.g. by adding a constructor?

没有,或者至少,没有了。唯一重要的是 class 仍然是 standard-layout class,它不受构造函数或任何其他非虚拟成员的影响。这自 C++11 (2011) 标准以来有效。在此之前,语言是关于 "POD-types",正如我刚刚为标准布局提供的 link 中所解释的那样。如果你有一个 C++11 之前的编译器,那么它很可能仍然按照与 C++11 标准相同的规则工作(C++11 标准规则(用于标准布局和普通类型)基本上是编写以匹配所有编译器供应商已经完成的工作)。

对于像您这样的标准布局 class,您可以轻松检查结构成员如何从结构开始定位。

#include <cstddef>

int x_offset = offsetof(struct Rect1,x); // probably 0
int y_offset = offsetof(struct Rect1,y); // probably 4
....
pos_offset = offsetof(struct Rect2,pos); // probably 0
....

http://www.cplusplus.com/reference/cstddef/offsetof/