Skip to content
Circuit

Functions in C

Functions divide a program into operations with named interfaces. A useful function does one understandable job and tells its caller what information it needs and what it returns. You can then reason about a calculation without repeatedly mixing it with prompts, input parsing, and output formatting.

Start with a small calculation

int square(int value) declares a function named square that accepts one int and returns an int. Inside its body, return value * value computes the result and ends that call. main can print the result, compare it, or pass it to another function.

Keeping calculation separate from printing makes the function reusable. A function that always prints an answer is harder to use as part of another expression or to test without inspecting terminal text. For a first example, use small input values whose squares fit in int; the type does not promise unlimited arithmetic.

#include <stdio.h>
int square(int value) {
    return value * value;
}
int main(void) {
    int result = square(7);
    printf("%d\n", result);
    return 0;
}

Declarations describe the interface

The compiler must know a function’s declaration before checking a call. Placing the complete definition before main is one approach. Alternatively, write a prototype such as int square(int value); before main and put the definition later. The prototype ends with a semicolon; the definition supplies a body.

Return types and parameter types must agree between declarations and definitions. A header commonly shares these declarations across source files in a desktop project. Circuit’s single-source workspace can keep the prototypes and definitions together. Do not confuse declaring a function with actually implementing it: an absent definition can fail at link time.

Arguments are passed by value

When you call square(number), the parameter receives a copy of number’s value. Assigning a new value to that parameter does not change number in the caller. Local variables also belong to a particular call and are not directly visible to other functions.

This rule still applies when the argument is a pointer: the address value is copied. Dereferencing that copied address can access the caller’s object, which is how functions such as scanf write results. C does not secretly switch to a separate pass-by-reference mechanism for pointer parameters.

Use return values and status deliberately

A function returning void does not provide a value to use in an expression. A non-void function should return a suitable result on every relevant path. Falling through accidentally can produce undefined behavior when the caller uses the missing result. main has special rules, but copying that exception into ordinary functions is a mistake.

When an operation can fail, design a way to report failure without confusing it with a valid answer. One option is a status return with a pointer output parameter. Another is a result structure. For a beginner input helper, a simple success/failure return is often clearer than a global error variable.

Write contracts for pointer and array parameters

A function receiving an array also needs a count unless its interface uses another explicit boundary, such as a null-terminated string. A pointer alone does not reveal the number of valid elements. State whether the function reads or writes the data and whether a null pointer is permitted.

Never return the address of an ordinary automatic local variable. Its lifetime ends when the function returns, leaving the caller with an invalid address. Returning an int value is safe because the value is copied. Returning a pointer requires storage with a lifetime that continues long enough for the caller’s intended use.

Recursion needs more than a base case

A recursive function calls itself on a smaller or otherwise simpler problem. It needs a stopping case and progress toward that case. A factorial function that always calls itself with the same n has a base case in its source but never reaches it for other inputs.

Each active call needs execution state, commonly stored on a stack. Deep recursion can exceed the available stack before finishing, even when the algorithm is mathematically correct. Circuit sets a one-megabyte program stack. Prefer a loop for simple counting exercises and reserve recursion for problems where its structure genuinely helps.

Test the interface independently

Call the function with several known inputs before adding interactive input. For square, test zero, one, a negative small number, and a positive small number. Then integrate scanf while preserving the same function. This separates input parsing bugs from calculation bugs.

Choose names that describe the result or action, avoid hidden global state when a parameter will do, and keep assumptions visible. A short function with an undocumented range can be more dangerous than a longer one with checked inputs. A good interface makes misuse easier to notice at the call site.