A structured lesson workspace with readable content, hands-on examples, and a clean path to completion.
C is strictly pass-by-value. Every argument is copied. But you can pass the address of a variable, and the function can dereference that address to modify the original. This is colloquially called pass-by-reference, though technically it is pass-by-value where the value happens to be a pointer. The effect is the same: the function reaches into the caller's stack frame and changes data. This is how functions return multiple results, how swap works, and how callbacks report state.
#include <stdio.h>void swap_by_value(int a, int b) {int temp = a;a = b;b = temp;}void swap_by_reference(int *a, int *b) {int temp = *a;*a = *b;*b = temp;}int main(void) {int x = 10, y = 20;printf("Before swap_by_value: x=%d, y=%d\n", x, y);swap_by_value(x, y);printf("After swap_by_value: x=%d, y=%d\n", x, y);printf("\nBefore swap_by_ref: x=%d, y=%d\n", x, y);swap_by_reference(&x, &y);printf("After swap_by_ref: x=%d, y=%d\n", x, y);return 0;}
swap_by_value modifies its own copies. The caller's x and y are untouched. swap_by_reference receives addresses, dereferences them, and modifies the actual variables. The & in the call passes the address. The * in the function body accesses the value at that address. Both symbols are essential. Omit either and the function does not work.
#include <stdio.h>#include <math.h>void polar_to_cartesian(double r, double theta, double *x, double *y) {*x = r * cos(theta);*y = r * sin(theta);}int main(void) {double x, y;polar_to_cartesian(5.0, M_PI / 4, &x, &y);printf("Cartesian: (%.3f, %.3f)\n", x, y);return 0;}
The function returns void. The results go into the caller's variables through the pointer parameters. This is the standard C pattern for functions that produce multiple outputs. The caller allocates the storage. The function fills it. Ownership is clear. No dynamic allocation. No struct wrapper needed for two values.
If a function takes a pointer but should not modify the target, declare the parameter as const. This communicates intent and lets the compiler catch accidental writes.
#include <stdio.h>void print_value(const int *p) {printf("Value: %d\n", *p);}int main(void) {int x = 42;print_value(&x);return 0;}
If you add *p = 99; inside print_value, the compiler rejects it: assignment of read-only location. The const qualifier turns a runtime corruption bug into a compile-time error. Use it on every pointer parameter that is input-only.
Pass-by-reference via pointers is the mechanism behind every C API that reports results through parameters. fopen writes to your FILE*. scanf writes to your int. pthread_create writes to your pthread_t. The pattern is universal. Next: passing arrays to functions, where pointer decay creates its own confusion.
Finish the lesson once you have worked through the material. This awards ★ 30 XP.