CS210 · Block 3 · Lesson 19

Pointers to Structs and Dynamic Arrays

Arrow operator · malloc of struct arrays · Pass-by-value vs pass-by-pointer · Ownership of struct members
At a glance: Lesson 18 introduced structs as values. Today we put a pointer in front of them. The arrow operator gives us a clean way to reach a struct’s fields through a pointer, and that unlocks the real workhorse pattern: dynamic arrays of structs allocated on the heap with malloc. The trap we close on is ownership, when a struct’s field is itself a pointer.
Contents
  1. Lesson Objectives
  2. Assigned Readings
  3. Pre-Class Work
  4. Lesson Materials and Overview
  5. In-Class Work
  6. After Class
1. Lesson Objectives

By the end of this lesson, cadets will be able to:

  1. Access struct members through a pointer using the arrow operator. (Outcome 2)
  2. Allocate and use dynamically sized arrays of structs. (Outcome 2)
  3. Manage memory correctly for arrays of structs containing pointer members. (Outcomes 2, 3)
2. Assigned Readings

Complete before class. These build the vocabulary we will use.

Source Sections Why
Beej-C Chapter 8 cont. (Structs and pointers) The arrow operator and how structs interact with pointers
Beej-C Chapter 20 (Structs II) Structs as full citizens: passing them to functions, returning them, and using them with pointers
3. Pre-Class Work
Do this first
Plan on 30 to 40 minutes for the reading and the pre-class skim. Most of today’s learning happens in the in-class walkthrough and the lab; come ready to type.

Pre-Class Checklist

1
Read the assigned chapters
Beej Chapter 8 cont. (arrow operator and structs with pointers) and Chapter 20 (Structs II, which covers passing structs to functions and returning them). Skim if confident, read closely if any of these feel unfamiliar:
  • The arrow operator (p->field) and what it expands to
  • Passing a struct by value vs by pointer
  • malloc(N * sizeof(T)) idiom
  • Pairing every malloc with a free
2
Recall structs from Lesson 18
Pull up your Lesson 18 lab. Re-read your struct definitions and how you wrote to their fields with the dot operator. Today we use the same struct shapes but reach the fields through pointers instead.
3
Skim the In-Class section below
You will see a planets walkthrough widget and a self-check quiz. No need to use them yet; just know they are there.
4. Lesson Materials and Overview

Today connects three threads you already know. Structs (Lesson 18) gave us a way to bundle related fields. Pointers (Lessons 12 to 16) gave us a way to refer to memory without copying it. malloc and free (Lesson 17) gave us heap allocation. Putting them together yields the workhorse pattern for the rest of CS210 and most of professional C: a dynamically sized array of structs on the heap, with the arrow operator as the everyday notation.

Jump to
Slides The Arrow Operator Dynamic Arrays of Structs Pass by Value vs Pass by Pointer Ownership and Pointer Members Designing Structs and Their Operations Together

Slides

The Lesson 19 deck covers the arrow operator, dynamic arrays of structs, pass-by-pointer mechanics, and the ownership rules when struct members are themselves pointers. The slides are the spine of the in-class lecture; the planets walkthrough is where you practice the mechanics hands on.

Open Lesson 19 slides →

The arrow operator

When you have a pointer to a struct and you want one of its fields, you have two ways to write it. The long way dereferences the pointer first, then takes the field:

Planet *p = &roster[1];
(*p).id = 5;  // dereference, then dot

The short way uses the arrow operator. It does exactly the same thing:

p->id = 5;  // equivalent to (*p).id = 5;

In real code you will almost always see the arrow. The parenthesized-deref form is just useful for understanding what the arrow expands to.

Common mistake: writing p.id when p is a pointer. The compiler will reject it with a "member reference base type is not a structure" error. The fix is the arrow.

Dynamic arrays of structs

An array of structs on the heap is the most common dynamic data pattern in C. The shape is always the same:

Planet *roster = malloc(n * sizeof(Planet));
if (roster == NULL) { /* allocation failed; handle and return */ }
// ...use roster[0] through roster[n-1]...
free(roster);
roster = NULL;

A few rules of thumb that you will follow in every CS210 lab from here on:

  • Always write the size as n * sizeof(T), not a hard-coded byte count. If the struct changes size later, your allocation still works.
  • Always check the return value of malloc. On modern systems it almost never returns NULL, but the autograder and the code-review culture in CS210 both expect you to check.
  • Pair every malloc with exactly one free. Allocate in one place, free in one place. Document who owns the memory.
  • After free, set the pointer to NULL. The memory is no longer yours; the pointer should no longer claim to point at anything.

Pass by value vs pass by pointer

When you pass a struct to a function by value, the function receives a copy. Mutations to its parameter do not affect the caller. This is identical to passing an int by value, except the bytes copied are larger.

void label_as_earth(Planet pl) {
  pl.id = 3;  // only mutates the local copy
}

int main(void) {
  Planet pl = {0, 0.0, 0};
  label_as_earth(pl);
  // pl.id is still 0; the copy is gone
}

To mutate the caller’s struct, take a pointer to it:

void label_as_earth(Planet *pl) {
  pl->id = 3;  // writes through the pointer to the caller
}

int main(void) {
  Planet pl = {0, 0.0, 0};
  label_as_earth(&pl);
  // pl.id is now 3
}

Even when the function does not need to mutate, passing by pointer is often the better choice for large structs because it copies only the pointer (8 bytes on a 64-bit system) instead of the whole struct. The convention to signal "I am not going to modify this" is to mark the parameter const:

void describe(const Planet *pl) {
  printf("id=%d, moons=%d\n", pl->id, pl->moons);
}

Ownership and pointer members

Suppose a struct has a field that is itself a pointer to heap memory:

typedef struct {
  char *name; // heap-allocated string
  int id;
  double gpa;
} Student;

Now you have two levels of allocation. The student record itself is one allocation; the name string is another. When you free the student, you have to free both, in the right order:

free(s->name);  // free what s owns first
free(s);       // then free s itself

Order matters. If you free s first, then try to read s->name to free it, you are reading freed memory. The C standard says that is undefined behavior; in practice it sometimes works, sometimes crashes, sometimes leaks. None of those outcomes is acceptable. Free what the struct owns, then free the struct.

The general rule: when you write a struct that owns heap memory through a pointer member, write a paired "create" and "free" function for it. Document who owns what in a header comment. The lab today is built on exactly that pattern.

Designing structs and their operations together

There is a design idea hiding in everything above. When you wrote the Student struct, you decided which fields belong together. That is decomposition of data: id, gpa, and name are one thing, not three loose variables. When you wrote the roster functions, you decided which operations belong together. That is decomposition of behavior: init, add, remove, print, and free are the things a roster supports.

The skill is doing both at once. When you reach for a struct from now on, design the fields and the functions that operate on them in a single pass. The struct should carry the data the functions need; the functions should know what the struct guarantees. If you find yourself adding a field that no function reads, or writing a function that does not match any struct's responsibilities, the design is drifting. Lesson 6 anchored decomposition for functions; Lesson 19 extends it to data.

5. In-Class Work

Three things to do in class. Use the checklist to track your progress.

  • Work through the planets walkthrough below. Nine guided tasks across four sections: a pointer to a struct, malloc of a dynamic array, the dot and arrow operators, and free with dangling-pointer awareness. About 20 minutes.
  • Take the pass-by-value vs pass-by-pointer self-check. Ten questions on how structs travel into and out of functions, plus dynamic arrays and ownership. About 12 minutes.
  • Complete Lab 19: Swapping Structs and a Dynamic Roster. Two parts. Part 1: implement two swap functions to see pass-by-value vs pass-by-pointer with structs in action. Part 2: build a small roster manager that creates, prints, and frees Student records with heap-allocated name fields. Auto-graded through GitHub Classroom.

Planets Walkthrough

Nine tasks across four sections. Type each C statement at the prompt. The terminal shows what the simulator did; the right pane shows the heap and any pointers aimed at it.

Before you begin

Enter your name. It will appear on the completion card so you can include it in your screenshot for the Blackboard reflection.

CS210 Lesson 19 · Planets Walkthrough

Nine guided tasks across four sections. Build a dynamic array of Planet structs on the heap, fill it with the dot and arrow operators, then free it. The right pane shows the heap as you go.
Task 1 of 9
Section 1: A pointer to a struct
Task 1 of 9
Loading...
Terminal
Heap and pointers
planets>
Mode:
How this works: Read each task, type the C statement at the prompt, press Enter. The terminal shows what the simulator did; the heap pane shows your data. Walkthrough mode advances you task by task; switch to Free play to type any supported statement at any time.

Pass-by-Value vs Pass-by-Pointer Self-Check

Ten questions. Read each code snippet carefully and trace what main sees. The final score card gives you the completion code to submit on Blackboard.

Before you begin

Enter your name. It will appear on the score card so you can include it in your screenshot for the Blackboard reflection.

CS210 Lesson 19 · Pass-by-Value vs Pass-by-Pointer Self-Check

Ten questions on how structs travel into and out of functions, plus a few on dynamic arrays and ownership. Pass at 70% or above; below that, take it again after reviewing.
How this works: One question per card. Pick or type your answer, click Submit, then click Next. Answers lock once submitted, so trace the code carefully before you commit. After all ten you get a score, a completion code, and a per-question recap.
6. After Class
  • Finish Lab 19 if you did not complete it in class. Submit through GitHub Classroom before Lesson 20.
  • Submit your Lesson 19 reflection on Blackboard (the completion code from the walkthrough plus a short-answer prompt drawn from the lesson question bank).
  • Read the Lesson 20 assigned reading. Lesson 20 builds on today by introducing functions that produce and consume structs through pointers, organized across multiple source files.
  • Replay the planets walkthrough in free play mode if you want extra practice. Try different malloc sizes; try assigning a slot pointer mid-way through and using it to fill a slot.
Need help? Schedule EI with your instructor. Bring specific code, the exact error message, or a screenshot of the walkthrough memory pane and a clear statement of what you expected versus what happened. Specific questions get unstuck quickly. For general reference material (the C Programming Standard, Git troubleshooting, debugging tips), see the Resources section of the course.