2

I've had to deal with bitfields in structs recently, and came accross a behaviour I can't explain.

The following struct should be 9 bytes, according to individual sizeof. But doing a sizeof of the main struct yields 10 bytes.

The following program yields "10; 1 1 2 1 2 1 1 =9"

int main(){
    struct{
        uint8_t doubleoscillator;

        struct{
            char monophonic : 1;
            char hold : 1;
            char padding : 6;
        } test;

        int16_t osc1_multisound; //int
        int8_t osc1_octave; // -2..1
        int16_t osc2_multisound; //int
        int8_t osc2_octave; // -2..1
        int8_t intervall;
    }osc;
    std::cout << sizeof(osc) << "; ";

    int a[7];
    a[0] = sizeof(osc.doubleoscillator);
    a[1] = sizeof(osc.test);
    a[2] = sizeof(osc.osc1_multisound);
    a[3] = sizeof(osc.osc1_octave);
    a[4] = sizeof(osc.osc2_multisound);
    a[5] = sizeof(osc.osc2_octave);
    a[6] = sizeof(osc.intervall);

    int total = 0;
    for(int i=0;i<7;i++){
        std::cout << a[i] << " ";
        total += a[i];
    }

    std::cout << " = " << total << std::endl;
    return 0;
}

Why do the sum individual sizeof() of the internal variables of the struct yield a different result from a sizeof() of the osc struct ?

10
  • 1
    Because the compiler is padding to align members. Commented Mar 28, 2019 at 22:50
  • Shouldn't it be aligned already ? The bitfields sum to 8 bits Commented Mar 28, 2019 at 22:51
  • Specifically, there's a byte of padding between osc1_octave and osc2_multisound. Commented Mar 28, 2019 at 22:51
  • 3
    has nothing to do with your bitfield. it has to do with you added a int8_t between two int16_t Commented Mar 28, 2019 at 22:54
  • 1
    Unaligned accesses either don't work at all, or are less efficient than aligned accesses, depending on the processor. Typical behavior is to align 16-bit values on 16-bit boundaries. There are #pragmas and/or attributes to change that behavior. Here's an example of the latter. Commented Mar 28, 2019 at 22:56

1 Answer 1

2

Primarily for performance reasons, padding is added before each member of a struct to align said member in the structure's memory layout. Thus ocs2_multisound likely has a padding byte before it to ensure it appears at a number of bytes into the struct that is a multiple of 2 (because int16_t has an alignment of 2).

Additionally, after all that is done, the structure's total size is padded to a multiple of its strictest alignment requirement (i.e. the highest alignment of any held field). This is so that e.g. elements of an array of said type will all be properly aligned.

The alignment of a type can be checked at compile-time via alignof(T) where T is the type.

The increased size is unavoidable in this case, but the common advice for cutting down on padding bytes is to order struct members in order of descending alignment. This is because the next item is guaranteed to be properly aligned without the need for padding because the previous field was either the same alignment or stricter alignment. So if any padding is added, it will only be to pad the total size of the structure, rather than (wasted) padding between fields.

The reason for alignment is primarily for efficiency nowadays. Reading an unaligned block of memory on hardware that supports it typically is about twice as slow because it's actually reading the two memory blocks around it and extracting what it needs. However there's also hardware that simply will not work if you try to read/write unaligned memory. Such hardware typically triggers a hardware exception in this event.

Sign up to request clarification or add additional context in comments.

6 Comments

I wish I could align them as i want, but sadly I can't. The file format forces that order
@Elcan The file format? I hope you don't mean you're reading binary data into a struct. That's undefined behavior because the position of members isn't standardized. As is assuming what bitfields are placed where.
I do mean that. It's not a public software, and it's either that, or writing about 1000 lines of code filling the struct. I moved my int16_t to int8_t[2] and it now works as it should, thanks
@Elcan It should only take one unformatted binary read for each field (and you'd have to change the bitfield to be a normal integer type and use bitwise ops or write a wrapper for it).
Writing the 1000 lines of code to manually set each member of the struct is the only safe/portable way to do it, AFAIK.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.