Functions, Headers, and Scope
Functions let a C program name one responsibility, receive the information it needs, and return a result without exposing every implementation detail. Learn how calls move through declarations, definitions, parameters, local scope, headers, and return paths.
Build a contract, then hide the work behind it
A function gives one responsibility a stable name
Without functions, main eventually becomes a long sequence of unrelated details. A function creates a boundary around one job: calculate a value, validate a rule, print a report, or coordinate another small action. The caller supplies only the required arguments and relies on the function's contract.
A useful function is smaller than the feature around it. Its name describes an outcome, its parameters represent the information that can vary, and its return type says what comes back. If you cannot describe the job in one sentence without using “and then,” the function may be carrying more than one responsibility.
Tell the compiler the function name, return type, and parameter types before a call needs them.
Evaluate the argument expressions and copy their values into the function's parameters.
Run the function body using its parameters and local variables.
Send one result to the caller, or finish a void action without a result value.
Read a function definition as a typed contract plus an implementation
In int lessons_left(int completed, int total), the first int is the return type. lessons_left is the function name. The two declarations inside parentheses are parameters. The braces contain the implementation, and return total - completed; supplies the promised result.
Return type
The caller may use the resulting whole number in an assignment, calculation, condition, or output statement.
Function name
A verb or outcome-focused name tells the reader what the call means without exposing its arithmetic.
Parameters
Each parameter has its own type and local name. Their order is part of the contract.
Returned value
The expression is evaluated inside the function and one value travels back to the call site.
Call a function that returns an int
Trace the two arguments into completed and total, evaluate the return expression, then store the returned value in remaining.
Edit the program, predict its output, then run it.
Parameters describe inputs; arguments supply values
A parameter belongs to a function declaration or definition, such as int completed. An argument is the expression used at a call site, such as lessons_left(3, 8). C evaluates each argument and initializes the corresponding parameter by position.
Parameter order should communicate the rule. For lessons_left(completed, total), a call reads naturally and the subtraction direction is predictable. Reversing the arguments still satisfies the types because both are int, but it changes the meaning.
Use void explicitly
int read_choice(void) states that the function accepts no arguments.
One changing fact
int square(int value) needs one whole-number value and returns another.
Keep order meaningful
Use a separate typed parameter for each independent fact and avoid long lists that hide the function's responsibility.
Match each position
weekly_hours(double hours, int sessions) expects a fractional duration first and a whole-number count second.
C passes these scalar arguments by value
For the int and double parameters in this lesson, a function receives its own parameter value. Assigning a new value to that parameter changes the function's local copy, not the caller's variable. The function below doubles value, yet original in main remains 7.
Change a parameter without changing the caller
Trace the caller's original variable, the argument value, the parameter copy, and the returned result as four separate ideas.
Edit the program, predict its output, then run it.
- 01Evaluate the argument
originalcurrently holds 7, so the call supplies the value 7. - 02Initialize the parameter
The function's local parameter named
valuebegins with its own copy of 7. - 03Change only the local copy
The assignment stores 14 in
value. It does not assign tooriginal. - 04Return a separate result
The function returns 14, and the caller stores that value in
doubled.
Use void when the function performs an action without producing a value
A void return type means that the call does not produce a value for an expression. The function can still perform useful work: it can print output, update data through an explicit shared boundary, or coordinate other functions. In this lesson, print_progress owns formatting and output while the caller owns the values.
Call a void function as a statement: print_progress(4, 6);. Do not assign its result, because there is no result value. A bare return; may end a void function early, but reaching the closing brace also completes it.
Separate an output action from a calculation
This function receives two values and prints them, but it deliberately returns no value to main.
Edit the program, predict its output, then run it.
A non-void function sends one typed value back to its caller
The return type is a promise. An int function returns a whole-number value; a double function returns a floating-point value. The caller decides what to do with that result—store it, compare it, pass it to another function, or ignore it. Returning a value is different from printing it: a returned value remains available to the program.
Return a double from mixed parameter types
weekly_hours multiplies a double duration by an int count and returns the reusable result.
Edit the program, predict its output, then run it.
A prototype makes a function contract visible before the call
C processes declarations in source order. When a call appears before the function definition, the compiler needs an earlier declaration. A function prototype supplies the return type, function name, and parameter types, followed by a semicolon: int square(int value);.
Declaration
Introduces the contract. It contains no body and ends with a semicolon.
Call
Supplies an argument and produces the value returned by the function.
Definition
Provides the function body. A definition is also a declaration.
Consistency
The declarations, definition, and calls must agree about the return and parameter types.
Repair a call that appears before its definition
The definition is below main, so square is unknown at the point where main calls it.
Edit the program, predict its output, then run it.
A header shares declarations across source files
A header file is a public contract for code that other source files may use. Standard header <stdio.h> declares facilities such as printf. Your own header might declare int lessons_left(int completed, int total);, while a matching .c file contains the definition.
Include guards prevent the same header contents from being processed more than once in one translation unit. A beginner header normally contains declarations, named constants, and shared type definitions—not ordinary function bodies or unrelated private variables.
Test the guard
Continue only if this header's unique guard name has not already been defined.
Mark it included
Define the guard before exposing the header's declarations.
Publish the interface
Consumers learn how to call the functions without seeing their implementation.
Close the guard
End the conditional region at the bottom of the header.
Scope answers: where can this name be used?
A parameter or variable declared inside a function body has block scope. It can be named from its declaration to the end of the enclosing block, including appropriate nested blocks. It does not become visible inside other functions. This boundary prevents implementation details from leaking into callers.
A declaration outside every function has file scope. Such names can be useful for shared constants and carefully designed module state, but broad mutable state creates hidden dependencies. Prefer parameters and returned values until the program truly needs longer-lived shared state.
Find the name that escaped its scope
local_bonus belongs to make_total. main receives only the returned total and cannot name the function's local variable.
Edit the program, predict its output, then run it.
Every reachable path in a non-void function must return a value
A conditional return does not complete the contract unless every possible execution path returns. In the example below, valid progress returns a count, but completed > total reaches the closing brace without an int result. That is a defect, not an automatic zero.
Choose an explicit policy for invalid input: return a documented sentinel such as -1, validate before the call, or redesign the interface to report success separately. The important part is that the caller can distinguish a valid result from an invalid case.
Repair an incomplete return path
The supplied values take the path that reaches the end of a non-void function without returning an int.
Edit the program, predict its output, then run it.
Split the contract, implementation, and caller into separate files
A multi-file program compiles each .c source file as a translation unit and links the resulting object code into one executable. Put shared declarations in progress.h, definitions in progress.c, and feature coordination in main.c. Both source files include the header so the compiler checks them against the same contract.
macOS with Clang
clang -Wall -Wextra -Wpedantic -Wconversion main.c progress.c -o trackerRun with ./tracker.
Linux with GCC
gcc -Wall -Wextra -Wpedantic -Wconversion main.c progress.c -o trackerRun with ./tracker.
Windows developer prompt
cl /W4 main.c progress.cRun the generated main.exe.
Independent lab: build a small progress-reporting module
Use the starter to practice three contracts: one integer calculation, one floating-point calculation, and one output action. First run it unchanged. Then complete the milestones without merging all work back into main.
Build with focused function boundaries
The starter uses prototypes before main and definitions afterward, so the call sites read like a concise description of the program.
Edit the program, predict its output, then run it.
double completion_percent(int completed, int total) prototype, call, and definition.print_summary; do not recompute them inside the output function.Lesson review
- A function should express one focused responsibility through a clear name.
- Parameters declare local inputs; arguments are the expressions supplied by the caller.
- Scalar arguments in these examples are copied into parameters, so parameter assignment does not modify the caller's variable.
voiddescribes a function that returns no result value.- A prototype declares a function contract before a call that cannot yet see the definition.
- A header publishes shared declarations; a source file normally owns their definitions.
- Parameters and local variables stay inside their blocks; file-scope names have broader visibility.
- Every reachable path through a non-void function must return a value compatible with its return type.