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.
By the end of this lesson, cadets will be able to:
void * type to write functions that operate on generic data.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 |
void * sections. Pay attention to:int (*p)(int, int) and why the parentheses around *p mattervoid * can and cannot do without a castqsort example at the end of the chapter-Wall -Werror, and confirm it still runs. Today's lab adds qsort from <stdlib.h>, nothing new to install.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.
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.
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.
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:
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.
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:
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.
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:
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.
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.
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.
The standard library sort is the payoff. It is generic over type through void * and configurable in behavior through a comparator callback:
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.
Two things to do in class. Use the checklist to track your progress.
qsort, a predicate callback, and a generic void * search. Auto-graded through GitHub Classroom; full instructions in the lab repo. Worth 2 points.
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.
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.
void * data. Type each line at the prompt. The terminal shows what happened; the right pane shows which function each pointer currently names.