Help improve SovranCode?

Google Analytics can measure anonymous usage after you choose Allow. Essential account and progress features work either way.

SovranCode
C: Foundations for Systems Programming Functions, Headers, and Scope
This device
Course contentsFunctions, Headers, and Scope · 15 titles

C foundations

What C Is and How C Programs RunYour First C ProgramVariables, Types, and OperatorsInput and Output with printf and scanfConditions and Loops

Program structure and data

Functions, Headers, and ScopeArrays and StringsPointers and Memory AddressesStructures, Enumerations, and UnionsFiles and Command-Line Arguments

Memory, tooling, and practice

Dynamic Memory AllocationPreprocessor Directives and Separate CompilationErrors, Debugging, and Undefined BehaviorBuild Tools and MakefilesProject: Command-Line Inventory Manager
C: Foundations for Systems Programming15 complete lessons

C foundations

What C Is and How C Programs RunYour First C ProgramVariables, Types, and OperatorsInput and Output with printf and scanfConditions and Loops

Program structure and data

Functions, Headers, and ScopeArrays and StringsPointers and Memory AddressesStructures, Enumerations, and UnionsFiles and Command-Line Arguments

Memory, tooling, and practice

Dynamic Memory AllocationPreprocessor Directives and Separate CompilationErrors, Debugging, and Undefined BehaviorBuild Tools and MakefilesProject: Command-Line Inventory Manager
PREVIOUS LESSONConditions and Loops
NEXT LESSONArrays and Strings
Program structure and data 120 min

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.

What you will leave with

You will be able to design a focused function, distinguish a declaration from a definition and call, pass typed arguments by value, use void for an action with no result, return int and double values, place prototypes before use, explain what a header promises, and identify which names are visible at a given source line.

Build a contract, then hide the work behind it

Define one jobGive one transformation or action a clear name, typed inputs, and a visible result.
Call by contractUse a declaration to know how to call a function without how it performs the work.
Control visibilityKeep parameters and local variables inside the block where they are meant to serve.
Repair boundariesUse compiler and runner evidence to correct missing declarations, lost values, and incomplete returns.

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.

01Declare

Tell the compiler the function name, return type, and parameter types before a call needs them.

02Call

Evaluate the argument expressions and copy their values into the function's parameters.

03Execute

Run the function body using its parameters and local variables.

04Return

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.

int

Return type

The caller may use the resulting whole number in an assignment, calculation, condition, or output statement.

lessons_left

Function name

A verb or outcome-focused name tells the reader what the call means without exposing its arithmetic.

int completed, int total

Parameters

Each parameter has its own type and local name. Their order is part of the contract.

return total - completed;

Returned value

The expression is evaluated inside the function and one value travels back to the call site.

C FUNDAMENTALS RUNNER

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.

#include <stdio.h>

int lessons_left(int completed, int total) {
  return total - completed;
}

int main(void) {
  int remaining = lessons_left(3, 8);

  printf("%d lessons remain.\n", remaining);
  return 0;
}
TERMINAL OUTPUT
Edit the program, predict its output, then run it.

This safe browser runner supports the lesson subset: scalar and fixed-size int/double arrays, character strings, indexed reads and numeric writes, arithmetic, printf/puts, numeric scanf into scalar or bounded array-element addresses, comparisons, bounded if/else, for/while loops, and return.

Try this next: Change completed and total, predict the returned value, then rename the function only after updating both its definition and call.

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.

no inputs

Use void explicitly

int read_choice(void) states that the function accepts no arguments.

one input

One changing fact

int square(int value) needs one whole-number value and returns another.

several inputs

Keep order meaningful

Use a separate typed parameter for each independent fact and avoid long lists that hide the function's responsibility.

mixed types

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.

C FUNDAMENTALS RUNNER

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.

#include <stdio.h>

int double_value(int value);

int main(void) {
  int original = 7;
  int doubled = double_value(original);

  printf("Original: %d\n", original);
  printf("Doubled: %d\n", doubled);
  return 0;
}

int double_value(int value) {
  value = value * 2;
  return value;
}
TERMINAL OUTPUT
Edit the program, predict its output, then run it.

This safe browser runner supports the lesson subset: scalar and fixed-size int/double arrays, character strings, indexed reads and numeric writes, arithmetic, printf/puts, numeric scanf into scalar or bounded array-element addresses, comparisons, bounded if/else, for/while loops, and return.

Try this next: Change original to 11, predict both output lines, then rename the parameter so its local role is clearer.

  1. 01
    Evaluate the argument

    original currently holds 7, so the call supplies the value 7.

  2. 02
    Initialize the parameter

    The function's local parameter named value begins with its own copy of 7.

  3. 03
    Change only the local copy

    The assignment stores 14 in value. It does not assign to original.

  4. 04
    Return 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.

C FUNDAMENTALS RUNNER

Separate an output action from a calculation

This function receives two values and prints them, but it deliberately returns no value to main.

#include <stdio.h>

void print_progress(int completed, int total) {
  printf("Progress: %d of %d\n", completed, total);
}

int main(void) {
  print_progress(4, 6);
  return 0;
}
TERMINAL OUTPUT
Edit the program, predict its output, then run it.

This safe browser runner supports the lesson subset: scalar and fixed-size int/double arrays, character strings, indexed reads and numeric writes, arithmetic, printf/puts, numeric scanf into scalar or bounded array-element addresses, comparisons, bounded if/else, for/while loops, and return.

Try this next: Change both arguments, then add a second call. Confirm that each call performs an action and cannot be assigned to a variable.

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.

Printing is not returning

printf makes text visible to a person. return transfers a value to the caller. A reusable calculation usually returns its result and lets a separate part of the program decide how to display it.

C FUNDAMENTALS RUNNER

Return a double from mixed parameter types

weekly_hours multiplies a double duration by an int count and returns the reusable result.

#include <stdio.h>

double weekly_hours(double session_hours, int sessions) {
  return session_hours * sessions;
}

int main(void) {
  double total = weekly_hours(1.5, 4);

  printf("Weekly study: %f hours\n", total);
  return 0;
}
TERMINAL OUTPUT
Edit the program, predict its output, then run it.

This safe browser runner supports the lesson subset: scalar and fixed-size int/double arrays, character strings, indexed reads and numeric writes, arithmetic, printf/puts, numeric scanf into scalar or bounded array-element addresses, comparisons, bounded if/else, for/while loops, and return.

Try this next: Try 0.75 hours and 5 sessions. Predict the result before running, then store and print another call with different arguments.

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);.

int square(int value);

Declaration

Introduces the contract. It contains no body and ends with a semicolon.

square(4)

Call

Supplies an argument and produces the value returned by the function.

int square(int value) { ... }

Definition

Provides the function body. A definition is also a declaration.

one matching contract

Consistency

The declarations, definition, and calls must agree about the return and parameter types.

C FUNDAMENTALS RUNNER

Repair a call that appears before its definition

The definition is below main, so square is unknown at the point where main calls it.

#include <stdio.h>

int main(void) {
  int result = square(4);

  printf("Square: %d\n", result);
  return 0;
}

int square(int value) {
  return value * value;
}
TERMINAL OUTPUT
Edit the program, predict its output, then run it.

This safe browser runner supports the lesson subset: scalar and fixed-size int/double arrays, character strings, indexed reads and numeric writes, arithmetic, printf/puts, numeric scanf into scalar or bounded array-element addresses, comparisons, bounded if/else, for/while loops, and return.

Try this next: Add int square(int value); between the include and main, then run the repaired program.

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.

#ifndef PROGRESS_H

Test the guard

Continue only if this header's unique guard name has not already been defined.

#define PROGRESS_H

Mark it included

Define the guard before exposing the header's declarations.

function prototypes

Publish the interface

Consumers learn how to call the functions without seeing their implementation.

#endif

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.

Shadowing does not reuse the same variable

If an inner block declares a name that already exists outside it, the inner declaration temporarily hides the outer name. The two objects are distinct. Clear names are usually better than making readers track which declaration is active.

C FUNDAMENTALS RUNNER

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.

#include <stdio.h>

int make_total(int base) {
  int local_bonus = 2;
  return base + local_bonus;
}

int main(void) {
  int total = make_total(5);

  printf("Total: %d\n", total);
  printf("Bonus: %d\n", local_bonus);
  return 0;
}
TERMINAL OUTPUT
Edit the program, predict its output, then run it.

This safe browser runner supports the lesson subset: scalar and fixed-size int/double arrays, character strings, indexed reads and numeric writes, arithmetic, printf/puts, numeric scanf into scalar or bounded array-element addresses, comparisons, bounded if/else, for/while loops, and return.

Try this next: Remove the invalid printf from main, or return the information through an explicit function contract instead of reaching into another function's locals.

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.

C FUNDAMENTALS RUNNER

Repair an incomplete return path

The supplied values take the path that reaches the end of a non-void function without returning an int.

#include <stdio.h>

int lessons_left(int completed, int total) {
  if (completed <= total) {
    return total - completed;
  }
}

int main(void) {
  int remaining = lessons_left(7, 5);

  printf("Remaining: %d\n", remaining);
  return 0;
}
TERMINAL OUTPUT
Edit the program, predict its output, then run it.

This safe browser runner supports the lesson subset: scalar and fixed-size int/double arrays, character strings, indexed reads and numeric writes, arithmetic, printf/puts, numeric scanf into scalar or bounded array-element addresses, comparisons, bounded if/else, for/while loops, and return.

Try this next: Add an explicit return -1; after the if block, run the program, then make main explain that sentinel instead of presenting it as a valid count.

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 tracker

Run with ./tracker.

Linux with GCC

gcc -Wall -Wextra -Wpedantic -Wconversion main.c progress.c -o tracker

Run with ./tracker.

Windows developer prompt

cl /W4 main.c progress.c

Run the generated main.exe.

Compile every implementation file

A prototype can make a call compile, but the linker still needs the matching definition. If progress.c is omitted from the build command, expect an unresolved or undefined reference.

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.

C FUNDAMENTALS RUNNER

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.

#include <stdio.h>

int lessons_left(int completed, int total);
double weekly_hours(double session_hours, int sessions);
void print_summary(int remaining, double hours);

int main(void) {
  int completed = 3;
  int total = 8;
  int sessions = 4;
  double session_hours = 1.5;
  int remaining = lessons_left(completed, total);
  double hours = weekly_hours(session_hours, sessions);

  print_summary(remaining, hours);
  return 0;
}

int lessons_left(int completed, int total) {
  return total - completed;
}

double weekly_hours(double session_hours, int sessions) {
  return session_hours * sessions;
}

void print_summary(int remaining, double hours) {
  printf("Lessons remaining: %d\n", remaining);
  printf("Weekly study: %f hours\n", hours);
}
TERMINAL OUTPUT
Edit the program, predict its output, then run it.

This safe browser runner supports the lesson subset: scalar and fixed-size int/double arrays, character strings, indexed reads and numeric writes, arithmetic, printf/puts, numeric scanf into scalar or bounded array-element addresses, comparisons, bounded if/else, for/while loops, and return.

Try this next: Validate impossible progress, add a completion percentage function, and update print_summary to receive and display that returned percentage.

01Protect the contractReturn a documented sentinel when completed or total is invalid, and make the caller handle it.
02Add one calculationCreate a double completion_percent(int completed, int total) prototype, call, and definition.
03Keep output separatePass the calculated results into print_summary; do not recompute them inside the output function.
04Test boundariesTry zero total, completed equal to total, and completed greater than total. Record the policy for each case.

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.
  • void describes 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.
KNOWLEDGE CHECK

Explain the contract and its boundaries

For each question, identify what the caller knows, what the function owns, and where a declaration must be visible. Use the explanation to correct your mental model before trying again.

01In int lessons_left(int completed, int total), what does the first int describe?
02Which items are arguments in lessons_left(3, 8)?
03What happens to an int argument when it is passed to an int parameter in C?
04When is void an appropriate return type?
05Why add int square(int value); above main when square is defined below main?
06Which description correctly distinguishes a function declaration from a definition?
07What is the main purpose of a project header such as progress.h?
08Why can main not print a variable declared inside make_total?
09What is wrong with an int function whose if branch returns but whose other path reaches the closing brace?
10A prototype compiles, but the linker reports an undefined reference. What should you check first?
PREVIOUS LESSONConditions and Loops
NEXT LESSONArrays and Strings
ON THIS PAGEFunctions, Headers, and ScopeLesson mapWhy functionsFunction anatomyParameters and argumentsPass by valuevoid functionsReturn valuesPrototypesHeadersScopeReturn pathsCompile multiple filesIndependent labLesson reviewKnowledge check
Course contents