CS210 · Block 4 · Lesson 36

Function Pointers

Functions as data · Declaring function pointers · Calling through a pointer · Callbacks · Generic data with void *
At a glance: Until now a function pointer has been the one kind of pointer you had not met. Today a function gets an address like everything else, which means you can store it, pass it, and call through it. That single idea unlocks callbacks and the standard library qsort, and with void * it lets one routine work on data of any type. This lesson closes Block 4. You practice in the walkthrough in Section 5, then build a late night food truck finder in the lab.
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. Declare, assign, and invoke function pointers.
  2. Use function pointers to implement callback patterns.
  3. Apply the void * type to write functions that operate on generic data.
2. Assigned Readings

Complete before class. Beej Chapter 23 is the spine of today's lesson.

Source Sections Why
Beej-C Chapter 23 (Pointers III), the function pointer and void * sections Function pointer syntax, callbacks, and generic pointers; ends with qsort, which ties both ideas together
King, C Programming: A Modern Approach Chapter 17 (Pointers to Functions) Optional deeper dive with more worked examples
3. Pre-Class Work
Do this first
Plan on 30 to 40 minutes. The syntax looks strange the first time; reading it before class is what makes the walkthrough click.

Pre-Class Checklist

1
Read Beej Chapter 23
Focus on the function pointer and void * sections. Pay attention to:
  • The declaration form int (*p)(int, int) and why the parentheses around *p matter
  • That a function name on its own is already an address
  • What void * can and cannot do without a cast
  • The qsort example at the end of the chapter
2
Confirm your toolchain still builds clean
Open your terminal, recompile any recent lab with -Wall -Werror, and confirm it still runs. Today's lab adds qsort from <stdlib.h>, nothing new to install.
If anything broke since your last lab, schedule EI before class so it does not eat your lab time.
3
Skim the In-Class section below
You will see an interactive function pointer walkthrough. No need to use it yet; just know it is there.
4. Lesson Materials and Overview

You already know that a pointer holds an address. The new idea is that a function has an address too, so a pointer can hold it. Once a function fits in a variable, you can pass it around like any other value. The reference material below covers what you will see in lecture and use in the walkthrough.

Jump to
Slides Behavior as Data Declaring Calling Callbacks void * qsort

Slides

The Lesson 36 deck walks from passing data to passing behavior, decodes the declaration syntax, then builds up callbacks, void *, and qsort. The slides are the spine of lecture; the walkthrough in Section 5 is where you practice each step.

Open Lesson 36 slides →

Passing behavior, not just data

Every function you have written so far takes data and does something fixed with it. A sort function sorts; a counter counts. But what if the caller wants to decide the rule? Sort by rating tonight, by distance tomorrow. Count the trucks that are quick, or the ones that are highly rated. You could write a separate function for each rule, or you could write one function and let the caller hand in the rule. The rule is a function, and to hand it in you need its address. That address is what a function pointer holds.

This is also a decomposition idea, one level up from what you have done before. You are not just deciding what data flows through a function; you are deciding which function runs inside it. Naming and isolating that piece of behavior so it can be swapped is the same instinct as pulling a helper out of a long main, applied to control flow instead of data.

Declaring and decoding a function pointer

The declaration reads strangely until you take it apart from the inside out. Start at the name, go right when you can, left when you must, and let the parentheses group things:

int (*op)(int, int);
// op is a pointer (the * inside parentheses)
// to a function taking (int, int)
// that returns int

The parentheses around *op are not optional. Without them, int *op(int, int) declares a function that returns int *, which is a completely different thing. The parentheses are what bind the * to the name and make it a pointer.

Binding and calling through a pointer

A bare function name is already its address, so you assign it with no & needed. Calling through the pointer looks exactly like a normal call:

op = add;
// op now points to add

int r = op(5, 7);
// identical to add(5, 7); r is 12

Reassign the pointer and the same call site does something different. The line op(5, 7) never changes, but after op = mult; it computes 35. The assignment is checked against the full signature: the return type and every parameter type must match, or the compiler rejects it.

Callbacks: passing a function as an argument

Once a function fits in a pointer, it fits in a parameter. A function that takes another function and calls it back is using a callback. The classic shape:

void print_math(int (*op)(int, int), int x, int y)
{
  printf("%d", op(x, y));
}

print_math does not know or care what op does. The caller decides by passing add, sub, or anything with the right signature. That inversion, where the caller supplies the behavior, is the whole point.

Generic data with void *

A void * is a pointer with no element type attached. It can hold the address of anything, but you cannot dereference it directly, because the compiler does not know how many bytes to read or how to interpret them. To use it, you cast it back to the real type first. That sounds like a loophole, and it is a deliberate one: it lets a single function operate on data whose type it never has to know.

int compare(const void *a, const void *b)
{
  const struct food_truck *ta = a;  // cast back to the real type
  const struct food_truck *tb = b;
  return ta->rating - tb->rating;  // see the caution below
}

The const is a promise that the comparator will look but not change. One caution on the return value: a comparator must return any negative number, zero, or any positive number, but returning a raw subtraction like a - b can overflow for large values and give the wrong sign. The safe habit is to compare and return -1, 0, or 1 explicitly.

qsort: where it all comes together

The standard library sort is the payoff. It is generic over type through void * and configurable in behavior through a comparator callback:

qsort(trucks, n, sizeof trucks[0], by_rating_desc);
// base, count, size of one element, comparator

qsort never looks at your data directly. It walks the array using the element size you gave it and, every time it needs to order two elements, it calls your comparator through the function pointer. That one callback is the only code that knows the elements are trucks. Swap the comparator and the order changes without touching the sort. Your lab builds exactly this on a list of late night food trucks.

5. In-Class Work

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

  • Work through the function pointer walkthrough below. Twelve guided tasks: declaring and calling a function pointer, swapping behavior, callbacks, and a generic sort. About 25 minutes. Screenshot the completion code for the Lesson 36 reflection.
  • Complete Lab 36: Late Night Food Truck Finder. Comparators for qsort, a predicate callback, and a generic void * search. Auto-graded through GitHub Classroom; full instructions in the lab repo. Worth 2 points.

Function Pointer Walkthrough

Twelve tasks across four sections. Type each line at the prompt. The terminal shows what happened; the right pane shows which function each pointer currently names. Finish all twelve to unlock your completion code.

Before you begin

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

CS210 Lesson 36 · Function Pointer Walkthrough

A guided tour through declaring, assigning, and calling function pointers, then using them as callbacks and with generic void * data. Type each line at the prompt. The terminal shows what happened; the right pane shows which function each pointer currently names.
Task 0 of 12
Section 1: A function is an address
Current task
Loading...
Function pointers
No function pointers yet.
Declare one on the left.
funcptr>
How this works: This is a teaching tool, not a compiler. A small library of functions is already defined for you (see the right pane). The task card tells you what to type next; only the expected line advances the walkthrough so the pointer state stays predictable. Mistype twice and the hint button will pulse. After the walkthrough completes, free mode unlocks so you can experiment.
6. After Class
  • Finish Lab 36 if you did not complete it in class. Push to GitHub Classroom before Lesson 37.
  • Complete the Lesson 36 reflection in Blackboard using your walkthrough completion code.
  • Read ahead for Lesson 37 (defensive programming and reading existing C code). Real code leans on function pointers heavily, so today's lesson is what lets you read it.
  • Function pointers and bitwise operators are both fair game in the team PEX work period. If you are on a team, this is useful leverage.
Need help? Schedule EI with your instructor. Bring the specific declaration you are stuck reading or the exact compiler message, and a screenshot of the walkthrough if it is about the walkthrough. The course Resources section has the full reference material for self-service. Specific questions get unstuck quickly.