Phase 3 · Structured & Modular CModule 11~46 min read

Structs, Unions, Enums & typedef

Model related data with structures, define named choices with enums, share storage with unions, and simplify types with typedef.

What you'll learn

User-defined types let a C program speak in domain concepts instead of loose collections of primitives. Structures group simultaneous values, enums name choices, and unions represent alternative views of shared storage.

By the end, you'll be able to:

  • Define, initialize, copy, and pass structure values
  • Use dot and arrow member access correctly
  • Model finite states with enums and variants with tagged unions
  • Account for alignment and padding without depending on one layout

Define structures

A struct creates one type from named members that exist simultaneously. The definition describes a layout; each declared object receives its own member storage.

student.c
#include <stdio.h>

struct Student {
    int id;
    char name[40];
    double average;
};

void print_student(const struct Student *student) {
    printf("#%d %s — %.1f\n",
           student->id, student->name, student->average);
}

int main(void) {
    struct Student maya = {
        .id = 7,
        .name = "Maya",
        .average = 93.5
    };
    print_student(&maya);
    return 0;
}

Initialize & copy

Designated initializers make member intent explicit and tolerate field reordering. Unlike arrays, complete structure objects may be assigned and passed by value.

struct_copy.c
#include <stdio.h>

typedef struct {
    int x;
    int y;
} Point;

int main(void) {
    Point original = {.x = 3, .y = 4};
    Point copy = original; // all members copied by value
    copy.x = 10;

    printf("original=(%d,%d) copy=(%d,%d)\n",
           original.x, original.y, copy.x, copy.y);
    return 0;
}

Note

Structure assignment is a memberwise copy. If a member is a pointer, the address is copied—not the pointed-to allocation. Ownership rules still need explicit design.

Pointers to structures

Use . with a structure value and -> with a pointer to a structure. The expression student->id is shorthand for(*student).id.

HaveMember accessExample
struct value.student.id
pointer to struct->student_ptr->id

Tip

Large structures are often passed by pointer to avoid copying. Add const when the function only reads their members.

Nested models

Members may be arrays, structures, pointers, enums, or other object types. Nesting small, meaningful types keeps complex models understandable.

nested.c
#include <stdio.h>

typedef struct { double latitude, longitude; } Coordinate;
typedef struct {
    char name[40];
    Coordinate location;
} City;

int main(void) {
    City beirut = {
        .name = "Beirut",
        .location = {.latitude = 33.8938, .longitude = 35.5018}
    };
    printf("%s: %.4f, %.4f\n",
           beirut.name, beirut.location.latitude, beirut.location.longitude);
    return 0;
}

Enumerations

An enum gives names to integral constants. It improves readability and works naturally withswitch, although C still permits values outside the listed enumerators.

status.c
#include <stdio.h>

typedef enum {
    STATUS_PENDING,
    STATUS_RUNNING,
    STATUS_DONE,
    STATUS_FAILED
} Status;

const char *status_name(Status status) {
    switch (status) {
        case STATUS_PENDING: return "pending";
        case STATUS_RUNNING: return "running";
        case STATUS_DONE: return "done";
        case STATUS_FAILED: return "failed";
    }
    return "invalid";
}

int main(void) {
    printf("%s\n", status_name(STATUS_DONE));
    return 0;
}

Watch out

Always handle invalid enum values at trust boundaries. A cast, corrupted file, or external input can produce a value that no enumerator names.

Unions & tagged unions

Union members overlap the same storage. Only the member representing the currently stored alternative should be read. Pairing a union with an enum tag makes that state explicit.

tagged_union.c
#include <stdbool.h>
#include <stdio.h>

typedef enum { VALUE_INT, VALUE_DOUBLE } ValueKind;

typedef struct {
    ValueKind kind;
    union {
        int as_int;
        double as_double;
    } data;
} Value;

void print_value(Value value) {
    if (value.kind == VALUE_INT) {
        printf("%d\n", value.data.as_int);
    } else {
        printf("%.2f\n", value.data.as_double);
    }
}

int main(void) {
    Value answer = {.kind = VALUE_INT, .data.as_int = 42};
    print_value(answer);
    return 0;
}

Key idea

The tag and union must change together. Hide updates behind constructor-style functions when a larger program needs to preserve that invariant.

typedef & API types

typedef creates an alias for an existing type. It does not create a runtime object or a fundamentally distinct type, but it can make public interfaces much clearer.

Good useExample
Name a domain structuretypedef struct Point Point;
Name a callback signaturetypedef void (*Handler)(int);
Hide an implementationtypedef struct Database Database;

Layout & padding

Implementations may insert padding so each member meets its alignment requirement and array elements remain correctly aligned.

A possible struct Record layout

tag : char

1 byte

padding

3 bytes

count : int

4 bytes

A common layout—not a portable byte-level promise.

layout.c
#include <stddef.h>
#include <stdio.h>

struct Record {
    char tag;
    int count;
};

int main(void) {
    printf("tag offset: %zu\n", offsetof(struct Record, tag));
    printf("count offset: %zu\n", offsetof(struct Record, count));
    printf("total size: %zu\n", sizeof(struct Record));
    return 0;
}
Typical output; the C standard does not require these exact offsets.

Watch out

Do not serialize a structure by blindly writing its raw bytes when portability or long-term compatibility matters. Padding, endianness, and type sizes can differ.

Recap & quick check

Key takeaways

  • Structures group simultaneous named members and support whole-object assignment.
  • Dot accesses a value's member; arrow accesses through a pointer.
  • Enums name integral states but external values still require validation.
  • A tagged union pairs a discriminator with one active representation.
  • Alignment may insert padding, so raw layouts are not portable serialization formats.

Quick check

1. Which operator accesses a member through a structure pointer?

2. What happens during structure assignment?

3. Why does a tagged union include an enum?

4. Why may sizeof(struct Record) exceed member-size totals?

Your data now has domain structure. Next up: Module 12 — Preprocessor, Headers & Modular Programs, where one source file becomes a maintainable project.