Structs
A struct groups several objects under one name and one type. The members keep their own types and are laid out in memory in declaration order, so a struct is a value that can be copied, passed and returned as a unit. The basics are collected on Structures in C; this lesson builds the working knowledge the rest of the course needs.
Defining a struct
The definition names the members and ends with a semicolon. It creates a type, not an object: the variable comes with the declaration that follows. The dot reaches a member, and assignment to a member works like assignment to any variable of that type.
#include <stdio.h>
struct Point {
int x;
int y;
};
int main(void)
{
struct Point start = {3, 4};
printf("start: %d,%d\n", start.x, start.y);
start.x = 10;
printf("moved: %d,%d\n", start.x, start.y);
return 0;
}
gcc -Wall -Wextra define.c -o define
./define
start: 3,4
moved: 10,4
The initialiser fills the members in declaration order. The whole struct can be copied with struct Point other = start;, which copies every member, including any array inside the struct. That is different from an array, which cannot be assigned as a whole.
Designated initialisers
A designated initialiser names the member it fills, so the order in the definition stops mattering and a reader sees which value belongs to which field. Unnamed members are zero, which is the same rule as an array initialiser from lesson 22.
#include <stdio.h>
struct Point {
int x;
int y;
};
int main(void)
{
struct Point corner = {.y = 2, .x = 1};
printf("%d,%d\n", corner.x, corner.y);
return 0;
}
gcc -Wall -Wextra named.c -o named
./named
1,2
The values are printed in member order, not in the order they were written. The form pays off in a struct with ten fields, where a positional initialiser is a puzzle and a designated one is a list of labels.
Arrays of structs
A struct is a complete type, so an array of structs is an ordinary array with a larger element. The inner braces follow the elements, and the loop reaches a member through the element and a dot. This is how a table of records is stored without a database.
#include <stdio.h>
struct Point {
int x;
int y;
};
int main(void)
{
struct Point path[3] = {
{0, 0},
{2, 3},
{5, 7},
};
for (int i = 0; i < 3; i++) {
printf("point %d: %d,%d\n", i, path[i].x, path[i].y);
}
return 0;
}
gcc -Wall -Wextra array.c -o array
./array
point 0: 0,0
point 1: 2,3
point 2: 5,7
path[i] is a struct value, and .x selects its member. The trailing comma after the last element is allowed and keeps the diff small when an element is added, exactly as with a two-dimensional array.
Padding and offsets
The compiler inserts unused bytes so that each member starts at an address its type can use. A char then an int leaves three bytes of padding, and the total size is rounded up to the strictest alignment. The offsets come from offsetof, which is safer than counting bytes by hand.
#include <stddef.h>
#include <stdio.h>
struct Sample {
char tag;
int value;
char flag;
};
int main(void)
{
printf("size: %zu\n", sizeof(struct Sample));
printf("tag at %zu\n", offsetof(struct Sample, tag));
printf("value at %zu\n", offsetof(struct Sample, value));
printf("flag at %zu\n", offsetof(struct Sample, flag));
return 0;
}
gcc -Wall -Wextra padding.c -o padding
./padding
size: 12
tag at 0
value at 4
flag at 8
The members take six bytes together, but the struct takes twelve: three bytes before value, three after flag, and the rounding to four. Reordering the members as int, int, char would still be twelve here, while int, char, char would be eight. Padding is why a struct should not be written to a file field by field.
No comparison, missing semicolon
C compares structs member by member, not with ==. The operator does not exist for two struct values, and the compiler says so with both operand types:
#include <stdio.h>
struct Point {
int x;
int y;
};
int main(void)
{
struct Point a = {1, 2};
struct Point b = {1, 2};
if (a == b) {
printf("equal\n");
}
return 0;
}
gcc -Wall -Wextra compare.c -o compare
compare.c: In function 'main':
compare.c:13:11: error: invalid operands to binary == (have 'struct Point' and 'struct Point')
13 | if (a == b) {
| ^~
The fix is a function that compares the members and returns 0 when they all match, or memcmp when the struct has no padding and the bytes are comparable. The second mistake is a missing semicolon after the definition, which turns the next declaration into a syntax error at a confusing line:
#include <stdio.h>
struct Point {
int x;
int y;
}
int main(void)
{
struct Point p = {1, 2};
printf("%d\n", p.x);
return 0;
}
gcc -Wall -Wextra nosemi.c -o nosemi
nosemi.c:8:1: error: expected ';', identifier or '(' before 'int'
8 | int main(void)
| ^~~
The error points at int main because the compiler is still waiting for the semicolon that ends the struct definition. When a declaration fails right after a struct, look at the closing brace first.
Exercises
- Define a struct for a book with a title, an author and a year, and print three books from an array.
- Write a function that takes two points and returns the one with the larger distance from the origin, squared.
- Measure the size of a struct with a char, a double and a short, then reorder the members to make it smaller.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What does a struct definition create?
Answer
A type that can be used in declarations
The definition names a type; an object appears in a later declaration such as
struct Point start = {3, 4};. -
What does
struct Point other = start;copy?Answer
Every member, including arrays inside the struct
A struct is a value, so assignment copies all its members; arrays inside it come along, unlike a bare array.
-
What is the advantage of a designated initialiser?
Answer
It names each member and does not depend on declaration order
The names make the initialiser readable and keep it correct when the definition changes order.
-
Why can a struct be larger than the sum of its members?
Answer
Padding aligns each member and rounds the total size
Members start at aligned addresses and the total is rounded to the strictest alignment, so unused bytes appear between and after them.
-
What does this program print?
#include <stdio.h> struct Point { int x; int y; }; int main(void) { struct Point start = {3, 4}; printf("start: %d,%d\n", start.x, start.y); start.x = 10; printf("moved: %d,%d\n", start.x, start.y); return 0; }Answer
start: 3,4 / moved: 10,4
Only
xis changed, so the point moves to 10,4 whileykeeps its value. -
What does this program print?
#include <stdio.h> struct Point { int x; int y; }; int main(void) { struct Point corner = {.y = 2, .x = 1}; printf("%d,%d\n", corner.x, corner.y); return 0; }Answer
1,2
The values are printed in member order, so
xprints first as 1 andysecond as 2. -
What does this program print?
#include <stdio.h> struct Sample { char tag; int value; char flag; }; int main(void) { printf("%zu\n", sizeof(struct Sample)); return 0; }Answer
12
Three bytes of padding before
valueand three afterflagmake the total twelve. -
What does this program print?
#include <stddef.h> #include <stdio.h> struct Sample { char tag; int value; char flag; }; int main(void) { printf("%zu\n", offsetof(struct Sample, value)); return 0; }Answer
4
The
intmust be aligned to four bytes, so three padding bytes follow thecharat offset zero. -
This program is built with
gcc -std=c17 -Wall -Wextra compare.c -o compare. Which error does GCC print?#include <stdio.h> struct Point { int x; int y; }; int main(void) { struct Point a = {1, 2}; struct Point b = {1, 2}; if (a == b) printf("equal\n"); return 0; }Answer
invalid operands to binary ==
C has no
==for two struct values, so the operands are invalid for the operator. -
This program is built with
gcc -std=c17 -Wall -Wextra nosemi.c -o nosemi. What does GCC report?#include <stdio.h> struct Point { int x; int y; } int main(void) { struct Point p = {1, 2}; printf("%d\n", p.x); return 0; }Answer
error: expected ';', identifier or '(' before 'int'
The compiler is still waiting for the semicolon, so the next declaration fails with a message that points at it.
The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev