Skip to content

Handout: Lecture 8 In-Class Exercises

Part A is pen-and-paper: rewrite member access through pointers, and reason about the size of a union. Part B is at the keyboard: change a struct through a pointer, give a struct a heap-owned string, model a small set of states with a typedef'd enum, and build a tagged union. None of these repeats an earlier exercise; they drill today's moves - the -> operator, pointer parameters, heap-owned members, enum, and union.

Try each one yourself first; we will discuss in class, and the solutions are in a separate document afterward. Compile with warnings on:

clang -Wall -Wextra -std=c17 myprog.c -o myprog

Reminders that will keep you out of trouble today:

  • p->member is shorthand for (*p).member. Use -> when you hold a pointer to a struct, and . when you hold the struct itself.
  • To change a caller's struct, take a pointer parameter and pass &s. Add const to the pointer when the function only reads.
  • A struct that owns a heap string allocates strlen(s) + 1 bytes (room for the '\0') and needs a free_* function: one malloc, one free.
  • An enum is named int constants, numbered from 0 by default.
  • A union is sized to its largest member and holds one member at a time; pair it with an enum tag and check the tag before reading.

Set up

mkdir -p ~/cmsc14300/lec08
cd ~/cmsc14300/lec08

Part A - Pointers and unions (pen and paper)

Exercise A1 - Dots and arrows

Given these declarations:

typedef struct { int x; int y; } point_t;
point_t  pt = {1, 2};
point_t *p  = &pt;
  1. For each expression, say whether it is correct as written, and if not, fix it: pt.x, p.x, p->x, (*p).y, *p.y.
  2. Write two different expressions that both set pt's y to 9 through the pointer p (do not use pt directly).
  3. In one sentence: when do you use . and when do you use ->?

Exercise A2 - How big is the union?

Assume int is 4 bytes and double is 8 bytes. Consider:

struct s { int a; double b; char c; };
union  u { int a; double b; char c; };
  1. Roughly how many bytes does struct s need, and why? (Think: members side by side.)
  2. Roughly how many bytes does union u need, and why? (Think: members on top of one another.)
  3. After union u x; x.a = 5; x.b = 2.0;, is reading x.a meaningful? Explain in one sentence what a union does not track, and how a tagged union fixes it.

(Sizes may be rounded up by the compiler for alignment; give the reasoning, not an exact byte count.)


Part B - At the keyboard

Exercise B1 - Deposit and withdraw through a pointer

Model a bank account as a struct, and write two functions that change the caller's account in place through a pointer. This is the pointer-to-struct version of an out-parameter.

typedef struct {
    char   owner[32];
    double balance;
} account_t;

void deposit(account_t *a, double amount);
int  withdraw(account_t *a, double amount);   /* returns 1 on success, 0 if too little */
Ada: 100.00
Ada: 130.00
withdraw 200: denied
Ada: 130.00
  • In deposit: a->balance += amount;. In withdraw: only subtract if a->balance >= amount, returning 1; otherwise change nothing and return 0.
  • In main, build an account (e.g. {"Ada", 100.0}), call deposit(&acct, 30), try withdraw(&acct, 200), and print acct.balance after each. Pass the address; use -> inside.
  • Note the difference from passing by value: these functions see and modify the caller's real account, because they hold its address.

Exercise B2 - A struct that owns a heap string

Write a struct that holds a pointer to a heap-allocated label, plus a function to build one and a function to free it. This is the ownership pattern from the heap lecture, now living inside a struct and freed through a pointer.

typedef struct {
    char *label;     /* heap string this struct owns */
    int   priority;
} tag_t;

tag_t make_tag(const char *label, int priority);
void  free_tag(tag_t *t);
[urgent] priority 5
  • In make_tag: set priority, then malloc(strlen(label) + 1), check it against NULL, and strcpy the characters into your own block. Return the struct by value. (#include <string.h> for strlen/strcpy.)
  • In main: build a tag, print t.label and t.priority, then call free_tag(&t).
  • In free_tag: free(t->label) and set t->label = NULL, reaching the member with -> because t is a pointer. One malloc, one free. Run under valgrind to confirm no leak.

Exercise B3 - A traffic light state machine

Use a typedef'd enum to model the three states of a traffic light, and write a function that returns the next state. This drills enums and switch on an enum.

typedef enum { GREEN, YELLOW, RED } light_t;

light_t next_light(light_t current);
GREEN -> YELLOW -> RED -> GREEN
  • The cycle is GREEN -> YELLOW -> RED -> GREEN -> .... Implement next_light with a switch on current (one case per state), or with arithmetic if you prefer.
  • Add a helper const char *light_name(light_t l) that returns "GREEN", "YELLOW", or "RED" for printing (a switch returning a string literal).
  • In main, start at GREEN and print four states by repeatedly calling next_light. Notice the enum names make the code read like the intent.

Exercise B4 - A tagged union of measurements (overflow)

This exercise goes with the unions section, which is overflow material. Do it in class if we reach unions; otherwise treat it as extra practice and we will pick it up next lecture.

Build a value that is either a temperature in Celsius or a distance in meters, using an enum tag and a union. A function prints it correctly by checking the tag.

typedef enum { KIND_TEMP, KIND_DIST } kind_t;

typedef struct {
    kind_t kind;
    union {
        double celsius;    /* valid when kind == KIND_TEMP */
        double meters;     /* valid when kind == KIND_DIST */
    };
} measurement_t;

void print_measurement(const measurement_t *m);
23.5 C
100.0 m
  • print_measurement does a switch (m->kind): for KIND_TEMP print m->celsius with a C, for KIND_DIST print m->meters with an m. Read the union member only after checking the tag.
  • In main, build one of each: measurement_t t = {.kind = KIND_TEMP, .celsius = 23.5}; and a distance the same way. Pass &t to the printer.
  • The whole point: the union stores only one double worth of space, and the tag is what tells you which meaning those bytes currently have.

Stretch - take it further

An array of shapes with a total area

Combine everything: a typedef'd tagged union shape_t that is either a circle (with a radius) or a rectangle (with w and h), plus a function double total_area(const shape_t *shapes, int n) that sums the areas of an array of them.

typedef enum { CIRCLE, RECT } shape_kind_t;

typedef struct {
    shape_kind_t kind;
    union {
        double radius;              /* CIRCLE */
        struct { double w, h; };    /* RECT   */
    };
} shape_t;
  • Write double shape_area(const shape_t *s) that switches on s->kind (3.14159 * r * r for a circle, w * h for a rectangle), then have total_area loop over the array calling it.
  • Build a small array mixing circles and rectangles with designated initializers, and print the total. This tagged-union-in-an-array is the shape of a great deal of real C: a heterogeneous list whose elements each carry a tag saying what they are.