简体   繁体   中英

C/C++ Struct memory layout equivalency

Consider the following C struct and C++ struct declarations:

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;
}
  • Are the memory layouts of Rect1 and Rect2 objects always identical?

  • 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 int s in Rect1 ?

  • Does it make a difference if I change Rect2 to a non-POD type, eg by adding a constructor?

  • I would think so, but I also think there could (legally) be padding between Rect2::pos and Rect2::size . So to make sure, I would add compiler-specific attributes to "pack" the fields, thereby guaranteeing all the int s are adjacent and compact. This is less about C vs. C++ and more about the fact that you are likely using two "different" compilers when compiling in the two languages, even if those compilers come from a single vendor.
  • Using reinterpret_cast to convert a pointer to one type to a pointer to another, you are likely to violate "strict aliasing" rules. Assuming you do dereference the pointer afterward, which you would in this case.
  • Adding a constructor will not change the layout (though it will make the class non-POD), but adding access specifiers like private between the two fields may change the layout (in practice, not only in theory).

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

Yes. As long as certain obvious requirements hold, they are guaranteed to be identical. Those obvious requirements are about the target platform/architecture being the same in terms of alignment and word sizes. In other words, if you are foolish enough to compile the C and C++ code for different target platforms (eg, 32bit vs. 64bit) and try to mix them, then you'll be in trouble, otherwise, you don't have to worry, the C++ compiler is basically required to produce the same memory layout as if it was in C, and ABI is fixed in C for a given word size and alignment.

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?

Yes. That follows from the first answer.

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

No, or at least, not any more. The only important thing is that the class remains a standard-layout class, which is not affected by constructors or any other non-virtual member. That's valid since the C++11 (2011) standard. Before that, the language was about "POD-types", as explained in the link I just gave for standard-layout. If you have a pre-C++11 compiler, then it is very likely still working by the same rules as the C++11 standard anyway (the C++11 standard rules (for standard-layout and trivial types) were basically written to match what all compiler vendors did already).

For a standard-layout class like yours you could easily check how members of a structure are positioned from the structure beginning.

#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/

Yes, they will always be the same. You could try running the below example here cpp.sh It runs as you expect.

  // 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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM