3

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 ints in Rect1?

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

Emil Laine
  • 41,598
  • 9
  • 101
  • 157

4 Answers4

2
  • 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 ints 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).
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • ​+1 But the last bullet is incorrect. Adding a constructor shouldn't affect anything, his class remains standard layout. I'm pretty certain `Rect1` and `Rect2` are layout compatible, so the best way to achieve what he wants would be use a `union` that contains the two types. – Praetorian Jan 08 '15 at 04:48
  • @Praetorian "the best way to achieve what he wants would be use a `union`" If he intends to add a constructor to his C++ type, then it won't be trivial any more (still standard-layout, but not trivial) which means that he won't be able to put it inside a `union`. In any case, it is pretty clear that the OP intends to inter-operate some C and C++ code, e.g., by passing a `Rect2*` to a C function that interprets it as a `Rect1*`, or vice versa, which is common practice and is the whole reason why the standard-layout rules exist in C++11. – Mikael Persson Jan 08 '15 at 05:00
  • @MikaelPersson Yes, that's exactly what I'm doing. Also, the versions of `Vector` and `Rect2` in my code do have constructors. – Emil Laine Jan 08 '15 at 05:36
  • @Praetorian It seems that I can accomplish what I want without unions. – Emil Laine Jan 08 '15 at 05:39
  • @MikaelPersson (Missed the second half of your comment earlier, so replying to the whole thing now). [Unrestricted unions](https://en.wikipedia.org/wiki/C%2B%2B11#Unrestricted_unions) allow you to put non trivial types in unions. And you're right that standard layout rules exist for interop with other languages, but that still doesn't allow for type punning (it might work in practice). You're allowed to alias into the objects using `char *`, or if they are layout compatible, as they are here, stick them in a union and use that to change the type. – Praetorian Jan 08 '15 at 05:49
0

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 (e.g., 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, e.g. 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).

Mikael Persson
  • 18,174
  • 6
  • 36
  • 52
  • *"Yes. As long as certain obvious requirements hold, they are guaranteed to be identical. "* What guarantees? It's implementation defined at best. And as such it's not safe to say they are always identical and purely portable. – user694733 Jan 08 '15 at 06:17
  • @user694733 Of course, it's "implementation-defined"! When we talk about binary compatibility, that's always the case. It's a question of what this depends on (target, OS, compiler, options, etc.). In this case, it is clear from the C and C++ standard that the layout of Rect1 (C) and Rect2 (C++) will depend only on the *alignment requirements* of the types involved (in this case `int`). As far as discussing ABI, this is as good as gold, because there is absolutely no way that you could run two pieces of compiled code that have different alignment requirements on a single computer / CPU. – Mikael Persson Jan 08 '15 at 07:08
  • 1
    And how it is clear? C doesn't give any guarantees that sruct with 4 ints will have same layout as struct made of 2 structs with 2 ints each. `Vector` may have padding at the end or between `x` and `y`, that `Rect1` doesn't have. Type punning is not legal between 2 different types of structures in C. – user694733 Jan 08 '15 at 07:22
0

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/

Severin Pappadeux
  • 18,636
  • 3
  • 38
  • 64
-3

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;
}
Rush
  • 486
  • 2
  • 11
  • 3
    Your test doesn't prove anything, and your code violates [strict aliasing rules](http://stackoverflow.com/a/99010/241631). – Praetorian Jan 08 '15 at 04:44