A structured lesson workspace with readable content, hands-on examples, and a clean path to completion.
A function in C is a named block of code that takes zero or more inputs, does work, and optionally returns one value. One value. Not two. Not a tuple. Not an object. If you need to return multiple things, you return a struct, or you pass pointers for the function to write into. This constraint shapes how C programs are architected at every level.
C compiles top-to-bottom in a single pass. If you call a function before its definition appears in the file, the compiler has not seen it yet. You get an implicit declaration warning (or error in C99+). The fix is a prototype: a declaration that tells the compiler the function exists, what it takes, and what it returns, without providing the body.
#include <stdio.h>int add(int a, int b);void greet(const char *name);int main(void) {int result = add(3, 7);printf("3 + 7 = %d\n", result);greet("Alex");return 0;}int add(int a, int b) {return a + b;}void greet(const char *name) {printf("Hello, %s!\n", name);}
When main calls add(3, 7), the CPU pushes a stack frame. The arguments go in (either on the stack or in registers, depending on the calling convention). The return address is saved. Local variables in add get their own space. When add hits return, the frame is popped, the return value lands in a register (eax/rax on x86), and execution resumes in main. Every function call does this. Recursion does this repeatedly until you run out of stack. We cover that disaster in the recursion module.
A function declared void returns nothing. You can still use return; inside it to exit early. This is the guard-clause pattern: validate inputs at the top, return immediately if they are bad, and let the main logic sit at the base indentation level without nested ifs.
#include <stdio.h>void print_array(const int *arr, int len) {if (arr == NULL) {printf("Error: NULL array\n");return;}if (len <= 0) {printf("Error: invalid length\n");return;}printf("[");for (int i = 0; i < len; i++) {printf("%d", arr[i]);if (i < len - 1) {printf(", ");}}printf("]\n");}int main(void) {int data[] = {10, 20, 30, 40};print_array(data, 4);print_array(NULL, 4);print_array(data, -1);return 0;}
C passes all arguments by value. The function receives a copy. Modifying a parameter inside the function does not affect the caller's variable. If you need the function to modify the original, you pass a pointer. This is not a limitation. It is a clarity feature. When you see a function signature, you know immediately which arguments might be modified (the pointer ones) and which are read-only inputs (the value ones).
#include <stdio.h>void try_to_modify(int x) {x = 999;printf("Inside function: x = %d\n", x);}int main(void) {int x = 42;try_to_modify(x);printf("After call: x = %d\n", x);return 0;}
x is still 42 in main. The function modified its own copy. The caller is unaffected. We cover pass-by-reference (via pointers) in the pointers module, where this becomes the mechanism for functions that need to report multiple results.
Functions are the unit of abstraction in C. Everything else in this course, from pointers to threads to file I/O, is expressed through functions. Next: arrays, which are contiguous memory blocks with a deeply confusing relationship to pointers that we will untangle over several lessons.
Finish the lesson once you have worked through the material. This awards ★ 20 XP.