C Programming Functions Reference
Core Programming Concept

C Functions - Complete Guide

Master C functions including types, user-defined functions, parameter passing techniques, arrays as arguments, and modular programming best practices.

Function Types

Built-in & User-defined

Parameter Passing

Value & Reference

Array Parameters

1D & 2D Arrays

1. Introduction to Functions

1.1 What is a Function?

A function is a self-contained block of code that performs a specific task. Think of it as a small machine: you give it inputs, it processes them, and optionally returns an output.

Real-world analogy:

  • Coffee machine: Input (water, beans) → Process (grind, brew) → Output (coffee)
  • Calculator: Input (numbers, operation) → Process (calculate) → Output (result)
  • Vending machine: Input (money, selection) → Process (check, dispense) → Output (product)

1.2 Why Use Functions?

BenefitDescriptionExample
Code ReusabilityWrite once, use many timescalculateArea() called from anywhere
ModularityBreak complex problems into smaller pieces10 functions of 50 lines vs one 500-line block
ReadabilitySelf-documenting codecalculateTotal() vs inline math
MaintainabilityFix bugs in one placeUpdate function once; all calls benefit
TestingTest each piece independentlyTest validateInput() alone
AbstractionHide complex detailsCall sortArray() without knowing the algorithm

1.3 Function Structure

Return Type Function Name Parameters (Input) ↓ ↓ ↓ int calculateSum(int a, int b) { ↑ ↑ Function Body Opening Brace int result = a + b; ← Statement return result; ← Return statement } ← Closing Brace

Components: return type, name, parameters, body, and optional return (not used for void).

2. Function Basics

2.1 Function Prototype (Declaration)

A prototype tells the compiler the function name, return type, and parameter types before the definition or call.

return_type function_name(type1 param1, type2 param2, ...);
Prototype example
#include <stdio.h>

int add(int a, int b);
void greet(void);

int main(void) {
    printf("Sum: %d\n", add(3, 5));
    greet();
    return 0;
}

int add(int a, int b) { return a + b; }
void greet(void) { printf("Hello\n"); }

2.2 Function Definition

The definition contains the actual body that runs when the function is called.

Definition example
int square(int n) {
    return n * n;
}

2.3 Function Call

A call transfers control to the function. Arguments (if any) are evaluated and passed according to the rules of C.

Call example
#include <stdio.h>

int max(int a, int b);

int main(void) {
    int x = 10, y = 25;
    int m = max(x, y);   /* call */
    printf("Max = %d\n", m);
    return 0;
}

int max(int a, int b) {
    return (a > b) ? a : b;
}

2.4 Complete Function Lifecycle

Prototype → call from main → definition → return.

calculatePower lifecycle
#include <stdio.h>

int calculatePower(int base, int exponent);

int main(void) {
    int result = calculatePower(2, 5);
    printf("2^5 = %d\n", result);
    return 0;
}

int calculatePower(int base, int exponent) {
    int power = 1;
    for (int i = 0; i < exponent; i++)
        power *= base;
    return power;
}

3. Function Types

3.1 No Parameters, No Return (void)

displayMenu
void displayMenu(void) {
    printf("===== MAIN MENU =====\n");
    printf("1. Add Record\n2. Delete Record\n3. View Records\n4. Exit\n");
    printf("====================\n");
}

int main(void) {
    displayMenu();
    return 0;
}

3.2 With Parameters, No Return

drawLine and printStudentInfo
#include <stdio.h>

void drawLine(char symbol, int length) {
    for (int i = 0; i < length; i++)
        printf("%c", symbol);
    printf("\n");
}

void printStudentInfo(char name[], int age, float gpa) {
    printf("Student: %s\nAge: %d\nGPA: %.2f\n", name, age, gpa);
    drawLine('-', 30);
}

int main(void) {
    printStudentInfo("Alice", 20, 3.8f);
    printStudentInfo("Bob", 22, 3.5f);
    return 0;
}

3.3 No Parameters, Returns a Value

getPi returns a constant (no time.h needed).

getPi
double getPi(void) {
    return 3.141592653589793;
}

int main(void) {
    printf("Pi = %.6f\n", getPi());
    return 0;
}

3.4 With Parameters and Return

Arithmetic helpers
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
double divide(int a, int b) {
    if (b == 0) return 0.0;
    return (double)a / b;
}
double circleArea(double radius) {
    double pi = 3.141592653589793;
    return pi * radius * radius;
}

int main(void) {
    printf("5+3=%d\n", add(5,3));
    printf("Area=%.2f\n", circleArea(2.5));
    return 0;
}

4. Parameters

4.1 Formal vs Actual Parameters

FormalActual
Names in the function definitionExpressions at the call site
int add(int a, int b)a, b are formaladd(x, y)x, y are actual

4.2 Pass by Value

C passes arguments by value: the callee receives copies. Changes to parameters do not affect the caller variables.

Pass by value
void modifyValue(int num) {
    printf("Inside - before: %d\n", num);
    num = 100;
    printf("Inside - after: %d\n", num);
}

int main(void) {
    int value = 5;
    printf("Before call: %d\n", value);
    modifyValue(value);
    printf("After call: %d\n", value);
    return 0;
}

4.3 Pass by Reference (Pointers)

To modify caller data, pass addresses and use pointers in the callee.

Pass by reference (pointers)
void modifyValue(int *num) {
    printf("Inside - before: %d\n", *num);
    *num = 100;
    printf("Inside - after: %d\n", *num);
}

int main(void) {
    int value = 5;
    printf("Before call: %d\n", value);
    modifyValue(&value);
    printf("After call: %d\n", value);
    return 0;
}

4.4 Multiple Outputs via Pointers

findMinMax
void findMinMax(int arr[], int n, int *minVal, int *maxVal) {
    *minVal = *maxVal = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] < *minVal) *minVal = arr[i];
        if (arr[i] > *maxVal) *maxVal = arr[i];
    }
}

int main(void) {
    int data[] = {4, 1, 9, 3};
    int lo, hi;
    findMinMax(data, 4, &lo, &hi);
    printf("min=%d max=%d\n", lo, hi);
    return 0;
}

4.5 Arrays as Parameters

Array parameters decay to pointers; pass size explicitly when needed.

arraySum
int arraySum(int arr[], int size) {
    int sum = 0;
    for (int i = 0; i < size; i++)
        sum += arr[i];
    return sum;
}

int main(void) {
    int nums[] = {10, 20, 30};
    printf("Sum = %d\n", arraySum(nums, 3));
    return 0;
}

5. Return Values

5.1 Return Types

A function return type must match what you return (or use void for no value).

Different return types
char gradeChar(int score) {
    if (score >= 90) return 'A';
    if (score >= 80) return 'B';
    return 'C';
}

int main(void) {
    printf("Grade: %c\n", gradeChar(84));
    return 0;
}

5.2 Multiple Return Paths

Use switch or conditional return statements for different outcomes.

getGrade with switch
const char *getGrade(int marks) {
    switch (marks / 10) {
        case 10: case 9: return "A";
        case 8: return "B";
        case 7: return "C";
        default: return "F";
    }
}

int main(void) {
    printf("Grade: %s\n", getGrade(88));
    return 0;
}

5.3 Return vs Pointer Parameters

Return a single value when that is enough; use pointer parameters when the caller must receive several results or large structures in place.

Return vs out-parameters
int square(int n) { return n * n; }

void bounds(int a, int b, int *lo, int *hi) {
    *lo = (a < b) ? a : b;
    *hi = (a > b) ? a : b;
}

5.4 Returning Arrays (static and heap)

Never return a pointer to a local array — it becomes invalid after the function ends (dangling pointer).

static buffer vs malloc
int *bad(void) {
    int local[3] = {1, 2, 3};
    return local;  /* WRONG: dangling after return */
}

int *goodStatic(void) {
    static int buf[3] = {1, 2, 3};
    return buf;    /* OK: static storage duration */
}

int *goodHeap(void) {
    int *p = (int *)malloc(3 * sizeof(int));
    if (p) { p[0]=1; p[1]=2; p[2]=3; }
    return p;      /* caller must free */
}
Prefer returning scalars, using caller-provided buffers, or documenting ownership when returning malloc memory.

5.5 void Functions

void functions perform an action and do not send a value back to the caller.

void example
void printBanner(const char *title) {
    printf("==== %s ====\n", title);
}

int main(void) {
    printBanner("Learn C");
    return 0;
}

6. Scope

6.1 Local Scope

Variables declared inside a function are visible only there.

Local variables
void demo(void) {
    int count = 0;
    count++;
    printf("count=%d\n", count);
}

int main(void) {
    demo();
    return 0;
}

6.2 Global Scope

File-scope globals are visible from declaration point to end of the translation unit (use sparingly).

Global counter
int g_calls = 0;

void tick(void) { g_calls++; }

int main(void) {
    tick(); tick();
    printf("calls=%d\n", g_calls);
    return 0;
}

6.3 static Locals

static local variables keep their value between calls but are not visible outside the function.

static local
void counter(void) {
    static int n = 0;
    n++;
    printf("n=%d\n", n);
}

int main(void) {
    counter(); counter(); counter();
    return 0;
}

6.4 Block Scope and Shadowing (C)

An inner block may declare a variable with the same name as an outer variable. Inside the inner block, the inner declaration hides (shadows) the outer one. C does not use C++ scope resolution (::).

Shadowing in C
int main(void) {
    int x = 10;
    printf("outer start: %d\n", x);
    {
        int x = 20;  /* shadows outer x in this block only */
        printf("inner block: %d\n", x);
    }
    printf("outer again: %d\n", x);
    return 0;
}

7. Recursion

A function that calls itself must have a base case and progress toward it.

countdown
void countdown(int n) {
    if (n <= 0) {
        printf("Blast off!\n");
    } else {
        printf("%d... ", n);
        countdown(n - 1);
    }
}

int main(void) {
    countdown(5);
    return 0;
}
factorial
long factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

int main(void) {
    printf("5! = %ld\n", factorial(5));
    return 0;
}

8. Advanced Function Concepts

8.1 Function Pointers (Brief)

Functions have addresses. A function pointer can store that address and be used to call the function indirectly — useful for callbacks and tables of operations.

return_type (*ptr)(param_types) = function_name; ptr(args); /* call through pointer */
Function pointers are advanced; master value, pointers, and arrays as parameters first.

Common Mistakes and Best Practices

Common Mistake 1: Missing Function Prototype
// ERROR: Function used before declaration int main() { int result = add(5, 3); // Compiler error return 0; } int add(int a, int b) { // Defined after main return a + b; }
Solution: Add function prototype int add(int a, int b); // Prototype at top
Common Mistake 2: Forgetting Array Size
// DANGEROUS: No size information void processArray(int arr[]) { for(int i = 0; i < ???; i++) { // How many elements? // Process array } }
// SOLUTION: Always pass size void processArray(int arr[], int size) { for(int i = 0; i < size; i++) { // Safe processing } }
Common Mistake 3: Not Validating Input Parameters
// RISKY: No validation float divide(int a, int b) { return (float)a / b; // May divide by zero }
// BETTER: Validate parameters float divide(int a, int b) { if(b == 0) { printf("Error: Division by zero!\n"); return 0.0; } return (float)a / b; }
Function Best Practices:
  1. Always declare function prototypes at the top
  2. Use meaningful function names (verbs for actions)
  3. Keep functions small and focused (single responsibility)
  4. Document functions with comments (purpose, parameters, return)
  5. Validate input parameters inside functions
  6. Pass array size as separate parameter
  7. Use const for parameters that shouldn't be modified
  8. Return meaningful error codes or use error handling
  9. Avoid global variables; use parameters instead
  10. Test functions independently (unit testing)
Good Function Design
  • Clear, descriptive names
  • Small, focused functionality
  • Proper parameter validation
  • Meaningful return values
  • No side effects (when possible)
Poor Function Design
  • Vague, cryptic names
  • Too many responsibilities
  • No parameter checking
  • Unnecessary global variables
  • Too many parameters (> 4)

Key Takeaways

  • Functions enable modular programming and code reusability
  • C supports built-in and user-defined functions
  • Call by value passes copies; call by reference passes addresses
  • Arrays are always passed by reference (as pointers)
  • For 2D arrays, must specify column size in function parameters
  • Always pass array size as separate parameter
  • Function prototypes should be declared before use
  • Functions should have single responsibility
  • Use const keyword for parameters that shouldn't be modified
  • Proper error handling is essential in functions
Next Topics: We'll explore recursion in detail, iteration vs recursion, types of recursion etc.