CLA (C language) study materials
CLA (C language) study resources

CLA (C language): Structures, Unions and Enums: Layout, Access and Self-Reference

CLA (C language) · Work through your exam, one topic at a time.

All guide sections

Structures, Unions and Enums: Layout, Access and Self-Reference

Domain 4 - Structures covers user-defined aggregate types: how members are laid out in memory, how structs are copied and passed, and how unions, enums and bit-fields extend them. Items test sizeof, padding, the -> operator and self-referential nodes.

Declaration and use

struct Point { int x, y; }; defines a type; members are accessed with .. Common forms are struct Point p; and typedef struct { int x, y; } Point;, after which Point p; works without the keyword. C99 designated initializers (Point p = {.y = 2};) zero the remaining members. Assignment copies the struct, but == does not compile, so compare fields individually.

Layout and padding

Members keep declaration order, with padding inserted so each meets its alignment, so sizeof(struct) is at least the sum of the members and often more. struct { char c; int i; char d; } is typically 12 bytes on a 4-byte-aligned platform, not 6. offsetof from stddef.h gives member offsets; reordering largest-first usually shrinks the struct.

Pointers, arrays and self-reference

struct Point *q = &p; uses q->x, which means (*q).x. Passing a struct by value copies every byte; passing a pointer avoids the copy and allows modification. Arrays of structs are contiguous and arr[i].field means (*(arr+i)).field. A struct may hold a pointer to its own type - struct node { int v; struct node *next; }; - which builds linked lists and trees; an incomplete type is fine at the pointer.

Unions, enums and bit-fields

Union members share storage, so the size is that of the largest member and writing one member overwrites the others. Enums give named integer constants. Bit-fields pack small integers into individual bits, but their layout is implementation-defined.

Worked example

For struct S { char c; int i; char d; }; a compiler using 4-byte alignment places c at offset 0, pads bytes 1–3, puts i at 4–7 and d at 8, then pads to 12. Checking sizeof(struct S) and offsetof(struct S, i) explains why writing a raw struct to a file is not portable across compilers.

Traps

Forgetting struct when no typedef exists; comparing structs with ==; assuming sizeof equals the sum of members; dereferencing an uninitialized struct pointer; copying structs that hold pointers and then freeing twice.

File I/O Streams and the C Preprocessor

Domain 5 - File I/O and preprocessor covers the two boundaries of a translation unit: talking to files through FILE * streams, and transforming source text before compilation. Items test open modes, return-value checking, stream positioning and macro pitfalls.

Streams and modes

FILE *f = fopen("data.txt", "r"); returns NULL on failure, which you must test. Modes: "r" read, "w" write (truncates or creates), "a" append, with + for update and b for binary. fclose flushes buffered output. stdin, stdout and stderr are already open. fseek/ftell position the stream using SEEK_SET, SEEK_CUR and SEEK_END.

Reading and writing

fgetc/fputc return int so EOF is distinguishable from a valid 0xFF byte. fgets(buf, size, f) keeps at most size-1 characters and stops after a newline; never use gets. fprintf/fscanf mirror the console versions. fread(ptr, size, count, f) and fwrite return the number of complete items transferred, so compare the result with count.

End of file and errors

feof(f) becomes true only after a read has already failed, so while (!feof(f)) processes the final record twice. Loop on the read function's return value instead: while ((c = fgetc(f)) != EOF). ferror(f) separates a genuine I/O error from a normal end of file.

Preprocessor

#include <header> searches system directories; #include "header" looks beside the source first. #define PI 3.14159 is object-like, #define SQUARE(x) ((x)*(x)) is function-like: pure textual substitution with no type checking. Parenthesize every parameter and the whole body, or SQUARE(1+2) expands to 1+2*1+2. # stringizes an argument and ## pastes tokens. #if, #ifdef, #ifndef, #elif, #else, #endif and defined drive conditional compilation; include guards prevent double inclusion. __FILE__, __LINE__ and __DATE__ are predefined macros.

Worked example

#define MAX(a,b) ((a) > (b) ? (a) : (b)) is safe for values, but MAX(i++, j) increments the larger operand twice because the argument text appears twice in the expansion. An inline function or a temporary variable avoids the double evaluation.

Traps

while (!feof(f)); ignoring a NULL return from fopen; missing parentheses in a macro; a stray semicolon after #define; %s in fscanf without a width limit; and never calling fclose, which loses buffered data.