Skip to content

Chapter 19 of 21

Advanced Topics

Apply typedef, callbacks, bit-fields, qualifiers, and flexible array members.

38 minutes 10 quick checksBy Subha Prasad
Lesson 19 of 21Course navigation

Lesson content

Read, practise, then check your understanding

Advanced C features help express low-level interfaces and reusable libraries, but each has portability and readability trade-offs.

typedef and opaque types

typedef introduces an alias, not a distinct runtime type.

typedef unsigned long UserId;
typedef int (*Comparator)(const void *, const void *);

Function-pointer aliases make callback APIs readable. Libraries can hide representation with an incomplete type:

// session.h
typedef struct Session Session;
Session *session_create(void);
void session_destroy(Session *session);

Only the implementation defines struct Session, preventing callers from depending on private fields.

Callbacks and dispatch tables

struct Handler {
    const char *name;
    int (*run)(const char *input, void *context);
};

void *context carries caller state, avoiding globals. Document callback lifetime, reentrancy, thread behavior, and whether the callback may retain pointers.

Bit-fields

struct Flags {
    unsigned ready : 1;
    unsigned mode : 3;
};

Bit-fields can compact in-memory flags, but allocation order, alignment, and interaction with hardware formats are implementation-defined. Do not assume a bit-field structure matches a network packet or device register. Explicit masks over fixed-width unsigned integers are usually more portable.

Qualifiers

const restricts modification through an access path. volatile tells the implementation accesses are observable and must not be optimized away in ordinary ways; it does not provide atomicity or thread synchronization. _Atomic and <stdatomic.h> provide language-level atomic operations when supported.

restrict is a promise that, for the relevant execution, an object accessed through one restricted pointer is not also accessed through an incompatible alias. It can enable optimization, but violating the promise is undefined behavior.

Flexible array members

struct Packet {
    size_t length;
    unsigned char data[];
};

struct Packet *packet = malloc(sizeof *packet + payload_length);

The flexible array must be the final member of a structure with another named member. Check addition overflow, store the allocated length, and free the complete object once.

Generic and compile-time facilities

_Generic can select an expression by type and _Static_assert verifies compile-time assumptions. Use them to strengthen small abstractions, not to recreate an unreadable alternate language in macros.

Knowledge check

Answer every question correctly to complete this chapter.

What does typedef create?
Where must a flexible array member appear?
What is an opaque structure API used for?
What does a callback API commonly receive?
Why is void *context commonly paired with callbacks?
Why should bit-fields not define portable network packet layout?
Does volatile provide thread synchronization?
What promise does restrict make?
What must be checked before allocating sizeof header + payload length?
What does _Static_assert verify?

0 of 10 checks passed

Your progress is saved on this device.