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
Table of Contents
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?
| Benefit | Description | Example |
|---|---|---|
| Code Reusability | Write once, use many times | calculateArea() called from anywhere |
| Modularity | Break complex problems into smaller pieces | 10 functions of 50 lines vs one 500-line block |
| Readability | Self-documenting code | calculateTotal() vs inline math |
| Maintainability | Fix bugs in one place | Update function once; all calls benefit |
| Testing | Test each piece independently | Test validateInput() alone |
| Abstraction | Hide complex details | Call sortArray() without knowing the algorithm |
1.3 Function Structure
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.
#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.
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.
#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.
#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)
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
#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).
double getPi(void) {
return 3.141592653589793;
}
int main(void) {
printf("Pi = %.6f\n", getPi());
return 0;
}
3.4 With Parameters and Return
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
| Formal | Actual |
|---|---|
| Names in the function definition | Expressions at the call site |
int add(int a, int b) — a, b are formal | add(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.
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.
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
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.
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).
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.
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.
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).
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 */
}
malloc memory.
5.5 void Functions
void functions perform an action and do not send a value back to the caller.
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.
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).
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.
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 (::).
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.
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;
}
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.
Common Mistakes and Best Practices
Function Best Practices:
- Always declare function prototypes at the top
- Use meaningful function names (verbs for actions)
- Keep functions small and focused (single responsibility)
- Document functions with comments (purpose, parameters, return)
- Validate input parameters inside functions
- Pass array size as separate parameter
- Use const for parameters that shouldn't be modified
- Return meaningful error codes or use error handling
- Avoid global variables; use parameters instead
- 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