Functions: Declaration, Definition, Parameters, Returns
This chapter covers how functions are declared and defined, how parameters and arguments interact, and how return values work. Functions are the primary unit of code organization in C.
Why This Matters
C has no methods, closures, or modules. Functions — and their declarations — are how you build abstractions and organize programs across files. The parameter/argument model (pass-by-value) is fundamental to every other concept.
Prerequisites
c.core.6— declarations and definitions.c.core.7— scope and lifetime.
Core Concept
Declaration (prototype) vs. definition
int add(int a, int b); /* declaration (prototype) */
int add(int a, int b) { /* definition */
return a + b;
}
A prototype declares the return type and parameter types. The definition provides the body.
Pass-by-value
C passes all arguments by value: the parameter is a copy of the argument. Modifying a parameter inside the function does not affect the caller's variable. To modify a caller's variable, pass its address (a pointer).
Return
return expr; returns a value (converted to the function's return type). return; (no value) is used in void functions. Reaching the end of a non-void function without returning is undefined behavior (except main, as noted in c.core.1).
Syntax
return_type function_name(parameter_type name, parameter_type name, ...)
{
/* body */
return value;
}
Parameter names are optional in prototypes but required in definitions (unless unused, in which case you can omit the name in the definition too, though most code keeps it and marks it unused).
Examples
Basic function
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main(void)
{
printf("%d\n", add(2, 3));
return 0;
}
Expected output: 5.
Pass-by-value demonstration
#include <stdio.h>
void set_to_five(int x)
{
x = 5; /* only the local copy changes */
}
void really_set(int *p)
{
*p = 5; /* writes through the pointer */
}
int main(void)
{
int a = 0;
set_to_five(a);
printf("%d\n", a); /* 0 */
really_set(&a);
printf("%d\n", a); /* 5 */
return 0;
}
Expected output: 0 then 5.
Function returning a struct (by value)
struct Point { int x; int y; };
struct Point make_point(int x, int y)
{
struct Point p = { x, y };
return p; /* returns a copy */
}
Returning a struct by value is legal and copies the whole struct.
How It Works
At the call site, arguments are evaluated (in unspecified order) and their values are copied into the parameters. The function executes, and on return the return value is copied back (for non-void functions). The details of where parameters and return values live (registers vs. stack) are the ABI's concern (c.build.calling-convention).
Variations
Old-style (K&R) function definitions
int add(a, b)
int a;
int b;
{
return a + b;
}
This is the pre-ANSI style. It is obsolescent and should not be used in new code. Prototypes (ANSI style) are preferred.
Function with no parameters
int f(void); /* takes no arguments */
int g(); /* unspecified arguments (avoid) */
Always use (void) for "no parameters."
Functions with static
static functions have internal linkage and are visible only within their translation unit (c.lang.linkage).
Common Mistakes
- Forgetting to declare a function before use (in C99 and later, calling an
undeclared function is a constraint violation; in C89 it was allowed with implicit int).
- Using
f()instead off(void). - Forgetting that parameters are copies.
- Returning a pointer to a local variable (
c.lang.lifetime). - Not returning a value from a non-
voidfunction.
Undefined Behavior
- Reaching the closing brace of a non-
voidfunction without areturnis
undefined behavior (except main in C99+). VERIFIED
- Calling a function through a pointer of incompatible type is UB.
- Using a return value from a function that did not return one is UB.
Portability
- The pass-by-value model is standard and portable.
- Old-style definitions are obsolescent but still accepted by many compilers.
(void)vs()is a real, portable difference.
Under the Hood
The ABI defines how arguments are passed (in registers for the first few arguments on many platforms, then on the stack) and how return values are returned (in a register or on the stack for large structs). This is covered in c.build.4.
Practical Usage
- Declare functions in headers, define them in
.cfiles. - Use output parameters (pointers) when a function must produce multiple
results or modify a caller's object.
- Prefer returning by value for small structs; use pointers for large or
opaque data.
Exercises
1. Write a function swap that exchanges two ints using pointers, and verify it works. 2. Demonstrate pass-by-value by showing that a function modifying its parameter does not change the caller's variable. 3. Write a function that returns a struct and inspect what happens to a large struct (use sizeof and observe the copy). 4. Deliberately omit a return from a non-void function and run it with warnings and UBSan.
Deep Challenge
Explain, using the pass-by-value model and pointer semantics, how to write a function that allocates a buffer and returns it to the caller, along with an error code — without using global state. Compare the "return pointer + status" and "output parameter" approaches and discuss ownership.
Related Concepts
c.ptr.basics— pointers for output parameters.c.lang.linkage— static and extern functions.c.func.recursion— recursion.c.build.calling-convention— ABI.
References
- ISO/IEC 9899:2018 §6.9.1 (function definitions), §6.7.6.3 (function
declarators), §6.5.2.2 (function calls).
Verification
- Pass-by-value is standard.
VERIFIED - Old-style definitions are obsolescent.
VERIFIED - Missing return in non-void function is UB (except main).
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Function declaration vs. definition
- [ ] Pass-by-value
- [ ] Return values
- [ ] void functions
- [ ] Output parameters via pointers
- [ ] Returning structs by value
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.func.decl | 0 | 5 |
| c.func.param | 0 | 5 |
| c.func.return | 0 | 5 |