C from the ground up

Grouping values with struct

When values belong together, wrap them in a struct. Define a new type, name its fields, and give it a short alias with typedef.

Part 9 of 12

Advanced15 min readBack to Courses

One value, several parts

A person is not one number: there is a name and an age. A struct bundles such values into a single type. You describe the parts once, then every value of that type carries all of them.

Here is the shape: struct Person { char name[20]; int age; };. The parts are called fields or members, and the type is now usable anywhere a type can go.

Reaching a field

You get at a field with a dot. ada.age reads the age of the ada variable, and ada.age = 36; writes it. Reading and writing a field is the same as reading and writing any variable.

You can also set every field at once when you declare the value: Person ada = {"Ada", 36};. The values must be in the same order the fields were listed.

Giving it a short name

Writing struct Person every time gets old. The typedef keyword makes an alias: typedef struct { ... } Person; lets you write just Person, as if it were a built in type.

Grouping fields also makes functions clearer. A function that takes one Person says more than one that takes a name and an age as two unrelated arguments.

A struct is a layout

The fields are stored in the order you declare them, each at an offset from the start of the struct. The compiler may insert padding between fields so each one starts at an address its type likes, which is why sizeof(Person) can be larger than the sum of its fields.

Field order is part of the type's cost, not just its meaning: reordering two fields can change the padding and the size. Assignment copies the whole layout, so Person copy = ada; gives copy its own name bytes and its own age. Changing copy.age leaves ada.age alone, and an array inside a struct is copied too.

Reaching a struct through a pointer

A function that takes Person receives a copy of every field, which is fine for a small struct and wasteful for a large one. Taking Person * passes one address and lets the function change the original.

With a pointer, the dot needs a dereference first: (*p).age. That is clumsy enough that C gives the arrow: p->age means exactly the same thing. Use the dot on a value and the arrow on a pointer.

Arrays of structs

A struct is a type, so it can be the element type of an array: Person people[3]; is room for three people, each with its own name and age. The array is one block, and every element is the full struct laid out one after another. people[i].age reaches the age of one of them.

An initialiser nests one brace per level: Person people[2] = {{"Ada", 36}, {"Bob", 41}};. A loop over the array uses the same index for both fields, so a report can print a name and a score from the same element without keeping two lists in step. That is the real reason to group the fields.

Initialisers, named and positional

Person ada = {"Ada", 36}; fills the fields in declaration order, so a value written in the wrong position lands in the wrong field: the compiler only catches it when the types do not fit. C99 lets you name them instead, as .name = "Ada", .age = 36, which survives a reordering of the struct and says at the call site which value is which.

Anything you leave out of an initialiser is zeroed, and a compound literal writes the same thing inline: (Student){"Ada", 90} makes a temporary struct value for an assignment or an argument. The cast-like syntax is not a cast; it is a struct literal, and the braces carry the field order just as they do in a declaration.

Three struct mistakes are easy to make: comparing two structs with ==, which C does not allow; forgetting the semicolon after the closing brace of the definition; and using the arrow on a value or the dot on a pointer. Compare field by field, keep the semicolon, and match the operator to what sits on the left.

Try these next

  • Add a double height field and print sizeof(Person) before and after; explain the change.
  • Write a function that takes a Person by value and changes its age; show that the caller is unchanged.
  • Write the same function with Person * and ->; show that the caller changes.

A small struct

#include <stdio.h>

typedef struct {
    char name[20];
    int age;
} Person;

int main(void) {
    Person ada = {"Ada", 36};
    printf("%s is %d\n", ada.name, ada.age);
    return 0;
}

Two people

Run it, then add a third Person and print it. Then change the order of the values in one initialiser and see what the fields report.

Output
Click "Run" to execute your code

Change a score through an arrow

Run it, then add a third student and pass it to `bump`. Check that the printed score changes for the original, not a copy.

Output
Click "Run" to execute your code

Fields can be any type, including other structs and arrays. A struct that holds a struct is how C models records within records without any extra syntax.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does a struct do?