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

Introduction to C Functions

Functions are the building blocks of C programming. They are self-contained blocks of code that perform specific tasks, enabling code reusability, modularity, and maintainability.

Why Use Functions?
  • Code Reusability: Write once, use multiple times
  • Modularity: Break complex problems into smaller parts
  • Abstraction: Hide implementation details
  • Maintainability: Easier debugging and updates
  • Team Collaboration: Divide work among programmers
Function Components
  • Function Declaration: Tells compiler about function
  • Function Definition: Actual implementation
  • Function Call: Invokes the function
  • Parameters: Input to function
  • Return Value: Output from function
Function Execution Flow:
Main
Call
Execute
Return
Stack Frame Created → Parameters Passed → Local Variables → Return Value

Important Function Components

Every function consists of: Return Type (data type returned), Function Name (identifier), Parameters (input values), Function Body (implementation), and Return Statement (output value).

Types of Functions in C

Here is a comprehensive comparison of all function types in C with their key characteristics:

Function Type Description Examples Characteristics
Built-in Functions
Library Functions
Pre-defined functions in C standard libraries
printf(), scanf()
strlen(), strcpy()
sqrt(), pow()
Ready to use Require headers
Optimized by compiler, part of standard library
User-Defined Functions
Custom Functions
Functions created by programmers
int add(int a, int b)
void display()
float calculate()
Custom logic Improve modularity
Project-specific, improve code organization
Functions with Return Value
Value-Returning
Functions that return a value to caller
return a + b;
return result;
Return type Use in expressions
Must specify return type, use return statement
Void Functions
No Return Value
Functions that don't return any value
void printHello()
void displayMenu()
Return type void No return
Return type is void, perform actions only
Recursive Functions
Self-Calling
Functions that call themselves
int factorial(int n)
int fibonacci(int n)
Base case Stack memory
Base case required, elegant for certain problems
Choosing the Right Function Type: Use built-in functions for common operations, user-defined functions for custom logic, value-returning functions when you need a result, void functions for actions without results, and recursive functions for problems with recursive nature.
Function Types Examples
#include <stdio.h>
#include <math.h>  // For built-in functions

// User-defined function with return value
int add(int a, int b) {
    return a + b;
}

// User-defined void function
void printLine(int length) {
    for(int i = 0; i < length; i++) {
        printf("-");
    }
    printf("\n");
}

// User-defined recursive function
int factorial(int n) {
    if(n <= 1) return 1;  // Base case
    return n * factorial(n - 1);  // Recursive call
}

int main() {
    int x = 5, y = 3;
    
    // Using built-in function
    printf("Built-in sqrt(%d) = %.2f\n", x, sqrt(x));
    
    // Using user-defined value-returning function
    int sum = add(x, y);
    printf("User-defined add(%d, %d) = %d\n", x, y, sum);
    
    // Using user-defined void function
    printf("Printing line:\n");
    printLine(20);
    
    // Using recursive function
    printf("Recursive factorial(%d) = %d\n", x, factorial(x));
    
    // Function calls in expressions
    printf("Combined: add(%d, %d) * 2 = %d\n", 
           x, y, add(x, y) * 2);
    
    return 0;
}

User-Defined Functions - Complete Guide

User-defined functions are created by programmers to perform specific tasks. They consist of function declaration, definition, and calling.

Syntax:
return_type function_name(parameter_list) { // function body return value; // optional }

Function Execution Flow:

Function Declaration
Function Call
Push to Stack
Execute Function Body
Return Value
Pop from Stack
Continue in Main

Key Characteristics:

  • Modularity: Break programs into manageable pieces
  • Reusability: Call functions multiple times
  • Abstraction: Hide implementation details
  • Testing: Test functions independently

Complete User-Defined Function Example:

Complete User-Defined Function Example
#include <stdio.h>

// 1. FUNCTION DECLARATION (Prototype)
// Tells compiler about function name, return type, and parameters
int findMaximum(int a, int b);
void printTable(int number);
float calculateAverage(int arr[], int size);

// 2. MAIN FUNCTION
int main() {
    int num1 = 45, num2 = 78;
    int numbers[] = {85, 90, 78, 92, 88};
    int size = 5;
    
    // Calling function with return value
    int max = findMaximum(num1, num2);
    printf("Maximum of %d and %d is: %d\n\n", num1, num2, max);
    
    // Calling void function
    printf("Multiplication table of 5:\n");
    printTable(5);
    
    // Calling function with array parameter
    float avg = calculateAverage(numbers, size);
    printf("\nAverage of array elements: %.2f\n\n", avg);
    
    // Calling functions multiple times (reusability)
    printf("Checking maximum for different values:\n");
    printf("findMaximum(10, 20) = %d\n", findMaximum(10, 20));
    printf("findMaximum(100, 50) = %d\n", findMaximum(100, 50));
    printf("findMaximum(-5, 10) = %d\n", findMaximum(-5, 10));
    
    return 0;
}

// 3. FUNCTION DEFINITIONS
// Definition of findMaximum function
int findMaximum(int a, int b) {
    if(a > b) {
        return a;
    } else {
        return b;
    }
}

// Definition of printTable function (void - no return value)
void printTable(int number) {
    for(int i = 1; i <= 10; i++) {
        printf("%d x %d = %d\n", number, i, number * i);
    }
}

// Definition of calculateAverage function
float calculateAverage(int arr[], int size) {
    int sum = 0;
    for(int i = 0; i < size; i++) {
        sum += arr[i];
    }
    return (float)sum / size;
}
Expected Output:
Maximum of 45 and 78 is: 78

Multiplication table of 5:
5 x 1 = 5
5 x 2 = 10
... (table continues)
5 x 10 = 50

Average of array elements: 86.60

Checking maximum for different values:
findMaximum(10, 20) = 20
findMaximum(100, 50) = 100
findMaximum(-5, 10) = 10

Function Components Explained:

Function Prototype
return_type function_name(parameters);

Example:
int add(int a, int b);

Function Call
function_name(arguments);

Example:
result = add(5, 3);

Function Definition
return_type function_name(parameters) {
    // function body
    return value;
}
Return Statement
return expression;

Void functions: No return needed
Other functions: Must return value of specified type

Parameter Passing Methods

Parameter passing methods determine how arguments are passed to functions. C supports call by value and call by reference.

Method Description Effect on Original Example
Call by Value Creates copy of actual parameter in formal parameter No change - Original unchanged
void func(int a)
Call by Reference Passes memory address of actual parameter Can change - Original can be modified
void func(int *a)
Call by Value vs Call by Reference
#include <stdio.h>

// Call by Value - receives copy of value
void swapByValue(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    printf("Inside swapByValue: a = %d, b = %d\n", a, b);
}

// Call by Reference - receives address
void swapByReference(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
    printf("Inside swapByReference: *a = %d, *b = %d\n", *a, *b);
}

// Another Call by Value example
int squareByValue(int num) {
    num = num * num;  // Changes local copy only
    return num;
}

// Call by Reference example
void squareByReference(int *num) {
    *num = (*num) * (*num);  // Changes original value
}

int main() {
    int x = 10, y = 20;
    
    printf("=== DEMONSTRATING CALL BY VALUE ===\n");
    printf("Before swapByValue: x = %d, y = %d\n", x, y);
    swapByValue(x, y);  // Pass values
    printf("After swapByValue: x = %d, y = %d\n", x, y);
    
    printf("\n=== DEMONSTRATING CALL BY REFERENCE ===\n");
    printf("Before swapByReference: x = %d, y = %d\n", x, y);
    swapByReference(&x, &y);  // Pass addresses
    printf("After swapByReference: x = %d, y = %d\n", x, y);
    
    printf("\n=== MORE EXAMPLES ===\n");
    int num1 = 5;
    int num2 = 5;
    
    // Call by Value - returns new value
    int result1 = squareByValue(num1);
    printf("squareByValue(%d) = %d (num1 unchanged: %d)\n", 
           5, result1, num1);
    
    // Call by Reference - modifies original
    squareByReference(&num2);
    printf("squareByReference(&num2): num2 is now %d\n", num2);
    
    // Practical use cases
    printf("\n=== PRACTICAL USE CASES ===\n");
    printf("Use Call by Value when:\n");
    printf("  - Function shouldn't modify original\n");
    printf("  - Working with simple data types\n");
    printf("  - Return value is sufficient\n\n");
    
    printf("Use Call by Reference when:\n");
    printf("  - Function needs to modify original\n");
    printf("  - Passing large data structures\n");
    printf("  - Need multiple return values\n");
    printf("  - Implementing efficient algorithms\n");
    
    return 0;
}
Call by Value Characteristics
  • Actual parameters copied to formal parameters
  • Changes inside function don't affect original
  • Safe but uses extra memory
  • Slower for large data
  • Default in C for simple types
Call by Reference Characteristics
  • Address of actual parameter passed
  • Changes inside function affect original
  • Efficient for large data
  • Requires pointer syntax
  • Can return multiple values
Parameter Passing Visualization:
Call by Value:
Main: x = 10 (Address: 1000)
Function: a = 10 (Address: 2000) ← Copy

Function modifies a (Address: 2000)

x (Address: 1000) remains 10
Call by Reference:
Main: x = 10 (Address: 1000)
Function: *a = address of x (1000)

Function modifies *a (Address: 1000)

x (Address: 1000) changes to new value

Arrays as Function Arguments

Arrays are always passed to functions by reference (as pointers), even though the syntax may look like call by value.

Syntax for Array Parameters:
// 1D Array void processArray(int arr[], int size); void processArray(int *arr, int size); // 2D Array (must specify columns) void processMatrix(int rows, int cols, int matrix[][cols]); void processMatrix(int rows, int cols, int (*matrix)[cols]);

1D Arrays as Function Arguments:

Passing 1D Arrays to Functions
#include <stdio.h>

// Method 1: Using array notation with size
void printArray(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 modifyArray(int *arr, int size) {
    printf("Doubling array elements...\n");
    for(int i = 0; i < size; i++) {
        arr[i] = arr[i] * 2;  // Changes original array
    }
}

// Method 3: Returning array through parameters
void getArrayStats(int arr[], int size, int *sum, float *avg, int *max, int *min) {
    *sum = 0;
    *max = arr[0];
    *min = arr[0];
    
    for(int i = 0; i < size; i++) {
        *sum += arr[i];
        if(arr[i] > *max) *max = arr[i];
        if(arr[i] < *min) *min = arr[i];
    }
    *avg = (float)(*sum) / size;
}

// Method 4: Creating and returning new array (through parameters)
void reverseArray(int src[], int dest[], int size) {
    for(int i = 0; i < size; i++) {
        dest[i] = src[size - 1 - i];
    }
}

int main() {
    int numbers[] = {1, 2, 3, 4, 5};
    int size = 5;
    
    printf("=== PASSING 1D ARRAYS TO FUNCTIONS ===\n\n");
    
    // Passing array to print function
    printArray(numbers, size);
    
    // Modifying original array
    printf("\nBefore modification: ");
    printArray(numbers, size);
    modifyArray(numbers, size);
    printf("After modification: ");
    printArray(numbers, size);
    
    // Getting array statistics
    int total, maximum, minimum;
    float average;
    getArrayStats(numbers, size, &total, &average, &maximum, &minimum);
    printf("\nArray Statistics:\n");
    printf("Sum: %d\n", total);
    printf("Average: %.2f\n", average);
    printf("Maximum: %d\n", maximum);
    printf("Minimum: %d\n", minimum);
    
    // Reversing array
    int reversed[5];
    reverseArray(numbers, reversed, size);
    printf("\nOriginal array: ");
    printArray(numbers, size);
    printf("Reversed array: ");
    printArray(reversed, size);
    
    // Important Note
    printf("\n=== IMPORTANT NOTES ===\n");
    printf("1. Arrays are ALWAYS passed by reference\n");
    printf("2. Changes inside function affect original array\n");
    printf("3. Size must be passed separately\n");
    printf("4. arr[] and *arr are equivalent in parameter list\n");
    
    return 0;
}

2D Arrays as Function Arguments:

Passing 2D Arrays to Functions
#include <stdio.h>

// Method 1: Fixed column size (must specify columns)
void printMatrix(int rows, int cols, int matrix[][cols]) {
    printf("Matrix (%dx%d):\n", rows, cols);
    for(int i = 0; i < rows; i++) {
        for(int j = 0; j < cols; j++) {
            printf("%4d", matrix[i][j]);
        }
        printf("\n");
    }
}

// Method 2: Using pointer to array (fixed column size)
void addMatrices(int rows, int cols, int A[][cols], int B[][cols], int result[][cols]) {
    for(int i = 0; i < rows; i++) {
        for(int j = 0; j < cols; j++) {
            result[i][j] = A[i][j] + B[i][j];
        }
    }
}

// Method 3: Using single pointer (flattened array)
void multiplyMatrix(int *matrix, int rows, int cols, int multiplier) {
    for(int i = 0; i < rows * cols; i++) {
        matrix[i] = matrix[i] * multiplier;
    }
}

// Method 4: Transpose matrix
void transposeMatrix(int rows, int cols, int src[][cols], int dest[][rows]) {
    for(int i = 0; i < rows; i++) {
        for(int j = 0; j < cols; j++) {
            dest[j][i] = src[i][j];
        }
    }
}

int main() {
    int rows = 3, cols = 4;
    
    // Initialize matrices
    int A[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    
    int B[3][4] = {
        {12, 11, 10, 9},
        {8, 7, 6, 5},
        {4, 3, 2, 1}
    };
    
    printf("=== PASSING 2D ARRAYS TO FUNCTIONS ===\n\n");
    
    // Print matrices
    printf("Matrix A:\n");
    printMatrix(rows, cols, A);
    
    printf("\nMatrix B:\n");
    printMatrix(rows, cols, B);
    
    // Matrix addition
    int C[3][4];
    addMatrices(rows, cols, A, B, C);
    printf("\nMatrix C = A + B:\n");
    printMatrix(rows, cols, C);
    
    // Matrix multiplication by scalar (using flattened array)
    printf("\nMultiplying matrix A by 2:\n");
    multiplyMatrix((int *)A, rows, cols, 2);
    printMatrix(rows, cols, A);
    
    // Transpose matrix
    int transposed[4][3];  // Note: dimensions swapped
    transposeMatrix(rows, cols, A, transposed);
    printf("\nTranspose of modified A (4x3):\n");
    printMatrix(cols, rows, transposed);
    
    printf("\n=== KEY POINTS FOR 2D ARRAYS ===\n");
    printf("1. Must specify column size in function parameters\n");
    printf("2. int matrix[][cols] or int (*matrix)[cols] can be used\n");
    printf("3. Row size can be passed as parameter\n");
    printf("4. Memory is contiguous (row-major order)\n");
    
    return 0;
}
Memory Layout of Array Parameters

1D Arrays: int arr[] is equivalent to int *arr in parameter list

2D Arrays: int matrix[][cols] is equivalent to int (*matrix)[cols]

Important: Array size information is lost when passed to functions. Always pass size as separate parameter.

Practical Function Applications

Complete Program Using Functions
#include <stdio.h>

// Function prototypes
void displayMenu();
int getChoice();
void performOperation(int choice);
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
float divide(int a, int b);
void processArray(int arr[], int size);
void processMatrix(int rows, int cols, int matrix[][cols]);

// Global constants
#define MAX_SIZE 10
#define ROWS 3
#define COLS 3

int main() {
    int choice;
    
    printf("=== FUNCTION-BASED PROGRAM DEMO ===\n\n");
    
    do {
        displayMenu();
        choice = getChoice();
        
        if(choice >= 1 && choice <= 4) {
            performOperation(choice);
        } else if(choice == 5) {
            // Array processing
            int arr[MAX_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
            printf("\n=== ARRAY PROCESSING ===\n");
            processArray(arr, MAX_SIZE);
        } else if(choice == 6) {
            // Matrix processing
            int matrix[ROWS][COLS] = {
                {1, 2, 3},
                {4, 5, 6},
                {7, 8, 9}
            };
            printf("\n=== MATRIX PROCESSING ===\n");
            processMatrix(ROWS, COLS, matrix);
        } else if(choice != 7) {
            printf("Invalid choice! Please try again.\n");
        }
        
    } while(choice != 7);
    
    printf("\nThank you for using the program!\n");
    return 0;
}

// Function definitions
void displayMenu() {
    printf("\n========== MAIN MENU ==========\n");
    printf("1. Addition\n");
    printf("2. Subtraction\n");
    printf("3. Multiplication\n");
    printf("4. Division\n");
    printf("5. Process Array\n");
    printf("6. Process Matrix\n");
    printf("7. Exit\n");
    printf("===============================\n");
}

int getChoice() {
    int choice;
    printf("Enter your choice (1-7): ");
    scanf("%d", &choice);
    return choice;
}

void performOperation(int choice) {
    int a, b;
    printf("Enter two numbers: ");
    scanf("%d %d", &a, &b);
    
    switch(choice) {
        case 1:
            printf("Result: %d + %d = %d\n", a, b, add(a, b));
            break;
        case 2:
            printf("Result: %d - %d = %d\n", a, b, subtract(a, b));
            break;
        case 3:
            printf("Result: %d × %d = %d\n", a, b, multiply(a, b));
            break;
        case 4:
            if(b != 0) {
                printf("Result: %d ÷ %d = %.2f\n", a, b, divide(a, b));
            } else {
                printf("Error: Division by zero!\n");
            }
            break;
    }
}

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; }
float divide(int a, int b) { return (float)a / b; }

void processArray(int arr[], int size) {
    int sum = 0;
    int max = arr[0];
    int min = arr[0];
    
    printf("Array elements: ");
    for(int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
        sum += arr[i];
        if(arr[i] > max) max = arr[i];
        if(arr[i] < min) min = arr[i];
    }
    
    printf("\nArray Statistics:\n");
    printf("Size: %d\n", size);
    printf("Sum: %d\n", sum);
    printf("Average: %.2f\n", (float)sum / size);
    printf("Maximum: %d\n", max);
    printf("Minimum: %d\n", min);
    
    // Reverse array
    printf("Reversed array: ");
    for(int i = size-1; i >= 0; i--) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

void processMatrix(int rows, int cols, int matrix[][cols]) {
    printf("Matrix (%dx%d):\n", rows, cols);
    for(int i = 0; i < rows; i++) {
        for(int j = 0; j < cols; j++) {
            printf("%4d", matrix[i][j]);
        }
        printf("\n");
    }
    
    // Calculate row sums
    printf("\nRow sums:\n");
    for(int i = 0; i < rows; i++) {
        int rowSum = 0;
        for(int j = 0; j < cols; j++) {
            rowSum += matrix[i][j];
        }
        printf("Row %d sum: %d\n", i+1, rowSum);
    }
    
    // Calculate column sums
    printf("\nColumn sums:\n");
    for(int j = 0; j < cols; j++) {
        int colSum = 0;
        for(int i = 0; i < rows; i++) {
            colSum += matrix[i][j];
        }
        printf("Column %d sum: %d\n", j+1, colSum);
    }
}

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.