0

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?

Phenglei Kai
  • 413
  • 5
  • 14
  • 2
    Because this is implementation-defined behavior. The C++ standard does not require any particular result from any implementation. https://gcc.gnu.org/onlinedocs/gcc/Empty-Structures.html#Empty-Structures appears to suggest that this gcc behavior cannot be changed. – Sam Varshavchik Mar 11 '20 at 00:22
  • clang and gcc both run on Windows and Linux , it would help to clarify this in your question rather than conflating compilers and operating systems. Also give details of the target, as the x64 ABI differs from 32-bit ABIs – M.M Mar 11 '20 at 00:23
  • 1
    You could get real hacky and add a preprocessor directive to the `derived_struct_container` that puts a single `char` in the struct if you're on a windows system. That is to say, you don't. Is there a purpose to this beyond academic curiosity? – JohnFilleau Mar 11 '20 at 00:26
  • I get output `0 1 5` in Windows (g++ 9.2.0 target x86_64-w64-mingw32, and same for target i686-w64-mingw32) – M.M Mar 11 '20 at 00:26

1 Answers1

3

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):

  • Use clang or g++ instead of MSVC, in Windows.
  • Try passing flags to MSVC to change EBCO behaviour see here for a writeup, maybe it can be made to give the 0 1 5 version.
  • Edit the source code of gcc or clang to build your own compiler and give the desired layout.

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.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • [[no_unique_address]] can be applied only for non-static data member , but in example no non-static data member with empty type . `__declspec(empty_bases)` more related here – RbMm Mar 11 '20 at 01:38
  • @RbMm that's a MSVC-only thing, right? I mention it in my bullet list – M.M Mar 11 '20 at 01:40
  • yes, this is from your link. but `[[no_unique_address]]` here can not help because only affect non-static data member with empty type, but not affect inherit. need and in general c++ attribute like `__declspec(empty_bases)` – RbMm Mar 11 '20 at 01:43
  • The `struct E2` suggestion was a great one and solved the problem. It looks like the [empty base optimization is prohibited](https://en.cppreference.com/w/cpp/language/ebo) if one of the empty base classes is also the type or the base of the type of the first non-static data member. I am converting a legacy Windows MSVC code base to clang so this was the best solution. – Phenglei Kai Mar 11 '20 at 15:24