Structs and padding

A struct bundles several values, of possibly different types, into one new type:

struct Player {
    char name[16];
    int hp;
    int level;
};

struct Player p = {"Ada", 100, 1};
p.hp = p.hp - 30;    // dot reaches a field

In memory, a struct is one block with the fields laid out in the order you declared them. There is nothing hidden: no type tag, no methods, just bytes.

Structs are passed by value like everything else, so a function that takes a struct Player gets a full copy. To change the original (or just avoid copying a big struct), pass a pointer. Then you reach fields with the arrow operator:

void heal(struct Player *pl) {
    pl->hp = pl->hp + 20;    // same as (*pl).hp
}

pl->hp is simply shorthand for (*pl).hp, which nobody enjoys typing.

Now a surprise. What is sizeof this?

struct Mixed { char a; int b; char c; };

You might say 1 + 4 + 1 = 6. On this runner it is 12. Most CPUs prefer (or require) an int to start at an address that is a multiple of 4, its alignment. So the compiler inserts 3 unused padding bytes after a, and 3 more after c so that in an array of these structs, every element's int stays aligned. Padding is implementation-defined, but this layout is very typical.

Reorder the fields as int b; char a; char c; and it drops to 8. Put big fields first and you waste less space.

Try it yourself

Edit it. Break it. Run it again.
#include <stdio.h>

struct Player {
    char name[16];
    int hp;
};

struct Mixed { char a; int b; char c; };

void heal(struct Player *pl, int amount) {
    pl->hp = pl->hp + amount;
}

int main(void) {
    struct Player p = {"Ada", 70};
    heal(&p, 25);
    printf("%s has %d hp\n", p.name, p.hp);
    printf("sizeof(struct Player) = %zu\n", sizeof(struct Player));
    printf("sizeof(struct Mixed) = %zu, not 6\n", sizeof(struct Mixed));
    return 0;
}
Ctrl/Cmd + Enter runs. Esc, then Tab, leaves the editor.

Your turn

Type it yourself. That is the whole trick.

1Move the point

Finish void move(struct Point *pt, int dx, int dy) so it adds dx to the point's x and dy to its y, using ->. Expected output: (5, -1)

#include <stdio.h>

struct Point {
    int x;
    int y;
};

void move(struct Point *pt, int dx, int dy) {
}

int main(void) {
    struct Point p = {2, 3};
    move(&p, 3, -4);
    printf("(%d, %d)\n", p.x, p.y);
    return 0;
}
Ctrl/Cmd + Enter runs. Esc, then Tab, leaves the editor.

2Spot the padding

Define two structs with the same three fields in different orders: struct Loose with char a; int b; char c; and struct Tight with int b; char a; char c;. Print their sizes with sizeof:

  • Loose: 12
  • Tight: 8
#include <stdio.h>

// define struct Loose and struct Tight here

int main(void) {
    printf("Loose: %d\n", 0);
    printf("Tight: %d\n", 0);
    return 0;
}
Ctrl/Cmd + Enter runs. Esc, then Tab, leaves the editor.

Code editor. Press Control or Command plus Enter to run the code. Tab indents; to move focus out of the editor, press Escape and then Tab.