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 ?

Thomas Jager
  • 4,836
  • 2
  • 16
  • 30
Elcan
  • 814
  • 13
  • 27
  • 1
    Because the compiler is padding to align members. – user3386109 Mar 28 '19 at 22:50
  • Shouldn't it be aligned already ? The bitfields sum to 8 bits – Elcan Mar 28 '19 at 22:51
  • Specifically, there's a byte of padding between `osc1_octave` and `osc2_multisound`. – user3386109 Mar 28 '19 at 22:51
  • `int16_t osc1_multisound; //int int8_t osc1_octave; // -2..1 int16_t osc2_multisound; //int` necessarily, at least one of the int16_t structs needs to be padded to be aligned. – MFisherKDX Mar 28 '19 at 22:53
  • Why is there ? Can't struct hold 8-bit variables without padding ? – Elcan Mar 28 '19 at 22:53
  • 3
    has nothing to do with your bitfield. it has to do with you added a `int8_t` between two `int16_t` – MFisherKDX Mar 28 '19 at 22:54
  • Is there a way to force an 8-bit alignment ? I'm reading a binary file and I need both 16-bit values, and the proper 8-bit alignment – Elcan Mar 28 '19 at 22:56
  • 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 `#pragma`s and/or `attribute`s to change that behavior. [Here's an example of the latter](https://stackoverflow.com/questions/7025965). – user3386109 Mar 28 '19 at 22:56
  • https://stackoverflow.com/questions/1496848/does-unaligned-memory-access-always-cause-bus-errors – MFisherKDX Mar 28 '19 at 22:59
  • "Is there a way to force an 8-bit alignment?" Yes, there are several choices that do not depend on compiler choices or compiler padding. My background in embedded software included messages sent between different processors, and the messages were binary, and in that system, padding was perceived to waste space and time. But this sounds like another question ... poke me if you post a new one. – 2785528 Mar 29 '19 at 01:18

1 Answers1

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.

Cruz Jean
  • 2,761
  • 12
  • 16
  • I wish I could align them as i want, but sadly I can't. The file format forces that order – Elcan Mar 28 '19 at 23:09
  • @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. – Cruz Jean Mar 28 '19 at 23:11
  • 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 Mar 28 '19 at 23:13
  • @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). – Cruz Jean Mar 28 '19 at 23:21
  • Writing the 1000 lines of code to manually set each member of the struct is the only safe/portable way to do it, AFAIK. – Jeremy Friesner Mar 28 '19 at 23:24
  • @CruzJean I know, but there's far more fields, this was just a MCVE to not bloat the question with useless data – Elcan Mar 28 '19 at 23:25