0

I'm trying to understand struct and class padding in depth, so I devised an example I considered more challenging than many of the examples I found in tutorials on the topic. I compiled it in an x64 machine with g++, without enabling any code optimization. My code is as follows:

class Example
{
    private:
        long double foobar;     // 10 bytes + 6 padded bytes as double follows
        double barfoo;          // 8 bytes + 8 padded bytes
        static float barbar;    // didn't count as it's a static member
        float *fooputs;         // 8 bytes + 8 padded bytes
        int footsa;             // 4 bytes, stored in the padded portion of float
        char foo;               // 1 byte, stored in the padded portion of float

    public:
        int function1(int foo) { return 1; }
        void function2(int bar) { foobar = bar; }
};

int main()
{
    std::cout << sizeof(Example) << std::endl;   // 48 bytes
    return 0;
}

Although I see that the size of Example is 48 bytes, I expected it to be 37 bytes. The argumentation on my expectation is as follows:

  • foobar needs 10 bytes. As double follows, 6 more bytes are needed for padding.
  • barfoo needs 8 bytes, as it's a double. No need for padding, as mod(16,8) == 0
  • *fooputs needs 8 bytes, as it's a pointer in an x64 architecture. No need for padding, as mod(24,8) == 0
  • footsa needs 4 bytes as an int. No need for padding, as mod(32,4) == 0
  • foo needs 1 byte as a char. No need for padding.

As the result is different that the expected, I tried to understand how C++ evaluated the size of Example to 48 bytes by commenting in and out class members. So, besides of the argumentation for foobar I assumed the justifications I'm writing in my inline comments for each member.

Could anyone explain me how the size is evaluated to 48 bytes and if my justifications are correct?

Kapoios
  • 688
  • 7
  • 22
  • See this for why a size of 37 would be bad: https://stackoverflow.com/questions/58435348/what-is-bit-padding-or-padding-bits-exactly/58436082#58436082 – NathanOliver Jun 08 '20 at 15:56
  • Your comments in the code suggest a sum of 45, not 37. – cigien Jun 08 '20 at 15:58
  • @DanielLangr can you elaborate a bit more on the "final padding" done by sizeof? – Kapoios Jun 08 '20 at 16:02
  • I count 48 bytes. Your comments are correct. `long double` + `double` + `float*` = 16 + 16 + 16 = 48. (I ignored all vars which got padded space.) – Darkproduct Jun 08 '20 at 16:02
  • Isn't `long double` 8 bytes on x64? The 80 bits format is for x87 in 32 bits mode; x64 uses SSE2. – MSalters Jun 08 '20 at 16:04
  • @MSalters No, for `long double`, "good old" FPU instructions are typically used: https://godbolt.org/z/Aru6kk. – Daniel Langr Jun 08 '20 at 16:06

1 Answers1

1

You forget about the final padding. sizeof returns a number of bytes between two adjacent members in an array. In your case, alignof(long double) is very likely 16, therefore each Example instance requires to be at 16-byte aligned address.

Consequently, if you have first instance of Example at a 16-bytes aligned address A, and then there are 37 bytes required by members, the next Example instance cannot be stored at A + 37 bytes, but it needs to be stored at A + k * 16. The smallest possible k that satisfies k * 16 >= 37 is 3. Which finally gives you the number of bytes between two Example instances in an array 3 * 16 = 48, which is exactly sizeof(Example).

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93