The following code generates a different layout in memory on MSVC vs clang/gcc. Why?
#include <stdio.h>
#pragma pack(push,1)
struct empty_struct
{
};
class derived_struct : public empty_struct
{
int m_derivedMember;
};
class derived_struct_container : public empty_struct
{
public:
derived_struct m_ds;
};
class foo
{
public:
foo() {}
derived_struct_container m_dsc;
int m_foo_member;
};
#pragma pack(pop)
int main()
{
foo fb;
printf("pf->m_dsc offset: %ld\n", (char *)&fb.m_dsc - (char *)&fb);
printf("pf->m_dsc.m_ds offset: %ld\n", (char *)&(fb.m_dsc.m_ds) - (char *)&fb);
printf("pf->m_foo_member offset: %ld\n", (char *)&(fb.m_foo_member) - (char *)&fb);
return 0;
}
The output on MSVC x64 is:
fb.m_dsc offset: 0
fb.m_dsc.m_ds offset: 0
fb.m_foo_member offset: 4
The output on clang x64 under Linux is:
fb.m_dsc offset: 0
fb.m_dsc.m_ds offset: 1
fb.m_foo_member offset: 5
How would I get the clang layout to match the MSVC layout?
The use of #pragma pack
causes implementation-defined behaviour.
Also, foo
is not a standard-layout class due to having multiple base class subobjects of the same type, so even without the pack
its layout is not subject to any ABI.
Relying on the layout of a non-standard-layout class is, frankly, a terrible idea and there is certainly a better way to achieve whatever the goal is here.
Here are some possible approaches that don't involve changing the code (of course even if any of these appear to work for now, it could change at any time):
In gcc the empty base class optimization is disabled by the class having two bases of the same type, so you can enable it with a code change as suggested in comments under this question:
struct empty_struct {};
struct E2 {};
class derived_struct : public E2
(and the rest of the code the same as your example). That gives me the 0 0 4
output even without pragma pack. I'm not aware of any flags for gcc or clang that would change the EBCO behaviour.
The rationale for this rule is that in Standard C++ if two valid pointers of the same type have the same value then they must point to the same object. The two empty subobjects are different objects therefore there must exist unique addresses for them. MSVC is non-conforming in this regard.
In C++20 there is an attribute [[no_unique_address]]
that supposedly relaxes this requirement, however I tried it out in my installation of g++ 9.2.0 and it did not change the layout. Not sure if that is a bug or intended behaviour, but either way, it doesn't seem to be a solution to the problem.