Functions in C Part 1: The Basics — Declaration, Definition, and Calling

A function is a self-contained block of code that performs a specific task. Instead of writing the same logic again and again, you write it once inside a function and call that function wherever you need that task done. This is the single most important idea for writing programs that are longer than a few dozen lines — it’s how real C programs (and every other language) stay readable and maintainable.

This is Part 1 of a 3-part series on functions in C:

  • Part 1 (this post): What functions are, why we use them, declaration vs. definition vs. calling, return types, and void functions.
  • Part 2: Parameter passing (call by value vs. call by reference), scope, and recursion.
  • Part 3: Storage classes, function pointers, and organizing large programs across multiple files.

Why Functions Matter

Consider a program that needs to find the square of a number in five different places. Without functions, you’d copy-paste result = num * num; five times. If you later needed to change the logic (say, to handle negative numbers differently), you’d have to find and fix all five copies. With a function, you write the logic once and call it five times — fix it once, and every call site is automatically correct.

Functions give you:

  • Reusability — write once, use many times.
  • Modularity — break a large problem into small, understandable pieces.
  • Abstraction — the caller doesn’t need to know how a function works internally, only what it does.
  • Easier debugging — you can test a function in isolation.

Anatomy of a Function

Every C function has four parts:

return_type function_name(parameter_list) {
    // function body
    return value; // only if return_type is not void
}
  • Return type — the data type of the value the function sends back to the caller (int, float, char, etc.), or void if it returns nothing.
  • Function name — an identifier, following the same naming rules as variables.
  • Parameter list — the inputs the function accepts, each with its own type. Can be empty (void or just ()).
  • Function body — the actual code, enclosed in { }.

Example 1: A Simple Function That Returns a Value

#include <stdio.h>

// Function definition
int square(int num) {
    int result = num * num;
    return result;
}

int main() {
    int x = 5;
    int y = square(x);   // function call

    printf("Square of %d is %d\n", x, y);
    printf("Square of 7 is %d\n", square(7));  // can also call directly inside printf

    return 0;
}

Sample Output

Square of 5 is 25
Square of 7 is 49

What’s happening here?

  • square is declared to return an int and accepts one int parameter named num.
  • When main() calls square(x), the value of x (5) is copied into num. Execution jumps into the function body.
  • return result; sends the computed value back to the exact place the function was called from, and execution resumes in main() right after the call.
  • A function can be called directly inside an expression (like printf("...", square(7))) — the call is evaluated first, and its return value is substituted in.

Example 2: A void Function (No Return Value)

Not every function needs to send a value back — sometimes a function’s job is just to do something, like printing output. For that, use void as the return type.

#include <stdio.h>

void printGreeting(char name[]) {
    printf("Hello, %s! Welcome to C programming.\n", name);
    // no return statement needed
}

int main() {
    printGreeting("Priya");
    printGreeting("Arjun");
    return 0;
}

Sample Output

Hello, Priya! Welcome to C programming.
Hello, Arjun! Welcome to C programming.

A void function can still use a bare return; (with no value) to exit early if needed, but it’s optional — the function returns automatically when it reaches the closing }.

Function Declaration (Prototype) vs. Definition

In the examples above, the function was defined before main(), so the compiler already knew about it by the time it was called. But in larger programs, you often want to define functions after main() for readability (keeping main() as the first thing a reader sees, acting like a table of contents for the program). For that, you need a function declaration (also called a prototype) before main(), so the compiler knows the function’s signature in advance.

#include <stdio.h>

// Function declarations (prototypes) — tell the compiler what's coming
int add(int a, int b);
int multiply(int a, int b);

int main() {
    int x = 4, y = 6;

    printf("Sum: %d\n", add(x, y));
    printf("Product: %d\n", multiply(x, y));

    return 0;
}

// Function definitions — the actual implementation
int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

Sample Output

Sum: 10
Product: 24

Without the prototypes, this code would fail to compile (or produce warnings/undefined behavior in older compilers) because main() calls add and multiply before the compiler has seen their definitions. The prototype is essentially a promise: “trust me, a function matching this signature exists somewhere in this file.”

Important detail: the parameter names in a prototype don’t have to match the definition (and can even be omitted entirely — int add(int, int); is a valid prototype) — only the types and their order matter to the compiler. Matching names is just good practice for readability.

Multiple Parameters and Mixed Types

#include <stdio.h>

float calculateSimpleInterest(float principal, float rate, float years) {
    return (principal * rate * years) / 100.0;
}

int main() {
    float p = 10000, r = 8.5, t = 3;
    float interest = calculateSimpleInterest(p, r, t);

    printf("Simple Interest: Rs. %.2f\n", interest);

    return 0;
}

Sample Output

Simple Interest: Rs. 2550.00

Functions can take any number of parameters, of any mix of types (as long as each one is declared with its own type — C doesn’t let you write int a, b as parameters to mean “two ints,” you must write int a, int b).

Common Beginner Mistakes

  • Forgetting the prototype when the function is defined after main() — leads to compiler errors or implicit-declaration warnings.
  • Mismatched return type — declaring a function void but trying to return someValue; inside it (or the reverse: declaring int but never returning anything).
  • Confusing the parameter with the argument — the parameter is the variable name used inside the function’s definition (num in square(int num)); the argument is the actual value passed at the call site (x or 7 in square(x) or square(7)). These terms are often used loosely, but knowing the distinction helps when reading compiler errors and textbooks.

Summary

  • A function bundles reusable logic under one name.
  • Structure: return type, name, parameter list, body.
  • Use void when a function doesn’t need to return a value.
  • A prototype (declaration) tells the compiler about a function’s signature before it’s actually defined — needed whenever a function is called before its definition appears in the file.
  • Functions can accept any number of parameters of any types.

Coming up in Part 2: how C actually passes values into functions (call by value vs. call by reference using pointers), what happens to variables declared inside a function (scope), and how a function can call itself (recursion).

Leave a Comment