Why this matters
So far every value has been a single number, character or array of one type. Real data comes in bundles: a point has an x and a y, a person has a name and a birth date, a network packet has a source, a destination, a length and a payload. A struct groups related values of different types into one new type you can declare, pass, return and copy as a unit. Structs are how C programs model everything: the operating system's struct stat describes a file, struct tm a calendar time, FILE (which you will meet in the files module) is a struct. Combined with pointers and the heap, structs are also how every data structure in the next modules is built.
Defining and using a struct
#include <stdio.h>
struct point {
int x;
int y;
};
int main(void)
{
struct point a;
a.x = 3;
a.y = 4;
struct point b = { 10, 20 };
struct point c = { .y = 7, .x = 1 };
struct point d = { 0 };
printf("a = (%d, %d)\n", a.x, a.y);
printf("b = (%d, %d)\n", b.x, b.y);
printf("c = (%d, %d)\n", c.x, c.y);
printf("d = (%d, %d)\n", d.x, d.y);
struct point e = b; /* copies every member */
e.x = 99;
printf("b.x = %d, e.x = %d\n", b.x, e.x);
printf("sizeof(struct point) = %zu\n", sizeof(struct point));
return 0;
}
a = (3, 4)
b = (10, 20)
c = (1, 7)
d = (0, 0)
b.x = 10, e.x = 99
sizeof(struct point) = 8
The pieces:
struct point { int x; int y; };defines a new type calledstruct point.pointis the tag;xandyare its members (also called fields). The definition creates no variable and uses no memory; it is a blueprint. Note the semicolon after the closing brace.- The type's full name is
struct point, both words. (The typedef lesson in this module shows how to give it a one-word name.) a.xis the member access operator, a dot: "thexinsidea".a.xis an ordinaryintvariable you can read, assign, increment or take the address of.{ 10, 20 }initializes members in order.{ .y = 7, .x = 1 }is a designated initializer (C99): members by name, in any order. This is clearer and survives reordering of the struct, so prefer it for structs with more than two members.{ 0 }sets the first member to 0, and any member not mentioned in an initializer is set to zero.{ 0 }is the idiomatic "zero everything" initializer. A struct declared without any initializer, likea, holds garbage until assigned (unless it is global or static).
What a struct is in memory
A struct variable is a block of memory with the members laid out in declaration order:
struct point b = { 10, 20 };
address: b+0 b+4
+------------+------------+
| 10 | 20 |
+------------+------------+
b.x (int) b.y (int)
<---- sizeof(struct point) = 8 ---->
The compiler knows each member's offset from the start. b.y compiles to "the 4 bytes at the address of b plus 4". No names exist at run time, only offsets, exactly like local variables in a stack frame. (The compiler may insert padding between members for alignment; the next lesson is about that.)
Structs are values: copying, passing, returning
Unlike arrays, structs behave like plain values:
- Assignment copies every byte.
e = bduplicates the struct;eandbare independent. - Passing a struct to a function copies it, just like passing an
int. The function works on its own copy. - A function can return a struct, which is copied back to the caller.
#include <stdio.h>
struct point {
int x;
int y;
};
struct point midpoint(struct point a, struct point b)
{
struct point m = { (a.x + b.x) / 2, (a.y + b.y) / 2 };
return m;
}
void try_to_move(struct point p)
{
p.x += 100; /* changes the copy only */
}
int main(void)
{
struct point a = { 0, 0 }, b = { 10, 4 };
struct point m = midpoint(a, b);
printf("midpoint: (%d, %d)\n", m.x, m.y);
try_to_move(a);
printf("a after try_to_move: (%d, %d)\n", a.x, a.y);
return 0;
}
midpoint: (5, 2)
a after try_to_move: (0, 0)
Returning m from midpoint is fine, even though m is a local: the value is copied out before the frame disappears. (Returning a pointer to m would be the dangling-pointer bug from the stack lesson.)
Copying is cheap for small structs like point. For a struct of several hundred bytes, every call copies all of them, so large structs are usually passed by pointer instead. That is the topic of the third lesson in this module.
Nested structs and arrays inside structs
Members can be any type, including arrays and other structs:
#include <stdio.h>
#include <string.h>
struct date {
int year, month, day;
};
struct person {
char name[16];
struct date born;
};
int main(void)
{
struct person ada = { "Ada", { 1815, 12, 10 } };
struct person copy = ada;
strcpy(copy.name, "Copy");
copy.born.year = 2000;
printf("%s born %d-%02d-%02d\n", ada.name, ada.born.year, ada.born.month, ada.born.day);
printf("%s born %d\n", copy.name, copy.born.year);
printf("sizeof(struct person) = %zu\n", sizeof(struct person));
return 0;
}
Ada born 1815-12-10
Copy born 2000
sizeof(struct person) = 28
int year, month, day; declares three members at once. ada.born.year chains member accesses left to right. The name array lives inside the struct (16 bytes of it), so copying the struct copies the characters too: 16 + 3 * 4 = 28 bytes.
Compare that with a member declared as char *name;. Then the struct holds only an 8-byte address, and copying the struct copies the address: both copies point at the same characters. This is called a shallow copy, and it matters a great deal once structs own heap memory.
char name[16] member: char *name member:
ada: [A d a \0 ...][born] ada: [ptr][born]--+
copy: [C o p y \0..][born] +--> "Ada" (shared!)
copy: [ptr][born]--+
Common mistakes
- Missing semicolon after the struct definition. The error appears on the next line:
main.c:3:1: error: expected ';', identifier or '(' before 'int' - Leaving out
structin the type name:main.c:3:17: error: unknown type name 'point'; use 'struct' keyword to refer to the type - Misspelled member:
error: 'struct point' has no member named 'z'. - Comparing structs with
==:error: invalid operands to binary == (have 'struct point' and 'struct point'). Compare members. - Expecting a function to modify a struct passed by value. It modifies its copy. Return the new struct, or pass a pointer (lesson 3).
- Uninitialized locals.
struct point p; printf("%d\n", p.x);prints garbage. Initialize with= { 0 }when in doubt.
Recap
struct tag { members };defines a type namedstruct tag; members are laid out in order, each at a fixed offset.- Access members with
.; initialize in order{ 1, 2 }, by name{ .y = 2 }, or all zero{ 0 }. Unmentioned members become zero. - Structs are values: assignment, passing and returning copy all the bytes, including arrays inside the struct. Pointer members are copied as addresses (a shallow copy).
- Structs cannot be compared with
==.
Going further: in the sandbox terminal, run grep -A12 'struct tm' /usr/include/x86_64-linux-gnu/bits/types/struct_tm.h to see struct tm, the standard library's calendar-time struct, with a comment on each member. Notice tm_year counts from 1900 and tm_mon from 0: documented quirks you can only learn by reading the definition.