Search code examples
cstructsizeofmemory-alignmentbit-fields

Size of a struct with union and bitfields


I'm trying to count the size in bytes of this struct and have a couple of questions

struct stc {
    int a;
    int b;
    char c;
    union stc2 {
        long a0;
        int a1;
        int a2;
    };
    char arr[10];
    int z:2;
};

I'm checking the size in this way:

int main(void) {
    printf("%zu\n", sizeof(struct stc));
}

and compile with:

gcc -std=c99 -m32 -Wall -Wextra test.c

It's gcc 4.9 and I'm on 64-bit computer but first want to try with 32-bit values so -m32. Now, the result is 20, but I've no idea why is that. This is how I'm counting.

struct stc {     
    int a;        // +4 = 4
    int b;        // +4 = 8
    char c;       // +1 but next is long, so +4 (alignment) = 12
    union stc2 {
        long a0;  // +4 = 16
        int a1;
        int a2;
    };
    char arr[10]; // +8 (for arr[8]) and 2 bits left = 24 (or 28)
    int z:2;      // +4 = 28 (or 32) 
                  // I'm not sure how it'll work with 2 bits left from char array and
                  // 2 bits from int, so that why I count 2 different values. 
};

This was strange so I tried to figure this out and I know that this union seem to be size 0.

struct stc {
    union stc2 {
        long a0;
        int a1;
        int a2;
    };

sizeof(struct stc) == 0. I think that is because of compiler optimization? If so, how to turn it off? If not, why is that? Another question is of course how to count the size of this struct properly (with or without optimization) and why the size of this simple struct:

struct stc {
    char arr[10];
};

is 10 not a 12. I've thought that every value is aligned to 4,8,12,16,20 etc if you won't say to compiler to not align values.


Solution

  • So you have a lot of questions in here. I'll answer some as I go:

    sizeof struct is zero?

    You gave the code:

    struct stc {
        union stc2 {
            long a0;
            int a1;
            int a2;
        };
    };
    

    Note that you declared no variables inside the struct. You probably meant to do the following, which will likely have the same size as sizeof(long).

    struct stc {
        union stc2 {
            long a0;
            int a1;
            int a2;
        } x;
    };
    

    Bitfield layouts

    So, let's try to figure out where 20 comes from. First, let's rerwite the type without the union that isn't doing anything (see previous heading).

    Also, note that structs with bitfields are not required to be laid out in memory the same way as normal types. But let's make some guesses anyways.

    struct stc {
        int a;        // Offset 0,  Size 4
        int b;        // Offset 4,  Size 4
        char c;       // Offset 8,  Size 1
        char arr[10]; // Offset 9,  Size 10
        int z:2;      // Offset 19, Size 1-ish
    };
    

    So it seems to fit fine.

    sizeof wasn't rounded up to 12?

    struct stc {
        char arr[10];
    };
    

    So, each type has a size and an alignment requirement. For char, both of these are 1. For uint32_t, they are usually both 4. But depending on the architecture, the alignment could be different.

    So in this case, the maximum alignment needs of any element of the struct stc is 1. So the alignment requirements of the entire struct is still 1, even though the size is 10.

    Generally speaking, you want to add whatever padding is necessary to the size such that the declaration struct stc x[2] would cause all of the elements to be properly aligned.

    Recommendations

    So, there are a few things that you could do here to help learn what your compiler is doing.

    1. When posting questions about memory layout on stack overflow, it's very helpful to use the types defined in stdint.h. These include types like int32_t and int16_t. These are nicer because there is no confusion on what size a long is.

    2. You can use the offsetof tool to determine where a compiler is placing the members. You can learn some more about how to use it here.

    Padding can occur though:

    Here's a pretty simple example of a struct with a good bit of padding that the compiler would have to insert.

    struct x {
        uint64_t x; // Offset 0,  Size 8
        char     y; // Offset 8,  Size 1
                    // Offset 9,  Size 1 (Padding)
        uint16_t z; // Offset 10, Size 2
                    // Offset 12, Size 4 (Padding)
    };