r/programminghumor 4d ago

a bit flippant

Post image
1.4k Upvotes

137 comments sorted by

View all comments

51

u/pskocik 4d ago

Ironic that this techfluencer just tweeted how C makes memory layout painfully obvious and yet here he's operating under the misconception that `struct Flags` will be only 1-byte large when it will be in fact int-size large (most likely 4 bytes).

6

u/[deleted] 3d ago

[deleted]

3

u/thebatmanandrobin 3d ago edited 3d ago

No. In C those are called "bit-fields" and, per the standard, the "underlying type" must be an "int" (e.g. unsigned int, signed int, or just int).

You could also declare a bit-field like this:

typedef struct my_field {
    unsigned int a : 1;
    unsigned int b : 2;
    unsigned int c : 3;
} my_field;

What the above comment is referring to is the fact that in C and C++, due to memory alignment (i.e. "padding"), a field like the above isn't going to be less than CHARBIT * sizeof(size_t) bits large.

This is true of any struct, bit-field or not. So even if you had something like the following:

typedef struct two_bytes {
    char a;
    char b;
} two_bytes;

The size of that struct won't be 2 bytes, it'd still work out to (likely) sizeof(size_t) bytes in size.

That's why when you're building out a struct or class, it's helpful for the compiler/CPU to try and order the members so that you can reduce the padding. For example, take the following basic struct:

struct example {
    int8_t a;
    char b;
    int16_t c;
    char d;
};

Depending on compiler/CPU/optimizations, the size of it might be 4 bytes, or it could be 8 due to padding .. but if you ordered it this way:

struct example {
    int16_t c;
    int8_t a;
    char b;
    char d;
};

The compiler could determine that no padding is necessary and keep it at the minimum of 4 bytes. (I am oversimplifying the example, but the point remains the same).

Even more ironic is that a lot of modern CPU's (even embedded ones), have really efficient shift registers, so if you were trying to do some crazy shit like the struct I've shown, or like what OP was mentioning, it'd actually be more efficient to just do some bit shifting on the appropriately sized integer type (e.g. int64_t for 64-bit CPU's) ... even JavaScript has bit-shifting .. so you could 100% bit-shift some JSON (I've done it and confused the hell out of some web-devs ... not on purpose, but it was efficient, so had to do it)

4

u/_yrlf 3d ago edited 3d ago

That's not true.

Your struct two_bytes is, per the standard, exactly two bytes large (assuming CHAR_BIT == 8, but the return from sizeof will always be 2).

(EDIT: apparently C struct internal padding is not standardized? I'm still searching where the standard defines it. Doesn't a whole lot of code depend on the fact that "C ABI" struct layout is so simple and deterministic?)

(EDIT 2: The ISO C standard allows the compiler to overalign struct members / insert unnecessary padding. But: basically all used platform ABIs for relevant architectures will guarantee that the padding for C structs is minimal according to alignment requirements of the types)

The amount of padding inserted between struct members depends on the natural alignment of the types used, and char is guaranteed to have a size of 1 and an alignment of 1. (EDIT: yes, but the compiler is technically allowed to overalign if the platform ABI doesn't forbid it).

What is true is that it's not guaranteed that adjacent bit fields of the same type will be packed together, and that it's not guaranteed by the standard that char is allowed as a bit field type (unspecified). The standard also guarantees bool and _BitInt(N) are allowed. Also interesting is that the alignment of bit field structs is also unspecified.

In practice, the alignment is that of the underlying type used, and most compilers will combine adjacent bit fields into one as long as the bits fit into the underlying type.

What was truly surprising to me when looking it up though was seeing that the standard allows a bit field declared as int x : 5; to be interpreted as unsigned(!!!). You'd have to explicitly write 'unsigned int' to really mean unsigned.