Defining and using structs
Group related data into custom types with C structs — declaration, member access, initialization, and passing structs to functions.
- Declare a struct type and create variables of that type
- Access struct members with the dot operator
- Initialise a struct using designated initialisers
- Pass a struct to a function by value and by pointer
A struct groups related variables of potentially different types into a single compound type. Structs are the primary way to model real-world entities in C — a point has x and y coordinates, a person has a name and age, a network packet has a header and a payload. Understanding structs is the gateway to every data structure you will build.
Declaring a struct
struct Point {
double x;
double y;
};This declares a type called struct Point. The variables inside are called members (or fields). The declaration does not create any variable or allocate any memory — it only defines the layout of the type.
Creating a variable:
struct Point origin; /* a variable of type struct Point */
origin.x = 0.0;
origin.y = 0.0;Member access with the dot operator
Use . to access struct members:
#include <stdio.h>
struct Rectangle {
double width;
double height;
};
double area(struct Rectangle r) {
return r.width * r.height;
}
int main(void) {
struct Rectangle rect;
rect.width = 5.0;
rect.height = 3.0;
printf("Area: %.1f\n", area(rect)); /* 15.0 */
return 0;
}Initialisation
Aggregate initialisation (C89 compatible):
struct Point p = {3.0, 4.0}; /* x=3.0, y=4.0 in order */Designated initialisers (C99, preferred):
struct Point p = { .x = 3.0, .y = 4.0 }; /* order-independent, clear */Designated initialisers make the code self-documenting. Unmentioned members are zero-initialised.
Compound literal (C99):
struct Point midpoint(struct Point a, struct Point b) {
return (struct Point){ .x = (a.x + b.x) / 2.0,
.y = (a.y + b.y) / 2.0 };
}A compound literal creates a temporary struct value inline. Useful for returning structs from functions.
Passing structs to functions
By value: the function receives a complete copy. Modifying it does not affect the original:
void scale(struct Rectangle r, double factor) {
r.width *= factor; /* modifies the copy */
r.height *= factor; /* original rect unchanged */
}By pointer: the function receives an address and can modify the original:
void scale_inplace(struct Rectangle *r, double factor) {
r->width *= factor; /* -> is . combined with * */
r->height *= factor;
}
int main(void) {
struct Rectangle rect = { .width = 5.0, .height = 3.0 };
scale_inplace(&rect, 2.0);
printf("%.1f x %.1f\n", rect.width, rect.height); /* 10.0 x 6.0 */
return 0;
}r->width is shorthand for (*r).width. Use -> when accessing members through a pointer.
Struct size and padding
The compiler may insert padding bytes between struct members to satisfy alignment requirements:
struct Padded {
char c; /* 1 byte */
/* 3 bytes padding to align next int */
int i; /* 4 bytes */
char d; /* 1 byte */
/* 3 bytes padding to keep struct size a multiple of 4 */
};
/* sizeof(struct Padded) is likely 12, not 6 */Use sizeof(struct Padded) to check the actual size. Never assume structs are packed tightly. The Advanced tier covers alignment and packing in detail.
Copying structs
Assignment copies the entire struct:
struct Point a = {1.0, 2.0};
struct Point b = a; /* b is a complete copy of a */
b.x = 99.0; /* does not affect a */This is a shallow copy — if a struct contains a pointer, only the pointer is copied, not the pointed-to data. Both copies then point to the same underlying data.
A complete example: 2D distance
#include <stdio.h>
#include <math.h>
struct Point { double x; double y; };
double distance(struct Point a, struct Point b) {
double dx = b.x - a.x;
double dy = b.y - a.y;
return sqrt(dx * dx + dy * dy);
}
int main(void) {
struct Point p1 = {0.0, 0.0};
struct Point p2 = {3.0, 4.0};
printf("Distance: %.1f\n", distance(p1, p2)); /* 5.0 */
return 0;
}Compile with -lm to link the math library: gcc -Wall point.c -o point -lm.
Where to go next
Next: nested structs and arrays of structs — composing more complex types from simpler ones.
Lab: Dynamic array (growable buffer)
Implement a growable dynamic array in C using malloc and realloc — the foundation of every dynamic collection in systems programming.
Nested structs and arrays of structs
Compose complex types from simpler structs — nested struct members, arrays of structs, and how to work with them efficiently.