reinterpret_cast : differents structs or classes with identical structural type

Hello, Supposing having struct A{ int a1; double a2; int a3[3]; }; struct B{ int b1; double b2; int b3[3]; }; A and B are two structure with same strucutural type but different name, (and different name for fields...) The question is: C++ grants that A a; a.a2=8.8; assert(reinterpret_cast<B*>(&a)->b2==8.8); If the response is yes, it is even true for classes with inheritance? that is A1,B1,C1 have same struttural type of A2,B2,C2 and class A1 :public B1, class B1: public C1 and class A2 :public B2, class B2: public C2 can i safaly reinterpreter cast from A1 to A2 and acceding public fields (of A1,B1,C1) trought the field names of A2? Thanks in advance for your help.

Marco Servetto wrote:
The question is: C++ grants that A a; a.a2=8.8; assert(reinterpret_cast<B*>(&a)->b2==8.8);
Please note that my reply is based on the final public review document and thus not authoritative. Yes, it grants this, according to the rules of layout-compatible PODs.
it is even true for classes with inheritance?
No. The layout-compatibility rules only apply to PODs. POD-structs are defined by their lack of member pointer members, reference members, object members of non-POD type (recursively), user-defined copy assignment and user-defined destructor. But there is one more requirement: they must be aggregate classes. Aggregate classes are defined in 8.5.1: "An aggregate is an array or a class with no user-declared constructors, no private or protected non-static data members, no base classes, and no virtual functions." Sebastian Redl

Just one small (and probably non-relevant) issue: I don't know the precise C++ answer, but in general you should be very careful when comparing floating point numbers as you might get unexpected results. An interesting article on this issue can be read here: http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm David On 9/22/07, Sebastian Redl <sebastian.redl@getdesigned.at> wrote:
Marco Servetto wrote:
The question is: C++ grants that A a; a.a2=8.8; assert(reinterpret_cast<B*>(&a)->b2==8.8);
Please note that my reply is based on the final public review document and thus not authoritative. Yes, it grants this, according to the rules of layout-compatible PODs.
it is even true for classes with inheritance?
No. The layout-compatibility rules only apply to PODs. POD-structs are defined by their lack of member pointer members, reference members, object members of non-POD type (recursively), user-defined copy assignment and user-defined destructor. But there is one more requirement: they must be aggregate classes. Aggregate classes are defined in 8.5.1:
"An aggregate is an array or a class with no user-declared constructors, no private or protected non-static data members, no base classes, and no virtual functions."
Sebastian Redl _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

on Fri Sep 21 2007, "Marco Servetto" <marco.servetto-AT-gmail.com> wrote:
Hello, Supposing having struct A{ int a1; double a2; int a3[3]; }; struct B{ int b1; double b2; int b3[3]; };
A and B are two structure with same strucutural type but different name, (and different name for fields...)
The question is: C++ grants that A a; a.a2=8.8; assert(reinterpret_cast<B*>(&a)->b2==8.8);
C++ guarantees very, very little about reinterpret_cast, and certainly not that. In general the results of reinterpret_cast are implementation-defined. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

David Abrahams wrote:
on Fri Sep 21 2007, "Marco Servetto" <marco.servetto-AT-gmail.com> wrote:
struct A{ int a1; double a2; int a3[3]; }; struct B{ int b1; double b2; int b3[3]; };
The question is: C++ grants that A a; a.a2=8.8; assert(reinterpret_cast<B*>(&a)->b2==8.8);
C++ guarantees very, very little about reinterpret_cast, and certainly not that.
The rules about layout compatibility of POD structs seem to guarantee exactly that. I don't know how else to interpret them - that is, how else to even be in a situation where the compatibility rules apply, except by using reinterpret_cast or a C-style cast. Sebastian Redl

Are you speaking about rules provided in the C++ ISO standard ? Could you point me to them ? (page, section). I'd like to give them a look . Regards, Marco On Tue, 02 Oct 2007 12:32:22 +0200, Sebastian Redl <sebastian.redl@getdesigned.at> wrote:
David Abrahams wrote:
on Fri Sep 21 2007, "Marco Servetto" <marco.servetto-AT-gmail.com> wrote:
<snip>
C++ guarantees very, very little about reinterpret_cast, and certainly not that.
The rules about layout compatibility of POD structs seem to guarantee exactly that. I don't know how else to interpret them - that is, how else to even be in a situation where the compatibility rules apply, except by using reinterpret_cast or a C-style cast.
Sebastian Redl _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Using Opera's revolutionary e-mail client: http://www.opera.com/mail/

Marco wrote:
Are you speaking about rules provided in the C++ ISO standard ? Could you point me to them ? (page, section). I'd like to give them a look .
Reinterpret_cast is defined in 5.2.10. The language there is rather clear about what is guaranteed and what not. Judging by that section, you can't rely on the results of the cast for anything except casting it back. Layout compatibility is defined in various sections of the standard. They are first mentioned in 3.9/11, where it says that a type is layout-compatible with itself. In 3.9.2/3 it says that pointers to layout-compatible types must have the same value representation and alignment requirements. 7.2/7 defines layout compatibility for enumerations; 9.2/14+15 do it for structures and unions. 9.2/16 is where it gets interesting. It says that, if two structs in a union share a common initial subsequence of layout-compatible types, you can access the fields of this sequence through either struct name. 9.2/17 makes an implicit guarantee about casts, even though nothing in chapter 5 (where casts are defined) really allows this. It says:
A pointer to a POD-struct object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa. In other words, given
struct foo { int i; }; you can do foo f = {4}; foo *pf1 = &f; int *pi = reinterpret_cast<int*>(pf1); cout << *pi; foo *pf2 = reinterpret_cast<foo*>(pi); cout << pf2->i; The implicit guarantee is there because there is no other way to convert the pointer except through a reinterpret_cast. (A C-style cast is defined in terms of the cast operator is takes the place of.) Layout compatibility is not mentioned anywhere else. So what does that mean? It means that, by the words of the standard, your code was not valid. On the other hand, making both structs the members of a union, then assigning one and reading the other, is valid. Make of that what you will. Sebastian Redl

First off thanks a lot for the detailed explanation and reference. On Tue, 02 Oct 2007 15:01:36 +0200, Sebastian Redl <sebastian.redl@getdesigned.at> wrote:
Marco wrote:
Are you speaking about rules provided in the C++ ISO standard ? Could you point me to them ? (page, section). I'd like to give them a look .
skip
9.2/16 is where it gets interesting. It says that, if two structs in a union share a common initial subsequence of layout-compatible types, you can access the fields of this sequence through either struct name.
So what you are saying it's that definition 9.2/14 about layout compatible structs has not provided to suggest that code like Marco Servetto's code is valid, but only in order to state the 9.2/16 rule. skip
Layout compatibility is not mentioned anywhere else. So what does that mean?
It means that, by the words of the standard, your code was not valid. On the other hand, making both structs the members of a union, then assigning one and reading the other, is valid. Make of that what you will.
Sebastian Redl
Maybe there is a little misunderstanding because I'm another Marco, but that doesn't matter :-) What I'm interesting is if the standard says something about declaration order of struct fields and their storage order in memory. But it appears that such relation is definitively implementation dependent, except for what stated at paragraph 9.2/17 as you pointed out. Kind Regards, Marco Cecchetti -- Using Opera's revolutionary e-mail client: http://www.opera.com/mail/

Marco wrote:
What I'm interesting is if the standard says something about declaration order of struct fields and their storage order in memory. It does. It says that members of a struct or class (even non-POD ones, which I think is a useless restriction) that have no intervening access specifier are stored in memory in the same order as they are declared. 9.2/12 says: Nonstatic data members of a (non-union) class declared without an intervening /access-specifier/ are allocated so that later members have higher addresses within a class object. The order of allocation of nonstatic data members separated by an /access-specifier/ is unspecified. Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions and virtual base classes.
That means: class foo { int i1; int i2; public: int i3; int i4; public: int i5; }; -> &i1 < &i2 == true &i3 < &i4 == true &i1 < &i3 unspecified &i3 < &i5 unspecified In particular, although no compiler I know of does it, an implementation can reorder fields to e.g. make the allocation pattern more efficient: class inefficient { char c1; private: int i1; private: char c2; private: int c2; }; Naively, on a typical 32-bit platform, sizeof(inefficent) would be 16. But with all the intervening access specifiers, a compiler would be allowed to arrange the members as i1,i2,c1,c2 and make sizeof(inefficient) 12. What I personally don't understand is why private members - or in fact all members of non-PODs - can't always be re-ordered. It's not like anyone should ever depend on their order. It probably was C compatibility that there were any guarantees at all. Sebastian Redl

It does. It says that members of a struct or class (even non-POD ones, which I think is a useless restriction) that have no intervening access specifier are stored in memory in the same order as they are declared.
[snip]
What I personally don't understand is why private members - or in fact all members of non-PODs - can't always be re-ordered. It's not like anyone should ever depend on their order. It probably was C compatibility that there were any guarantees at all.
Binary compatibility is one reason: you need a guideline that allows compilers to agree on class layout. It's a pain, but it's necessary if ever you're to compile a .h in two separate places (and even in two different compiler versions, to some extent) and have the same class layout. JF

On Tue, 02 Oct 2007 20:21:12 +0200, Sebastian Redl <sebastian.redl@getdesigned.at> wrote:
Marco wrote:
What I'm interesting is if the standard says something about declaration order of struct fields and their storage order in memory. It does. It says that members of a struct or class (even non-POD ones, which I think is a useless restriction) that have no intervening access specifier are stored in memory in the same order as they are declared. 9.2/12 says: Nonstatic data members of a (non-union) class declared without an intervening /access-specifier/ are allocated so that later members have higher addresses within a class object. The order of allocation of nonstatic data members separated by an /access-specifier/ is unspecified. Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions and virtual base classes.
That means:
class foo { int i1; int i2; public: int i3; int i4; public: int i5; };
->
&i1 < &i2 == true &i3 < &i4 == true &i1 < &i3 unspecified &i3 < &i5 unspecified
What surprise me it's that the order of allocation is unspecified even if two adjacent access specifiers are the same. That is &i3 < &i5 unspecified. So access specifiers could be used to hint the compiler that it could reorder member allocation according to its needs.
In particular, although no compiler I know of does it, an implementation can reorder fields to e.g. make the allocation pattern more efficient:
class inefficient { char c1; private: int i1; private: char c2; private: int c2; };
Naively, on a typical 32-bit platform, sizeof(inefficent) would be 16. But with all the intervening access specifiers, a compiler would be allowed to arrange the members as i1,i2,c1,c2 and make sizeof(inefficient) 12.
I see, maybe on some exotic platform this could actually happen, or if the size optimization option is passed to the compiler.
What I personally don't understand is why private members - or in fact all members of non-PODs - can't always be re-ordered. It's not like anyone should ever depend on their order. It probably was C compatibility that there were any guarantees at all.
Sebastian Redl
Thanks for the further explanation. Regards, Marco Cecchetti -- Using Opera's revolutionary e-mail client: http://www.opera.com/mail/

Sebastian Redl wrote:
What I personally don't understand is why private members - or in fact all members of non-PODs - can't always be re-ordered. It's not like anyone should ever depend on their order. It probably was C compatibility that there were any guarantees at all.
Why make POD-types a special case? I like being able to depend on the order of private members. It allows me to nicely encapsulate my data while using SIMD operations internally. I think that if the compiler figures out that my members would be better off in a different order I'd like it to warn me so I can reorder them myself. I can imagine a cache performance degradation because it reorders my members to make the class size smaller but must access memory randomly in order to initialize them in the constructor in the order they appeared in the class definition instead of accessing them with ascending addresses if they were laid out the order in which they were specified. - Michael Marcin

Michael Marcin wrote:
I like being able to depend on the order of private members. It allows me to nicely encapsulate my data while using SIMD operations internally.
I can imagine a cache performance degradation because it reorders my members to make the class size smaller but must access memory randomly in order to initialize them in the constructor in the order they appeared in the class definition instead of accessing them with ascending addresses if they were laid out the order in which they were specified. Possible, but not very likely on today's desktop systems, given typical class sizes. A L1 cache line on x86 usually has 64 bytes - enough for 16 integers/pointers, still enough for 8 pointers on x86-64. Thus, any object not exceeding this size would always fit into one cache line, and
You use SIMD operations on non-uniform data? What kind? there is no penalty to accessing the single line in random order. And let's not forget that it might be _because_ of the reordering that the object has shrunk to 64 bytes in the first place. On the other hand, if the compiler sees that the object IS larger, it could simply not reorder the elements. I find the argument about binary compatibility more convincing. It would indeed be tricky to work out good, predictable rules. Sebastian Redl

Sebastian Redl wrote:
Marco wrote:
Are you speaking about rules provided in the C++ ISO standard ? Could you point me to them ? (page, section). I'd like to give them a look .
Reinterpret_cast is defined in 5.2.10. The language there is rather clear about what is guaranteed and what not. Judging by that section, you can't rely on the results of the cast for anything except casting it back.
Yes. To give a practical example: GCC will complain about invalid type casts, since they break its ability to optimize based on proper alias analysis. The issue is not about layout compatibility, but the compiler's ability to infer whether two pointers may refer to the same address. If their types differ, they may not, in general. Regards, Stefan -- ...ich hab' noch einen Koffer in Berlin...

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Tuesday 02 October 2007 17:47 pm, Stefan Seefeld wrote:
Sebastian Redl wrote:
Marco wrote:
Are you speaking about rules provided in the C++ ISO standard ? Could you point me to them ? (page, section). I'd like to give them a look .
Reinterpret_cast is defined in 5.2.10. The language there is rather clear about what is guaranteed and what not. Judging by that section, you can't rely on the results of the cast for anything except casting it back.
Yes. To give a practical example: GCC will complain about invalid type casts, since they break its ability to optimize based on proper alias analysis. The issue is not about layout compatibility, but the compiler's ability to infer whether two pointers may refer to the same address. If their types differ, they may not, in general.
Yes, as I learned once by trying to change the byte-ordering of floats by manipulating them through a uint32_t*, once your turn optimization on your code breaks badly. The only exception is that char* is allowed to alias with anything (don't think you can bounce a pointer through a char* into something else though). gcc allows (as an extension) using a union to access the same chunk of data a more than one type. See http://mail-index.netbsd.org/tech-kern/2003/08/11/0001.html - -- Frank -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) iD8DBQFHA5Jj5vihyNWuA4URAhYDAKCfTGJaBkE83UuGouCR1gKujRsPsgCfcKkC 0J8bm7kqPcHfCVoNfXYuztg= =Dl7P -----END PGP SIGNATURE-----

on Tue Oct 02 2007, Sebastian Redl <sebastian.redl-AT-getdesigned.at> wrote:
The implicit guarantee is there because there is no other way to convert the pointer except through a reinterpret_cast. (A C-style cast is defined in terms of the cast operator is takes the place of.)
Layout compatibility is not mentioned anywhere else. So what does that mean?
It means that, by the words of the standard, your code was not valid. On the other hand, making both structs the members of a union, then assigning one and reading the other, is valid. Make of that what you will.
If you want to make it valid, replace reinterpret_cast<X*>(a) with static_cast<X*>(static_cast<void*>(a)) -- Dave Abrahams Boost Consulting http://www.boost-consulting.com
participants (9)
-
David Abrahams
-
David RodrÃguez Ibeas
-
Frank Mori Hess
-
Jean-Francois Bastien
-
Marco
-
Marco Servetto
-
Michael Marcin
-
Sebastian Redl
-
Stefan Seefeld