Introduction to Functions
Functions are the building blocks of C programs. They allow code reuse, modularity, and better organization. A function is a self-contained block of code that performs a specific task and can be called from other parts of the program.
Function Architecture Overview
Function Structure ├── Function Declaration (Prototype) │ ├── Return Type │ ├── Function Name │ └── Parameter List ├── Function Definition │ ├── Function Header │ ├── Local Variables │ ├── Function Body │ └── Return Statement └── Function Call ├── Arguments ├── Stack Frame └── Return Value
Basic Function Syntax
Function Declaration (Prototype)
return_type function_name(parameter_type1 parameter1, parameter_type2 parameter2, ...);
Function Definition
return_type function_name(parameter_type1 parameter1, parameter_type2 parameter2, ...) {
// Function body
// Local variables
// Statements
return value; // If return_type is not void
}
Simple Function Example
#include <stdio.h>
// Function declaration
int add(int a, int b);
int main() {
int x = 10, y = 20;
int result;
// Function call
result = add(x, y);
printf("%d + %d = %d\n", x, y, result);
return 0;
}
// Function definition
int add(int a, int b) {
return a + b;
}
Output:
10 + 20 = 30
Types of Functions
1. Functions with Return Value and Parameters
#include <stdio.h>
// Function that returns maximum of two numbers
int max(int a, int b) {
return (a > b) ? a : b;
}
int main() {
int num1 = 45, num2 = 78;
int maximum = max(num1, num2);
printf("Maximum of %d and %d is %d\n", num1, num2, maximum);
return 0;
}
2. Functions with Return Value but No Parameters
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// Function that returns a random number
int getRandomNumber() {
return rand() % 100;
}
int main() {
srand(time(NULL)); // Seed random generator
for (int i = 0; i < 5; i++) {
printf("Random number %d: %d\n", i+1, getRandomNumber());
}
return 0;
}
3. Functions with No Return Value (void) but Parameters
#include <stdio.h>
// Void function that prints a pattern
void printPattern(int n, char symbol) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
printf("%c ", symbol);
}
printf("\n");
}
}
int main() {
printPattern(5, '*');
printf("\n");
printPattern(4, '#');
return 0;
}
Output:
* * * * * * * * * * * * * * * # # # # # # # # # #
4. Functions with No Return Value and No Parameters
#include <stdio.h>
// Function that prints a welcome message
void printWelcome() {
printf("************************\n");
printf("* Welcome to My App *\n");
printf("************************\n");
}
int main() {
printWelcome();
printf("Main program continues...\n");
return 0;
}
Function Parameters
1. Pass by Value
#include <stdio.h>
void modifyValue(int x) {
printf("Inside function (before): x = %d\n", x);
x = 100; // Modifies local copy only
printf("Inside function (after): x = %d\n", x);
}
int main() {
int num = 50;
printf("Before function call: num = %d\n", num);
modifyValue(num);
printf("After function call: num = %d\n", num);
return 0;
}
Output:
Before function call: num = 50 Inside function (before): x = 50 Inside function (after): x = 100 After function call: num = 50
2. Pass by Reference (using Pointers)
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
printf("Inside swap: a = %d, b = %d\n", *a, *b);
}
int main() {
int x = 10, y = 20;
printf("Before swap: x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("After swap: x = %d, y = %d\n", x, y);
return 0;
}
Output:
Before swap: x = 10, y = 20 Inside swap: a = 20, b = 10 After swap: x = 20, y = 10
3. Passing Arrays to Functions
#include <stdio.h>
// Method 1: Using array notation
void printArray1(int arr[], int size) {
printf("Array elements: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// Method 2: Using pointer notation
void printArray2(int *arr, int size) {
printf("Array elements (pointer): ");
for (int i = 0; i < size; i++) {
printf("%d ", *(arr + i));
}
printf("\n");
}
// Modify array elements
void doubleArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2;
}
}
int main() {
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);
printArray1(numbers, size);
printArray2(numbers, size);
doubleArray(numbers, size);
printf("After doubling: ");
printArray1(numbers, size);
return 0;
}
4. Passing Strings to Functions
#include <stdio.h>
#include <string.h>
int countVowels(char str[]) {
int count = 0;
for (int i = 0; str[i] != '\0'; i++) {
char c = tolower(str[i]);
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
count++;
}
}
return count;
}
void toUpperCase(char *str) {
for (int i = 0; str[i] != '\0'; i++) {
if (str[i] >= 'a' && str[i] <= 'z') {
str[i] = str[i] - 32;
}
}
}
int main() {
char text[] = "Hello World";
printf("Original: %s\n", text);
printf("Vowel count: %d\n", countVowels(text));
toUpperCase(text);
printf("Uppercase: %s\n", text);
return 0;
}
Return Values
1. Returning Basic Types
#include <stdio.h>
#include <math.h>
int getSquare(int x) {
return x * x;
}
float getAverage(float a, float b) {
return (a + b) / 2.0;
}
char getGrade(int score) {
if (score >= 90) return 'A';
else if (score >= 80) return 'B';
else if (score >= 70) return 'C';
else if (score >= 60) return 'D';
else return 'F';
}
int main() {
printf("Square of 5: %d\n", getSquare(5));
printf("Average of 10.5 and 20.5: %.2f\n", getAverage(10.5, 20.5));
printf("Grade for 85: %c\n", getGrade(85));
return 0;
}
2. Returning Pointers
#include <stdio.h>
#include <stdlib.h>
// Return pointer to static array (DANGEROUS - don't do this in real code)
int* getStaticArray() {
static int arr[5] = {10, 20, 30, 40, 50};
return arr; // Returns pointer to static array
}
// Return pointer to dynamically allocated memory
int* createArray(int size, int initialValue) {
int *arr = (int*)malloc(size * sizeof(int));
if (arr != NULL) {
for (int i = 0; i < size; i++) {
arr[i] = initialValue;
}
}
return arr; // Caller must free this memory
}
int main() {
// Static array return
int *staticArr = getStaticArray();
printf("Static array: ");
for (int i = 0; i < 5; i++) {
printf("%d ", staticArr[i]);
}
printf("\n");
// Dynamic array return
int *dynamicArr = createArray(10, 5);
if (dynamicArr != NULL) {
printf("Dynamic array: ");
for (int i = 0; i < 10; i++) {
printf("%d ", dynamicArr[i]);
}
printf("\n");
free(dynamicArr); // Don't forget to free!
}
return 0;
}
3. Returning Structures
#include <stdio.h>
#include <string.h>
typedef struct {
char name[50];
int age;
float salary;
} Employee;
Employee createEmployee(const char *name, int age, float salary) {
Employee emp;
strcpy(emp.name, name);
emp.age = age;
emp.salary = salary;
return emp;
}
void printEmployee(Employee emp) {
printf("Name: %s, Age: %d, Salary: %.2f\n",
emp.name, emp.age, emp.salary);
}
int main() {
Employee emp1 = createEmployee("John Doe", 30, 50000.0);
Employee emp2 = createEmployee("Jane Smith", 28, 55000.0);
printEmployee(emp1);
printEmployee(emp2);
return 0;
}
Function Scope and Lifetime
1. Local Variables
#include <stdio.h>
void function1() {
int x = 10; // Local to function1
printf("function1: x = %d\n", x);
}
void function2() {
int x = 20; // Local to function2 (different from function1's x)
printf("function2: x = %d\n", x);
}
int main() {
int x = 5; // Local to main
printf("main: x = %d\n", x);
function1();
function2();
printf("main: x = %d\n", x);
return 0;
}
2. Static Variables
#include <stdio.h>
void counter() {
static int count = 0; // Initialized only once
count++;
printf("Count = %d\n", count);
}
int main() {
printf("Static variable example:\n");
counter(); // Count = 1
counter(); // Count = 2
counter(); // Count = 3
counter(); // Count = 4
return 0;
}
3. Global Variables
#include <stdio.h>
// Global variable
int globalCounter = 0;
void incrementCounter() {
globalCounter++; // Modifies global variable
}
void resetCounter() {
globalCounter = 0;
}
void printCounter() {
printf("Global counter: %d\n", globalCounter);
}
int main() {
printCounter(); // 0
incrementCounter();
incrementCounter();
incrementCounter();
printCounter(); // 3
resetCounter();
printCounter(); // 0
return 0;
}
Recursive Functions
1. Factorial Using Recursion
#include <stdio.h>
unsigned long long factorial(int n) {
// Base case
if (n == 0 || n == 1) {
return 1;
}
// Recursive case
return n * factorial(n - 1);
}
int main() {
for (int i = 0; i <= 10; i++) {
printf("%d! = %llu\n", i, factorial(i));
}
return 0;
}
Output:
0! = 1 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880 10! = 3628800
2. Fibonacci Series Using Recursion
#include <stdio.h>
int fibonacci(int n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
int terms = 10;
printf("Fibonacci series (%d terms):\n", terms);
for (int i = 0; i < terms; i++) {
printf("%d ", fibonacci(i));
}
printf("\n");
return 0;
}
3. Tower of Hanoi
#include <stdio.h>
void towerOfHanoi(int n, char from, char to, char aux) {
if (n == 1) {
printf("Move disk 1 from %c to %c\n", from, to);
return;
}
towerOfHanoi(n - 1, from, aux, to);
printf("Move disk %d from %c to %c\n", n, from, to);
towerOfHanoi(n - 1, aux, to, from);
}
int main() {
int disks = 3;
printf("Tower of Hanoi with %d disks:\n", disks);
towerOfHanoi(disks, 'A', 'C', 'B');
return 0;
}
Output:
Tower of Hanoi with 3 disks: Move disk 1 from A to C Move disk 2 from A to B Move disk 1 from C to B Move disk 3 from A to C Move disk 1 from B to A Move disk 2 from B to C Move disk 1 from A to C
Function Pointers
1. Basic Function Pointer
#include <stdio.h>
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;
}
int divide(int a, int b) {
if (b != 0) return a / b;
return 0;
}
int main() {
// Declare function pointer
int (*operation)(int, int);
int x = 20, y = 5;
// Use function pointer
operation = add;
printf("Add: %d\n", operation(x, y));
operation = subtract;
printf("Subtract: %d\n", operation(x, y));
operation = multiply;
printf("Multiply: %d\n", operation(x, y));
operation = divide;
printf("Divide: %d\n", operation(x, y));
return 0;
}
2. Array of Function Pointers
#include <stdio.h>
void printHello() { printf("Hello"); }
void printWorld() { printf("World"); }
void printExclamation() { printf("!"); }
void printNewLine() { printf("\n"); }
int main() {
// Array of function pointers
void (*functions[])() = {
printHello, printWorld, printExclamation, printNewLine
};
// Call functions using array
for (int i = 0; i < 4; i++) {
functions[i]();
}
return 0;
}
3. Function Pointers as Parameters
#include <stdio.h>
// Function that takes another function as parameter
void processArray(int arr[], int size, int (*operation)(int)) {
for (int i = 0; i < size; i++) {
arr[i] = operation(arr[i]);
}
}
int square(int x) { return x * x; }
int doubleIt(int x) { return x * 2; }
int increment(int x) { return x + 1; }
void printArray(int arr[], int size, const char *name) {
printf("%s: ", name);
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);
printArray(numbers, size, "Original");
processArray(numbers, size, square);
printArray(numbers, size, "After square");
processArray(numbers, size, doubleIt);
printArray(numbers, size, "After double");
processArray(numbers, size, increment);
printArray(numbers, size, "After increment");
return 0;
}
Inline Functions (C99)
#include <stdio.h>
// Inline function definition
inline int max(int a, int b) {
return (a > b) ? a : b;
}
// Static inline - common in header files
static inline int min(int a, int b) {
return (a < b) ? a : b;
}
int main() {
int x = 10, y = 20;
printf("max(%d, %d) = %d\n", x, y, max(x, y));
printf("min(%d, %d) = %d\n", x, y, min(x, y));
return 0;
}
Variadic Functions
1. Variable Number of Arguments
#include <stdio.h>
#include <stdarg.h>
// Function that sums variable number of integers
int sum(int count, ...) {
va_list args;
int total = 0;
va_start(args, count);
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
// Function that finds maximum among variable arguments
int maxOf(int count, ...) {
va_list args;
int max, current;
va_start(args, count);
max = va_arg(args, int); // First argument
for (int i = 1; i < count; i++) {
current = va_arg(args, int);
if (current > max) {
max = current;
}
}
va_end(args);
return max;
}
int main() {
printf("Sum of 3, 5, 7: %d\n", sum(3, 3, 5, 7));
printf("Sum of 1, 2, 3, 4, 5: %d\n", sum(5, 1, 2, 3, 4, 5));
printf("Max of 10, 20, 15: %d\n", maxOf(3, 10, 20, 15));
printf("Max of 100, 50, 75, 200, 150: %d\n", maxOf(5, 100, 50, 75, 200, 150));
return 0;
}
2. printf-style Variadic Function
#include <stdio.h>
#include <stdarg.h>
void myPrintf(const char *format, ...) {
va_list args;
va_start(args, format);
vprintf(format, args); // Use vprintf for variadic printing
va_end(args);
}
int main() {
myPrintf("Hello %s, your score is %d and grade is %c\n",
"Alice", 95, 'A');
myPrintf("Value: %f, Hex: %x\n", 3.14159, 255);
return 0;
}
Advanced Function Examples
1. Calculator Program
#include <stdio.h>
#include <stdlib.h>
// Function prototypes
float add(float a, float b) { return a + b; }
float subtract(float a, float b) { return a - b; }
float multiply(float a, float b) { return a * b; }
float divide(float a, float b) { return (b != 0) ? a / b : 0; }
float power(float base, int exp) {
float result = 1;
for (int i = 0; i < abs(exp); i++) {
result *= base;
}
return (exp >= 0) ? result : 1 / result;
}
// Function pointer array
typedef struct {
char symbol;
float (*func)(float, float);
char *name;
} Operation;
int main() {
Operation ops[] = {
{'+', add, "Addition"},
{'-', subtract, "Subtraction"},
{'*', multiply, "Multiplication"},
{'/', divide, "Division"},
{'^', (float (*)(float, float))power, "Power"}
};
int choice;
float a, b;
printf("=== Calculator ===\n");
printf("Operations:\n");
for (int i = 0; i < 5; i++) {
printf("%d. %s (%c)\n", i+1, ops[i].name, ops[i].symbol);
}
printf("Enter choice (1-5): ");
scanf("%d", &choice);
if (choice >= 1 && choice <= 5) {
printf("Enter two numbers: ");
scanf("%f %f", &a, &b);
float result;
if (choice == 5) {
result = ((float (*)(float, int))ops[4].func)(a, (int)b);
} else {
result = ops[choice-1].func(a, b);
}
printf("Result: %.2f\n", result);
} else {
printf("Invalid choice\n");
}
return 0;
}
2. Sorting with Function Pointers
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// Comparison functions
int compareAscending(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
int compareDescending(const void *a, const void *b) {
return (*(int*)b - *(int*)a);
}
int compareAbsolute(const void *a, const void *b) {
return abs(*(int*)a) - abs(*(int*)b);
}
void printArray(int arr[], int size, const char *message) {
printf("%s: ", message);
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int numbers[] = {5, -3, 10, -8, 2, -1, 7};
int size = sizeof(numbers) / sizeof(numbers[0]);
printArray(numbers, size, "Original");
// Sort ascending
int arr1[7];
memcpy(arr1, numbers, sizeof(numbers));
qsort(arr1, size, sizeof(int), compareAscending);
printArray(arr1, size, "Ascending");
// Sort descending
int arr2[7];
memcpy(arr2, numbers, sizeof(numbers));
qsort(arr2, size, sizeof(int), compareDescending);
printArray(arr2, size, "Descending");
// Sort by absolute value
int arr3[7];
memcpy(arr3, numbers, sizeof(numbers));
qsort(arr3, size, sizeof(int), compareAbsolute);
printArray(arr3, size, "By absolute value");
return 0;
}
3. Memoization for Performance
#include <stdio.h>
#define MAX 100
// Memoized Fibonacci
unsigned long long fibMemo(int n, unsigned long long memo[]) {
if (memo[n] != 0) {
return memo[n];
}
if (n <= 1) {
return n;
}
memo[n] = fibMemo(n-1, memo) + fibMemo(n-2, memo);
return memo[n];
}
// Regular recursive Fibonacci (inefficient)
unsigned long long fibRecursive(int n) {
if (n <= 1) return n;
return fibRecursive(n-1) + fibRecursive(n-2);
}
int main() {
unsigned long long memo[MAX] = {0};
printf("Memoized Fibonacci:\n");
for (int i = 0; i <= 40; i+=5) {
printf("fib(%d) = %llu\n", i, fibMemo(i, memo));
}
printf("\nPerformance comparison (fib(40)):\n");
clock_t start = clock();
unsigned long long result1 = fibMemo(40, memo);
clock_t end = clock();
printf("Memoized: %llu (time: %ld ticks)\n", result1, end - start);
start = clock();
unsigned long long result2 = fibRecursive(40);
end = clock();
printf("Recursive: %llu (time: %ld ticks)\n", result2, end - start);
return 0;
}
Common Pitfalls and Best Practices
1. Missing Function Prototypes
// BAD - no prototype
int main() {
int result = add(5, 3); // Compiler assumes add returns int
// May cause issues if add returns something else
return 0;
}
int add(int a, int b) {
return a + b;
}
// GOOD - with prototype
int add(int a, int b); // Prototype
int main() {
int result = add(5, 3);
return 0;
}
int add(int a, int b) {
return a + b;
}
2. Returning Address of Local Variable
#include <stdio.h>
// BAD - returning address of local variable
int* badFunction() {
int x = 10;
return &x; // x is destroyed when function ends
}
// GOOD - static or dynamic allocation
int* goodFunction() {
static int x = 10; // Static - persists
return &x;
}
int* betterFunction() {
int *x = malloc(sizeof(int));
*x = 10;
return x; // Caller must free
}
int main() {
// This is undefined behavior
// int *p = badFunction();
int *p1 = goodFunction();
printf("Static: %d\n", *p1);
int *p2 = betterFunction();
printf("Dynamic: %d\n", *p2);
free(p2);
return 0;
}
3. Not Checking for NULL
#include <stdio.h>
#include <stdlib.h>
void safeProcessArray(int *arr, int size) {
if (arr == NULL) {
printf("Error: NULL array passed\n");
return;
}
for (int i = 0; i < size; i++) {
arr[i] *= 2;
}
}
int main() {
int *arr = malloc(5 * sizeof(int));
if (arr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 5; i++) {
arr[i] = i;
}
safeProcessArray(arr, 5); // OK
safeProcessArray(NULL, 5); // Handled gracefully
free(arr);
return 0;
}
4. Parameter Order Consistency
#include <stdio.h>
#include <string.h>
// BAD - inconsistent parameter order
void copyString1(char *dest, char *src) { strcpy(dest, src); }
void copyString2(char *src, char *dest) { strcpy(dest, src); } // Confusing!
// GOOD - consistent with standard library
void stringCopy(char *dest, const char *src) {
strcpy(dest, src);
}
int main() {
char dest[50];
const char *src = "Hello";
// Standard library uses (dest, src)
strcpy(dest, src);
// Your functions should follow the same convention
stringCopy(dest, src);
return 0;
}
Function Organization Best Practices
Header File Example (functions.h)
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
// Function prototypes
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(int a, int b);
// Inline function
static inline int max(int a, int b) {
return (a > b) ? a : b;
}
// Constants
#define PI 3.14159
#endif
Implementation File (functions.c)
#include "functions.h"
#include <stdio.h>
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) {
printf("Error: Division by zero\n");
return 0;
}
return (double)a / b;
}
Main Program (main.c)
#include <stdio.h>
#include "functions.h"
int main() {
int x = 10, y = 5;
printf("Add: %d\n", add(x, y));
printf("Subtract: %d\n", subtract(x, y));
printf("Multiply: %d\n", multiply(x, y));
printf("Divide: %.2f\n", divide(x, y));
printf("Max: %d\n", max(x, y));
return 0;
}
Performance Considerations
1. Function Call Overhead
#include <stdio.h>
#include <time.h>
// Inline function (suggested)
static inline int squareInline(int x) {
return x * x;
}
// Regular function
int squareRegular(int x) {
return x * x;
}
int main() {
const int iterations = 10000000;
int result = 0;
clock_t start, end;
// Test inline function
start = clock();
for (int i = 0; i < iterations; i++) {
result += squareInline(i % 100);
}
end = clock();
printf("Inline: %ld ticks\n", end - start);
// Test regular function
start = clock();
for (int i = 0; i < iterations; i++) {
result += squareRegular(i % 100);
}
end = clock();
printf("Regular: %ld ticks\n", end - start);
return 0;
}
2. Function Inlining Trade-offs
// For small, frequently called functions
static inline int min(int a, int b) {
return (a < b) ? a : b;
}
// For larger functions, let compiler decide
int complexCalculation(int a, int b, int c) {
// Complex logic here
return (a * b) + (b * c) + (c * a);
}
Conclusion
Key Takeaways
- Functions promote code reuse and modularity
- Function prototypes are essential for type checking
- Parameters can be passed by value or reference
- Return values can be of any type
- Scope determines variable accessibility
- Recursion provides elegant solutions for certain problems
- Function pointers enable dynamic behavior
- Variadic functions handle variable arguments
Best Practices Summary
- Always declare function prototypes
- Use meaningful function names
- Keep functions focused (single responsibility)
- Validate parameters
- Document complex functions
- Consider inline for small, frequently called functions
- Free dynamically allocated memory
- Avoid global variables when possible
- Use const for parameters that shouldn't be modified
- Handle errors gracefully
When to Use Functions
- Code that's used multiple times
- Complex operations that need isolation
- Logical units that form a coherent task
- Algorithms that benefit from recursion
- Interface boundaries between modules
Functions are fundamental to C programming, enabling modular, maintainable, and reusable code. Mastery of functions is essential for writing professional-quality C programs.