VC++ 2013 class Layout Change and Wasted Space

The VC++ compiler generally maintains a high degree of binary compatibility between compiler versions, allowing objects to be passed between DLLs made with different compiler versions. However VS 2013 made changes to the layouts of some classes in 64-bit builds.

Luckily this only affects a tiny percentage of classes, and if you do hit this problem there is a simple solution. This simple solution can also be used to reduce the size of classes in both 32-bit and 64-bit builds.

The layout change happens if you have a class that has virtual functions, does not derive from a class with virtual functions, contains a member that needs 16-byte alignment or higher which isn’t the first data member, and you are compiling for 64-bit.

Clarification: the VC++ compiler maintains a high-level of binary compatibility to allow DLLs built with one version to coexist with DLLs built with another version. The C++ run-time and STL do not maintain binary compatibility between versions.

This class layout change was notable to me because I could not think of another time when a VC++ upgrade has changed the layout of a class (other than through the class definition changing, which is what happens with the CRT and STL). If you know of a case where the layout has changed, let me know in the comments.

What VS 2010 does

To make this discussion concrete, here’s a class definition:

class VirtualVecOne
{
public:
    virtual ~VirtualVecOne() {}
    void* p;
    __m128 v;
};

This class will consist of a v-table pointer, the pointer member variable p, and the vector member variable v. VC++ aligns built-in types in structs and classes to a multiple of their size, but if you squeeze the items in this class together then in 64-bit builds they automatically end up naturally aligned, so it is possible to lay out VirtualVecOne like this:

image

And with 64-bit builds in VC++ 2010 and VC++ 2012 this is exactly how this class is laid out. Short, sweet, no padding. But it appears that this was a bug.

What is supposed to happen (in VC++)

For historical reasons the VC++ rule for laying out these classes is that the first member of the class after the v-table pointer should be aligned at the highest alignment requirement of any of the class members. That is, if the class has an __m128 member that requires 16-byte alignment then the first member is supposed to* be 16-byte aligned. That means the compiler inserts padding bytes after the v-table, and perhaps elsewhere in the class. That’s not ideal, but it’s been the VC++ layout rule for decades, and this layout is what VC++ 2013 does.

* By ‘supposed to’ I mean by VC++ rules not by C++ standard rules. The standard leaves padding of struct/class layouts to the implementation.

In other words, according to the VC++ rules, the 64-bit compiler is supposed to lay out the class like this:

image

Note the 16 bytes of padding in two blocks of 8. With VC++ 2013 the 64-bit compiler changed from Layout A to Layout B. The offsets of p and v and the size of the class all changed. I discovered this because one class (out of thousands in the ~600 projects I ported) was laid out similarly to VirtualVecOne. When we passed it from a VC++ 2010 DLL to a VC++ 2013 DLL, things broke.

For completeness, here’s what the class looks like in the 32-bit compiler, all versions, with 24 bytes of padding:

image

Workarounds

Layout B may be the VC++ standard, but it is bloated, and it is incompatible with VC++ 2010, and both of these are potential  problems. What I wanted was a way to get VC++ 2013 to use the old layout style. And I found the answer because of a tweet that was pseudo-randomly sent to me a few days earlier. That tweet linked to a bug which talked about layout errors in the VC++ x64 compiler. That bug linked to a page which explained the layout rules, and showed how to change them. Pretty cool. My superpower is learning things just-in-time and it worked perfectly this time.

The workaround is to inherit from a class that has virtual functions. An otherwise empty class with a virtual destructor is the ideal choice. So I changed the class to look like this:

class LayoutFixer
{
public:
    virtual ~LayoutFixer() {}
};

class VirtualVecOne : public LayoutFixer
{
public:
    virtual ~VirtualVecOne() {}
    void* p;
    __m128 v;
};

That’s it. Problem fixed. The class is now laid out efficiently (Layout A) and the layout is now consistent between VC++ 2010, 2012, and 2013. The structure will also get smaller in 32-bit builds. Drink some Scotch and go home early.

Note that while the compatibility aspect of this problem doesn’t happen with the 32-bit compiler, the wasted space issue does. So the empty base class technique is valid for 32-bit programs and can save space in classes with virtual functions and double, __int64, or __m128 members.

Diagnostics

There are a few things to know when investigating this sort of thing. One is to watch the debugger. I realized what was happening when I stepped from one function to another and saw that the object whose address I passed across the boundary went from valid to invalid. I could go up and down the stack and see that this object – with its address unchanging – would be displayed correctly and then incorrectly, depending on which DLL contained the active function on the stack. It was pretty cool actually. The lesson there is that debug information is associated with a particular source file and may vary within a debug session.

In the world of Windows it is actually entirely legal to have the layout of a class change between DLLs. The one-definition-rule applies on a per-DLL basis. This means that I can have a class called Vector and you can have a class called Vector and our DLLs can coexist in one process. If we pass a Vector from one DLL to another then their definitions had better match, but it is our job to ensure that!

You can get VC++ to dump the layout of a particular class or all classes by using the undocumented /d1reportSingleClassLayoutVirtualVecOne or /d1reportAllClassLayout flags. The output is a bit weird – it doesn’t reliably indicate padding – but it can still be quite useful. Here’s the VC++ 2013 result with LayoutFixer – it’s similar to the results you would get with VC++ 2010:

class VirtualVecOne    size(32):
    +—
    | +— (base class LayoutFixer)
0  | | {vfptr}
    | +—
8  | p
16  | __m128 v
    +—

Here’s the VC++ 2013 result without LayoutFixer – note that it explicitly indicates the padding after ‘p’ but not the padding before:

class VirtualVecOne    size(48):
    +—
0  | {vfptr}
16  | p
    | <alignment member> (size=8)
32  | __m128 v
    +—

Finally, when investigating class layout issues you can always use offsetof together with printf or static_assert, like this:

printf(“Offset of VirtualVecOne::p is %u\n”, (unsigned)offsetof(VirtualVecOne, p));
printf(“Offset of VirtualVecOne::v is %u\n”, (unsigned)offsetof(VirtualVecOne, v));
printf(“sizeof(VirtualVecOne is %u\n”, (unsigned)sizeof(VirtualVecOne));

Why has nobody else reported this compatibility problem?

  • Most people haven’t upgraded to VC++ 2013 yet
  • It’s 64-bit only
  • It only happens if you mix VC++ 2010/2012 with VC++ 2013 binaries
  • Many classes don’t have virtual functions.
  • Most classes don’t contain __m128 members.
  • Those that have virtual functions often inherit from classes that have virtual functions (interfaces) which avoids the bug.

Remember that the space wastage issue is much more common than the compatibility problem, so look for that in your 32-bit and 64-bit types if you create a lot of them.

Related notes

I wrote a while ago about the poorly understood behaviors of bitfields in structures.

Extra credit question

What are 64-bit integer variables aligned to? Answer for as many x86/x64 compilers as possible. I only learned this earlier this year.

Talkback

Some people on reddit disagreed with my claim that the VC++ compiler generally maintains binary compatibility. One commenter found documentation of a structure layout change between VC++ 4.2 and 5.0. So that’s two in about twenty years, one of which is documented. That, plus my discussions with VC++ developers, leaves me feeling okay about my claim.

Another reader (I love my readers – I learn a lot from writing this blog) found some VC++ 2013 documentation that explains this breaking change and how to detect it. Pretty cool. Go to http://msdn.microsoft.com/en-us/library/bb531344.aspx and search for “Object layout has changed”. Note in particular that warning C4370 with VS 2010 can be used to detect this problem.

About these ads

About brucedawson

I'm a programmer, working for Valve (http://www.valvesoftware.com/), focusing on optimization and reliability. Nothing's more fun than making code run 5x faster. Unless it's eliminating large numbers of bugs. I also unicycle. And play (ice) hockey. And juggle.
This entry was posted in Programming, Visual Studio and tagged , , . Bookmark the permalink.

13 Responses to VC++ 2013 class Layout Change and Wasted Space

  1. nikbackm says:

    Does VC++ promise that struct layout will stay the same between compiler versions? Seems like something that can change if you merely change the the compiler switches, so trusting that seems dangerous. But I guess you have to unless you stick to using only non-composite types in DLL interfaces.

    • brucedawson says:

      Binary compatibility is very important and it is extremely rare (I cannot think of another case) for VC++ to change the layout of class/struct objects between versions. Normally the only time that layouts change is if you change the class/struct, use #pragma pack, or change the compiler packing settings (which is a bad idea).

      Some STL objects change size based on STL debug settings and that is a very specific well documented case, that still cause confusion and problems.

      I’m sure that gcc and clang are similarly conservative because otherwise many things break.

      If you can think of another time where VC++ changes class layout let me know — I’d be interested.

  2. Are you sure? I see the same behavior in 2010 and 2012. I thought this was something they fixed when migrating from x86 to x64, since there’s (in theory) no binary compatibility concern:

    $ cat t.cpp
    #include
    #include
    struct A {
    virtual void foo();
    void *a;
    __m128 b;
    };
    int main() {
    printf(“%d\n”, sizeof(A) / 8);
    }

    $ (compiler64 && cl t.cpp && ./t.exe )
    Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64

    4

    $ (compiler64_2012 && cl t.cpp && ./t.exe )
    Microsoft (R) C/C++ Optimizing Compiler Version 17.00.60610.1 for x64

    4

    4 here means there was no padding between the vfptr, a, and b.

    • brucedawson says:

      Note that you can’t use %d to print sizeof(), unless you first cast it to int. In 64-bit builds sizeof() returns a 64-bit value.

      I’m not sure why you’re dividing the size by eight — that seems confusing. But, I’ll translate your output of 4 to 32-bytes. Which is exactly what I said you would get in VC++ 2010 and 2012. The change happens in VC++ 2013 where that struct grows to 48 bytes. Was my post confusing somewhere?

  3. Severin Pappadeux says:

    You’re paying with another call to dtor, aren’t you?

    • brucedawson says:

      It’s possible that the extra destructor will be inlined, but probably not, so yes, there is some extra cost. I can’t imagine it would be significant, compared to the cost of the memory free that is presumably also happening.

      • Ofek Shilon says:

        The dtor body is probably inlined – but you *are* most probably paying with an extra vtable switch in the ctor/dtor: http://ofekshilon.com/2010/06/03/on-_purecall-and-the-overheads-of-virtual-functions/ (or did the VC2013 compiler grow smart enough to avoid that?)
        Does the workaround work even if the parent LayoutFixer has only pure virtuals? If so, it is probably best to add to it declspec(novtable).
        Kudos for the great find!

        • brucedawson says:

          A base-class of pure virtuals should be fine. All that is needed to control the class layout is a base class that has a v-table. The contents of the v-table are irrelevant for layout purposes.

          As for the details of the cost, it doesn’t seem important enough for me to want to bother investigating. If I’m repeatedly calling the destructor in a loop then I should stop doing that, rather than optimizing the destructor.

  4. Pingback: VC++ 2013 Class Layout Change and Wasted Space | musingstudio

  5. Will says:

    That reminded me of http://lolengine.net/blog/2012/10/21/the-stolen-bytes (not quite the same issue, but same workaround). “What if I told you… Inheriting from an empty abstract class could make your objects smaller?” ^^

    • brucedawson says:

      Yep, that is the article (with a slightly different URL) where I found the solution. It was quite fortuitous that a reader tweeted me a link to a bug he had filed contained a link to the stolen-bytes article, since otherwise I’m not sure I could have solved the problem.

  6. Pingback: Today I Learned: Struct Packing With A Microsoft Twist | Anthony's Scribbles

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s