Declaring and defining functions
Learn how to declare and define functions in C — including the declaration vs definition distinction, function prototypes, and void functions.
- Write a function definition with a return type, name, and parameter list
- Explain the difference between a declaration (prototype) and a definition
- Call a function and use its return value
- Write a void function that produces output as a side effect
So far every program has been a single main function that does everything. That approach does not scale. Functions let you name a piece of logic, reuse it, and reason about it independently. They are the primary unit of abstraction in C.
A first function
#include <stdio.h>
int square(int x) {
return x * x;
}
int main(void) {
int result = square(7);
printf("7 squared is %d\n", result); /* 49 */
return 0;
}Breaking down the function definition:
int— the return type: the type of value this function produces.square— the name: how you call the function.(int x)— the parameter list: values the caller passes in.xis a local copy.{ return x * x; }— the body: the code that runs when the function is called.return x * x;— sends the result back to the caller.
Declaration versus definition
In C, you cannot call a function before the compiler has seen it — either a declaration (also called a prototype) or the full definition. The simplest approach is to define functions above main:
int add(int a, int b) { return a + b; }
int main(void) {
printf("%d\n", add(3, 4)); /* OK: add is above main */
}The alternative is to separate declaration from definition using a prototype:
/* Declaration: tells the compiler the function exists and its signature */
int add(int a, int b);
int main(void) {
printf("%d\n", add(3, 4)); /* OK: the prototype was above main */
}
/* Definition: the actual implementation, can come after main */
int add(int a, int b) {
return a + b;
}The prototype int add(int a, int b); is a promise to the compiler: "I will define this function, and it will have exactly this signature." In larger programs, prototypes live in header files.
Parameter names in prototypes are optional. int add(int, int); is a valid prototype — the parameter names are just for documentation. In practice, keep the names in prototypes for readability.
void functions
When a function does not return a value, use void as the return type:
#include <stdio.h>
void print_separator(void) {
printf("-------------------\n");
}
void print_header(const char *title) {
print_separator();
printf(" %s\n", title);
print_separator();
}
int main(void) {
print_header("Results");
printf("Score: 95\n");
return 0;
}void functions can use return; (with no value) to exit early, but they do not need to — control returns automatically at the end of the body.
Calling conventions
When you call square(7):
- The compiler evaluates the argument
7. - A stack frame is pushed — a block of memory for the function's local variables (including
x). - The value 7 is copied into
x. - The function body executes.
return x * xputs the result where the caller can find it.- The stack frame is popped.
- Execution returns to the line after the call.
The key word is copied. In C, arguments are passed by value. Modifying x inside square does not change the original variable in main. This is different from references in languages like Python (where mutable objects can be modified through a function call).
Multiple parameters
#include <stdio.h>
double hypotenuse(double a, double b) {
return a * a + b * b; /* returns a^2 + b^2; caller takes the sqrt */
}
int max_of_three(int a, int b, int c) {
int max = a;
if (b > max) { max = b; }
if (c > max) { max = c; }
return max;
}
int main(void) {
printf("sum of squares: %.1f\n", hypotenuse(3.0, 4.0)); /* 25.0 */
printf("max: %d\n", max_of_three(7, 2, 9)); /* 9 */
return 0;
}Where to go next
Next: parameters and return values in depth — how values are passed into functions, how to return multiple results using pointers, and how to write functions that modify their arguments.