Pointers to structs
In the last lesson, a struct passed to a function was a copy, so the function couldn't change the one outside. In lesson 16 we solved the same problem with pointers: give the function the address. Structs work the same way. First, a pointer to a struct:
1#include <stdio.h>2 3struct student {4 char name[20];5 int score;6};7 8int main(void) {9 struct student s = { "Ana", 80 };10 struct student *p = &s;11 (*p).score = 90;12 printf("%s: %d points\n", (*p).name, s.score);13 return 0;14}
Ana: 90 points
struct student *p is a pointer that points to a struct student. *p is s itself, so (*p).score = 90 changes s.score. Press Step: p is a small cell, with an arrow from it to the big box of s.
The arrow ->
The parentheses in (*p).score are required. The dot . goes before *, so without them, *p.score means "first take the member score of p", and p is a pointer, which has no members:
1#include <stdio.h>2 3struct student {4 char name[20];5 int score;6};7 8int main(void) {9 struct student s = { "Ana", 80 };10 struct student *p = &s;11 *p.score = 90;12 printf("%d\n", s.score);13 return 0;14}
main.c: In function 'main':
main.c:11:7: error: 'p' is a pointer; did you mean to use '->'?
11 | *p.score = 90;
| ^
| ->The compiler even guesses what you meant: did you mean to use '->'?. Writing (*p). every time is a chore, so C has a shorthand for it, the arrow ->: p->score means exactly the same as (*p).score.
1#include <stdio.h>2 3struct student {4 char name[20];5 int score;6};7 8int main(void) {9 struct student s = { "Ana", 80 };10 struct student *p = &s;11 p->score = 90;12 p->score += 5;13 printf("%s: %d points\n", p->name, p->score);14 return 0;15}
Ana: 95 points
The rule is simple: if you have a struct, use .; if you have a pointer to a struct, use ->.
Changing a struct through a pointer
The deposit from the last lesson couldn't save any money because it got a copy. Make the parameter a pointer, pass the address &acc when calling, and the function can change the one in main directly:
1#include <stdio.h>2 3struct account {4 char owner[20];5 int balance;6};7 8void deposit(struct account *a, int amount) {9 a->balance += amount;10}11 12int main(void) {13 struct account acc = { "Ana", 100 };14 deposit(&acc, 50);15 deposit(&acc, 30);16 printf("%s's balance: %d\n", acc.owner, acc.balance);17 return 0;18}
Ana's balance: 180
Press Step and go into deposit: its stack frame no longer holds a full copy, only a pointer a with an arrow back to acc in the frame of main. a->balance += amount changes the number over there. Passing a pointer has another upside: however big the struct is, only an address gets copied.
Tip: When a function only reads the struct, write the parameter as const struct account *a. Nothing is copied, and if the function changes it by mistake, the compiler gives an error.
Pointing into an array
A pointer can also point to one element of an array of structs. top below finds the student with the highest score and returns the address of that element:
What does this program print?
1#include <stdio.h>2 3struct student {4 char name[20];5 int score;6};7 8struct student *top(struct student list[], int n) {9 struct student *best = &list[0];10 for (int i = 1; i < n; i++) {11 if (list[i].score > best->score) {12 best = &list[i];13 }14 }15 return best;16}17 18int main(void) {19 struct student group[3] = { { "Ana", 82 }, { "Ben", 91 }, { "Chloe", 77 } };20 struct student *p = top(group, 3);21 p->score = 100;22 printf("%d\n", group[1].score);23 return 0;24}
top returns &list[1], the address of group[1], not a copy. p->score = 100 changes group[1] itself. Returning the address of an array element is fine here: the array lives in main and is still there after the function ends.
A struct on the heap
A struct can live on the heap too (lessons 19 and 20). malloc(sizeof(struct product)) asks for a block just big enough for one struct. What you get back is a pointer to it, so from then on you use ->:
1#include <stdio.h>2#include <stdlib.h>3#include <string.h>4 5struct product {6 char name[20];7 double price;8};9 10int main(void) {11 struct product *p = malloc(sizeof(struct product));12 if (p == NULL) {13 printf("Out of memory\n");14 return 1;15 }16 strcpy(p->name, "Water bottle");17 p->price = 19.0;18 printf("%s %.2f\n", p->name, p->price);19 free(p);20 return 0;21}
Water bottle 19.00
Press Step and watch a box appear on the heap with name and price inside, and an arrow from p in main pointing to it. After free(p), the box turns gray and dashed, marked "freed": that memory has been given back.
The function makes it, the caller frees it
Building a struct on the heap is often wrapped in a function: it gets the memory, fills in the members and hands back the pointer. Things on the heap outlive the function, so this is fine (returning the address of a local variable would not be, lesson 18). The price: the caller has to remember to free it. The parameter const char *name is the usual way to take a string the function only reads: it gets the address of the first character (lesson 17), and const (lesson 18) promises not to change it.
1#include <stdio.h>2#include <stdlib.h>3#include <string.h>4 5struct product {6 char name[20];7 double price;8};9 10struct product *new_product(const char *name, double price) {11 struct product *p = malloc(sizeof(struct product));12 if (p != NULL) {13 strcpy(p->name, name);14 p->price = price;15 }16 return p;17}18 19int main(void) {20 struct product *a = new_product("Water bottle", 19.0);21 struct product *b = new_product("Umbrella", 12.5);22 if (a == NULL || b == NULL) {23 return 1;24 }25 printf("%s %.2f\n", a->name, a->price);26 printf("%s %.2f\n", b->name, b->price);27 printf("Total %.2f\n", a->price + b->price);28 free(a);29 free(b);30 return 0;31}
Water bottle 19.00 Umbrella 12.50 Total 31.50
Two common mistakes
The first: using it after free. The box has been given back, but p still points there:
1#include <stdio.h>2#include <stdlib.h>3#include <string.h>4 5struct product {6 char name[20];7 double price;8};9 10int main(void) {11 struct product *p = malloc(sizeof(struct product));12 if (p == NULL) {13 return 1;14 }15 strcpy(p->name, "Umbrella");16 p->price = 12.5;17 free(p);18 printf("%s %.2f\n", p->name, p->price);19 return 0;20}
heap-use-after-freeWhat happened: Stopped at line 18. This memory is no longer yours: it was freed, or it belonged to a function that has returned.
Real C may not complain: it might still show the old values, or it might show garbage. Our engine stops on this line. Press "see how it went wrong" and see that after free, the box is gray and p's arrow has turned into a red dashed line.
The second: the pointer is NULL, and you use -> on it anyway. This happens when, say, a function returns NULL because it found nothing, and the caller forgets to check. Here p is simply set to NULL:
1#include <stdio.h>2#include <string.h>3 4struct student {5 char name[20];6 int score;7};8 9int main(void) {10 struct student *p = NULL;11 strcpy(p->name, "Ana");12 printf("End\n");13 return 0;14}
store to null pointerWhat happened: Stopped at line 11. This pointer is NULL: nothing can be read or written through it.
NULL points to nothing, so p->name has nowhere to go. On a real computer, a program like this usually just crashes. Whenever a pointer might be NULL, check if (p != NULL) before you use ->.
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it.
Use the arrow
✓This program doesn't compile. Change every dot after p into an arrow so that it prints Desk lamp 89.00.
Desk lamp 89.00
1#include <stdio.h>2 3struct product {4 char name[20];5 double price;6};7 8int main(void) {9 struct product item = { "Desk lamp", 99.0 };10 struct product *p = &item;11 p.price = p.price - 10;12 printf("%s %.2f\n", p.name, p.price);13 return 0;14}
Add points
✓Write a function add_points that takes a pointer to a struct player and a number of points, and adds the points to that player. The program should print Ben: 35 points.
Ben: 35 points
1#include <stdio.h>2 3struct player {4 char name[20];5 int points;6};7 8// write add_points here9 10int main(void) {11 struct player pl = { "Ben", 20 };12 add_points(&pl, 10);13 add_points(&pl, 5);14 printf("%s: %d points\n", pl.name, pl.points);15 return 0;16}
A book on the heap
✓Use malloc to make a struct book on the heap, set its title to "Emma" and its pages to 474, print Emma: 474 pages, and free it at the end.
Emma: 474 pages
1#include <stdio.h>2#include <stdlib.h>3#include <string.h>4 5struct book {6 char title[20];7 int pages;8};9 10int main(void) {11 12 return 0;13}
What you learned
struct student *p = &s;is a pointer to a struct, and*piss.p->scoreis the same as(*p).score: use.on a struct and->on a pointer to one.- Make the parameter a struct pointer and pass
&acc, and the function can change the struct outside, with no copy. - A function can return the address of one element of an array; changes through it change that element.
malloc(sizeof(struct product))makes a struct on the heap; a function can build one and return the pointer, and the caller mustfreeit.- Don't use it after
free; when a pointer might beNULL, check it before using->.