C Programming Pointers
Core Concept

C Pointers - Complete Guide

Master C pointers including pointer types, arrays, strings, functions, pointer arithmetic, dynamic memory allocation, and programming best practices.

Memory Management

Direct access

Pointer Types

Multiple variations

Performance

Efficient operations

Dynamic Memory

Runtime allocation

Introduction to C Pointers

Pointers are variables that store memory addresses rather than actual values. They are one of the most powerful features of C programming, enabling direct memory access, efficient array handling, dynamic memory allocation, and function callbacks.

Why Pointers Matter
  • Direct Memory Access: Manipulate memory directly
  • Efficient Arrays: Faster array operations
  • Dynamic Memory: Allocate memory at runtime
  • Function Arguments: Pass by reference
  • Data Structures: Essential for linked lists, trees, graphs
  • System Programming: Required for OS and driver development
Pointer Concepts
  • Address Operator (&): Gets memory address
  • Dereference Operator (*): Accesses value at address
  • Pointer Arithmetic: Increment/decrement pointers
  • NULL Pointer: Pointer that points to nothing
  • Void Pointer: Generic pointer type
  • Double Pointer: Pointer to pointer

Fundamental Pointer Concepts

Every variable has: Name (identifier), Value (stored data), Type (data type), and Address (memory location). Pointers store addresses, allowing indirect access to values. Understanding this memory model is crucial for mastering pointers.

C Pointer Types Comparison

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

Pointer Type Declaration Use Case Key Features Example
Basic Pointer int *ptr; Store address of variable Points to single variable ptr = &var;
Array Pointer int *ptr; or int (*ptr)[N]; Array traversal Pointer arithmetic works ptr = arr;
String Pointer char *str; String manipulation Null-terminated str = "Hello";
Function Pointer int (*funcPtr)(int, int); Callbacks, dynamic dispatch Points to function funcPtr = &add;
Double Pointer int **ptr; 2D arrays, dynamic arrays Pointer to pointer ptr = &p;
Void Pointer void *ptr; Generic programming Can point to any type ptr = &anyVar;
NULL Pointer int *ptr = NULL; Initialization, error handling Points to nothing if(ptr == NULL)
Choosing the Right Pointer: Use basic pointers for single variables, array pointers for collections, string pointers for text, function pointers for callbacks, and void pointers for generic code. Always initialize pointers to prevent undefined behavior.

Basic Pointers - Declaration and Usage

Basic pointers store the memory address of a variable. They are declared using the asterisk (*) symbol and are fundamental to understanding all pointer concepts.

Syntax:
data_type *pointer_name; // Declaration pointer_name = &variable_name; // Assignment *pointer_name = value; // Dereferencing

Key Operators:

  • & (Address Operator): Returns memory address of a variable
  • * (Dereference Operator): Accesses value at stored address
  • NULL: Special value indicating pointer points to nothing

Memory Visualization:

Pointer Relationship with Variables
0x1000
var = 42
Variable
Address operator &
0x2000
ptr = 0x1000
Pointer
0x2000
ptr = 0x1000
Pointer
Dereference operator *
0x1000
*ptr = 42
Accessed Value

Basic Pointer Examples:

Example 1: Basic Pointer Operations
#include <stdio.h>

int main() {
    printf("=== BASIC POINTER OPERATIONS ===\n\n");
    
    // Variable declaration
    int number = 42;
    float pi = 3.14159;
    char letter = 'A';
    
    // Pointer declarations
    int *intPtr;
    float *floatPtr;
    char *charPtr;
    
    // Assign addresses to pointers
    intPtr = &number;
    floatPtr = π
    charPtr = &letter;
    
    // Display addresses and values
    printf("Variable 'number':\n");
    printf("  Value: %d\n", number);
    printf("  Address: %p\n", &number);
    printf("  Via pointer: %d\n\n", *intPtr);
    
    printf("Variable 'pi':\n");
    printf("  Value: %.5f\n", pi);
    printf("  Address: %p\n", &pi);
    printf("  Via pointer: %.5f\n\n", *floatPtr);
    
    printf("Variable 'letter':\n");
    printf("  Value: %c\n", letter);
    printf("  Address: %p\n", &letter);
    printf("  Via pointer: %c\n\n", *charPtr);
    
    // Modify values through pointers
    printf("=== MODIFYING THROUGH POINTERS ===\n\n");
    
    *intPtr = 100;
    *floatPtr = 2.71828;
    *charPtr = 'Z';
    
    printf("After modification through pointers:\n");
    printf("  number = %d\n", number);
    printf("  pi = %.5f\n", pi);
    printf("  letter = %c\n\n", letter);
    
    // Pointer to pointer (double pointer)
    printf("=== DOUBLE POINTER ===\n\n");
    
    int **doublePtr = &intPtr;
    
    printf("doublePtr = %p (address of intPtr)\n", doublePtr);
    printf("*doublePtr = %p (value of intPtr = address of number)\n", *doublePtr);
    printf("**doublePtr = %d (value of number)\n", **doublePtr);
    
    // Modify through double pointer
    **doublePtr = 999;
    printf("After **doublePtr = 999: number = %d\n", number);
    
    return 0;
}
Output:
=== BASIC POINTER OPERATIONS ===

Variable 'number':
Value: 42
Address: 0x7ffeed7d8a7c (example)
Via pointer: 42

Variable 'pi':
Value: 3.14159
Address: 0x7ffeed7d8a78 (example)
Via pointer: 3.14159

Variable 'letter':
Value: A
Address: 0x7ffeed7d8a77 (example)
Via pointer: A

=== MODIFYING THROUGH POINTERS ===

After modification through pointers:
number = 100
pi = 2.71828
letter = Z

=== DOUBLE POINTER ===

doublePtr = 0x7ffeed7d8a70 (address of intPtr)
*doublePtr = 0x7ffeed7d8a7c (value of intPtr = address of number)
**doublePtr = 100 (value of number)
After **doublePtr = 999: number = 999
Example 2: NULL Pointers and Pointer Safety
#include <stdio.h>

int main() {
    printf("=== NULL POINTERS AND SAFETY ===\n\n");
    
    // Always initialize pointers!
    int *uninitializedPtr;    // DANGEROUS: contains garbage
    int *nullPtr = NULL;      // SAFE: explicitly null
    int *initializedPtr;
    int value = 50;
    
    initializedPtr = &value;   // Proper initialization
    
    // Check before dereferencing
    printf("1. Checking nullPtr before use:\n");
    if(nullPtr == NULL) {
        printf("   nullPtr is NULL - safe to initialize\n");
        // Can't dereference: *nullPtr would crash
    }
    
    printf("\n2. Using initialized pointer:\n");
    if(initializedPtr != NULL) {
        printf("   Value via pointer: %d\n", *initializedPtr);
    }
    
    // Common pointer mistakes
    printf("\n3. Common mistakes to avoid:\n");
    
    // Mistake 1: Dereferencing uninitialized pointer
    // *uninitializedPtr = 10;  // CRASH: Segmentation fault
    
    // Mistake 2: Dereferencing NULL pointer
    // *nullPtr = 20;           // CRASH: Segmentation fault
    
    // Mistake 3: Using pointer after freeing memory
    int *dynamicPtr = (int*)malloc(sizeof(int));
    *dynamicPtr = 100;
    printf("   Before free: %d\n", *dynamicPtr);
    free(dynamicPtr);
    // *dynamicPtr = 200;      // DANGEROUS: Use after free
    
    // Proper way: Set to NULL after free
    dynamicPtr = NULL;
    
    // Void pointer demonstration
    printf("\n4. Void pointer (generic pointer):\n");
    
    void *genericPtr;
    int intValue = 65;
    char charValue = 'B';
    float floatValue = 3.14;
    
    genericPtr = &intValue;
    printf("   Void pointer pointing to int: %d\n", *(int*)genericPtr);
    
    genericPtr = &charValue;
    printf("   Void pointer pointing to char: %c\n", *(char*)genericPtr);
    
    genericPtr = &floatValue;
    printf("   Void pointer pointing to float: %.2f\n", *(float*)genericPtr);
    
    // Pointer size
    printf("\n5. Pointer sizes (platform dependent):\n");
    printf("   Size of int*: %lu bytes\n", sizeof(int*));
    printf("   Size of char*: %lu bytes\n", sizeof(char*));
    printf("   Size of float*: %lu bytes\n", sizeof(float*));
    printf("   Size of void*: %lu bytes\n", sizeof(void*));
    
    return 0;
}
CRITICAL: Pointer Safety Rules
  1. Always initialize pointers (to NULL or valid address)
  2. Always check for NULL before dereferencing
  3. Never dereference freed memory
  4. Set pointers to NULL after free()
  5. Be careful with pointer arithmetic
  6. Use const when pointers shouldn't modify data

Pointer to Arrays - Complete Guide

Array names in C are essentially constant pointers to the first element of the array. Pointers provide efficient ways to traverse and manipulate arrays.

Syntax:
int arr[5]; // Array declaration int *ptr = arr; // Pointer to array (same as &arr[0]) ptr = &arr[2]; // Pointer to specific element

Key Characteristics:

  • Array Name as Pointer: arr is equivalent to &arr[0]
  • Pointer Arithmetic: ptr + i moves i elements forward
  • Indexing: ptr[i] is same as *(ptr + i)
  • Multi-dimensional: Pointers can point to 2D/3D arrays
  • Bounds Checking: No automatic bounds checking - programmer's responsibility

Memory Visualization:

Array Memory Layout with Pointer
arr[0]
10
arr
arr[1]
20
arr+1
arr[2]
30
arr+2
arr[3]
40
arr+3
arr[4]
50
arr+4
ptr
→ arr[0]
Initially
ptr+2
→ arr[2]
After ptr += 2

Array Pointer Examples:

Example 1: 1D Array Pointer Operations
#include <stdio.h>

int main() {
    printf("=== 1D ARRAY POINTER OPERATIONS ===\n\n");
    
    int numbers[] = {10, 20, 30, 40, 50};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    
    // Different ways to get array address
    int *ptr1 = numbers;           // Array name decays to pointer
    int *ptr2 = &numbers[0];       // Address of first element
    int *ptr3 = &numbers[2];       // Address of third element
    
    printf("Array elements: ");
    for(int i = 0; i < size; i++) {
        printf("%d ", numbers[i]);
    }
    printf("\n\n");
    
    printf("1. Basic pointer operations:\n");
    printf("   numbers = %p (address of first element)\n", numbers);
    printf("   &numbers[0] = %p (same as above)\n", &numbers[0]);
    printf("   *numbers = %d (first element)\n", *numbers);
    printf("   *(numbers + 2) = %d (third element)\n", *(numbers + 2));
    printf("   numbers[3] = %d (fourth element)\n", numbers[3]);
    printf("   3[numbers] = %d (same as above - unusual but valid)\n", 3[numbers]);
    
    printf("\n2. Pointer arithmetic:\n");
    int *ptr = numbers;
    printf("   Start: ptr points to %d at address %p\n", *ptr, ptr);
    
    ptr++;  // Move to next element
    printf("   After ptr++: points to %d at address %p\n", *ptr, ptr);
    
    ptr += 2;  // Move two elements forward
    printf("   After ptr += 2: points to %d at address %p\n", *ptr, ptr);
    
    ptr--;  // Move one element back
    printf("   After ptr--: points to %d at address %p\n", *ptr, ptr);
    
    printf("\n3. Array traversal with pointers:\n");
    printf("   Forward: ");
    for(int *p = numbers; p < numbers + size; p++) {
        printf("%d ", *p);
    }
    
    printf("\n   Backward: ");
    for(int *p = numbers + size - 1; p >= numbers; p--) {
        printf("%d ", *p);
    }
    
    printf("\n\n4. Modifying array through pointer:\n");
    int *modPtr = numbers;
    *modPtr = 100;           // Modify first element
    *(modPtr + 3) = 400;     // Modify fourth element
    
    printf("   Modified array: ");
    for(int i = 0; i < size; i++) {
        printf("%d ", numbers[i]);
    }
    
    // Difference between two pointers
    printf("\n\n5. Pointer difference:\n");
    int *p1 = &numbers[1];
    int *p2 = &numbers[4];
    printf("   p2 - p1 = %ld (elements between them)\n", p2 - p1);
    
    return 0;
}
Example 2: 2D Arrays and Pointers
#include <stdio.h>

int main() {
    printf("=== 2D ARRAYS AND POINTERS ===\n\n");
    
    // 2D array
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    
    printf("2D Array (3x4 matrix):\n");
    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 4; j++) {
            printf("%3d ", matrix[i][j]);
        }
        printf("\n");
    }
    
    printf("\n1. Understanding 2D array memory:\n");
    printf("   matrix = %p (address of first row)\n", matrix);
    printf("   matrix[0] = %p (address of first element)\n", matrix[0]);
    printf("   &matrix[0][0] = %p (same as above)\n", &matrix[0][0]);
    printf("   *matrix = %p (address of first element)\n", *matrix);
    printf("   **matrix = %d (first element)\n", **matrix);
    
    printf("\n2. Pointer to 2D array (row pointer):\n");
    int (*rowPtr)[4] = matrix;  // Pointer to array of 4 integers
    
    printf("   First row via rowPtr: ");
    for(int i = 0; i < 4; i++) {
        printf("%d ", (*rowPtr)[i]);
    }
    
    rowPtr++;  // Move to next row
    printf("\n   Second row after rowPtr++: ");
    for(int i = 0; i < 4; i++) {
        printf("%d ", (*rowPtr)[i]);
    }
    
    printf("\n\n3. Element pointer:\n");
    int *elemPtr = &matrix[0][0];  // Pointer to first element
    
    printf("   Traversing as 1D array: ");
    for(int i = 0; i < 12; i++) {
        printf("%d ", *(elemPtr + i));
    }
    
    printf("\n\n4. Accessing via pointer arithmetic:\n");
    printf("   matrix[1][2] = %d\n", matrix[1][2]);
    printf("   *(*(matrix + 1) + 2) = %d (same as above)\n", *(*(matrix + 1) + 2));
    printf("   *(matrix[1] + 2) = %d (same as above)\n", *(matrix[1] + 2));
    
    printf("\n5. Dynamic 2D array using pointer to pointer:\n");
    int rows = 3, cols = 4;
    int **dynamicMatrix = (int**)malloc(rows * sizeof(int*));
    
    for(int i = 0; i < rows; i++) {
        dynamicMatrix[i] = (int*)malloc(cols * sizeof(int));
        for(int j = 0; j < cols; j++) {
            dynamicMatrix[i][j] = (i * cols) + j + 1;
        }
    }
    
    printf("   Dynamic matrix:\n");
    for(int i = 0; i < rows; i++) {
        printf("   ");
        for(int j = 0; j < cols; j++) {
            printf("%3d ", dynamicMatrix[i][j]);
        }
        printf("\n");
    }
    
    // Free memory
    for(int i = 0; i < rows; i++) {
        free(dynamicMatrix[i]);
    }
    free(dynamicMatrix);
    
    return 0;
}

Pointer to Strings - Complete Guide

In C, strings are arrays of characters terminated by a null character ('\0'). Pointers provide efficient ways to manipulate strings without copying entire strings.

Syntax:
char str[] = "Hello"; // Array of characters char *ptr = "World"; // Pointer to string literal char *ptr2 = str; // Pointer to string array

Key Characteristics:

  • String Literals: Stored in read-only memory (cannot be modified)
  • Null Termination: Strings end with '\0' character
  • Array vs Pointer: Arrays can be modified, string literals cannot
  • Standard Functions: strcpy, strlen, strcmp work with pointers
  • Pointer Arithmetic: Can traverse strings character by character

String Memory Visualization:

String Storage in Memory
str[0]
'H'
str[1]
'e'
str[2]
'l'
str[3]
'l'
str[4]
'o'
str[5]
'\0'
Null terminator
ptr
→ 'H'
String pointer
Read-only
"World"
String literal

String Pointer Examples:

Example: String Operations with Pointers
#include <stdio.h>
#include <string.h>
#include <ctype.h>

// Custom string functions using pointers
int stringLength(const char *str) {
    const char *ptr = str;
    while(*ptr != '\0') {
        ptr++;
    }
    return ptr - str;
}

void stringCopy(char *dest, const char *src) {
    while((*dest = *src) != '\0') {
        dest++;
        src++;
    }
}

int stringCompare(const char *str1, const char *str2) {
    while(*str1 && (*str1 == *str2)) {
        str1++;
        str2++;
    }
    return *(unsigned char*)str1 - *(unsigned char*)str2;
}

int main() {
    printf("=== STRING POINTER OPERATIONS ===\n\n");
    
    // Different ways to declare strings
    char str1[] = "Hello, World!";          // Modifiable array
    char *str2 = "String Literal";          // Read-only literal
    char str3[50];                          // Empty buffer
    const char *str4 = "Constant Pointer";  // Read-only pointer
    
    printf("1. String declarations:\n");
    printf("   str1 (array): %s\n", str1);
    printf("   str2 (pointer to literal): %s\n", str2);
    printf("   str4 (const pointer): %s\n", str4);
    
    // String literals are read-only
    // str2[0] = 'X';  // ERROR: Attempt to modify string literal
    
    // But arrays can be modified
    str1[0] = 'J';
    printf("   After str1[0] = 'J': %s\n", str1);
    
    printf("\n2. Custom string functions:\n");
    printf("   Length of '%s': %d\n", str1, stringLength(str1));
    
    stringCopy(str3, str1);
    printf("   Copied to str3: %s\n", str3);
    
    printf("   Compare '%s' with '%s': %d\n", str1, str3, stringCompare(str1, str3));
    printf("   Compare '%s' with '%s': %d\n", str1, str2, stringCompare(str1, str2));
    
    printf("\n3. Standard library functions:\n");
    printf("   strlen(str1): %lu\n", strlen(str1));
    
    strcpy(str3, "New String");
    printf("   After strcpy: %s\n", str3);
    
    strcat(str3, " Appended");
    printf("   After strcat: %s\n", str3);
    
    printf("   strcmp('%s', '%s'): %d\n", "Apple", "Banana", strcmp("Apple", "Banana"));
    
    printf("\n4. String traversal with pointers:\n");
    char *ptr = str1;
    printf("   Characters: ");
    while(*ptr != '\0') {
        printf("%c ", *ptr);
        ptr++;
    }
    
    printf("\n   Uppercase: ");
    ptr = str1;
    while(*ptr != '\0') {
        printf("%c", toupper(*ptr));
        ptr++;
    }
    
    printf("\n   Reverse: ");
    ptr = str1 + strlen(str1) - 1;  // Point to last character
    while(ptr >= str1) {
        printf("%c", *ptr);
        ptr--;
    }
    
    printf("\n\n5. Array of strings (using pointers):\n");
    char *fruits[] = {"Apple", "Banana", "Cherry", "Date", "Elderberry"};
    int fruitCount = sizeof(fruits) / sizeof(fruits[0]);
    
    printf("   Fruits array:\n");
    for(int i = 0; i < fruitCount; i++) {
        printf("   [%d] %s (length: %lu)\n", i, fruits[i], strlen(fruits[i]));
    }
    
    printf("\n6. Modifying string through pointer:\n");
    char buffer[50] = "Original Text";
    char *modPtr = buffer;
    
    printf("   Original: %s\n", buffer);
    
    // Modify through pointer
    *modPtr = 'M';
    modPtr += 9;  // Skip to 'T'
    *modPtr = 't';
    
    printf("   Modified: %s\n", buffer);
    
    // Find substring
    printf("\n7. Finding substring:\n");
    char text[] = "The quick brown fox jumps over the lazy dog";
    char *found = strstr(text, "fox");
    
    if(found) {
        printf("   Found 'fox' at position: %ld\n", found - text);
        printf("   Substring: %s\n", found);
    }
    
    // Tokenization
    printf("\n8. String tokenization:\n");
    char data[] = "apple,banana,cherry,date";
    char *token = strtok(data, ",");
    
    printf("   Tokens: ");
    while(token != NULL) {
        printf("'%s' ", token);
        token = strtok(NULL, ",");
    }
    
    return 0;
}

Function Pointers - Complete Guide

Function pointers store the address of a function, allowing functions to be passed as arguments, returned from functions, and stored in data structures. This enables callbacks and dynamic function dispatch.

Syntax:
return_type (*pointer_name)(parameter_types); // Declaration pointer_name = &function_name; // Assignment (optional &) return_value = (*pointer_name)(arguments); // Calling return_value = pointer_name(arguments); // Alternative calling

Key Characteristics:

  • Type Safety: Must match function signature exactly
  • Callbacks: Pass functions as arguments to other functions
  • Dynamic Dispatch: Choose function at runtime
  • Function Tables: Arrays of function pointers for state machines
  • qsort: Standard library uses function pointers for comparison

Function Pointer Visualization:

Function Pointer Relationship
Code Memory
add() function
Actual function code
Address of function
funcPtr
→ add()
Function pointer
Function Table
[add, sub, mul, div]
Array of function pointers
Callback
qsort(..., compare)
Function as argument

Function Pointer Examples:

Example: Comprehensive Function Pointer Usage
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

// Function prototypes
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) { return b != 0 ? a / b : 0; }

// Different function signatures
void printHello() { printf("Hello! "); }
void printWorld() { printf("World! "); }
double square(double x) { return x * x; }
double cube(double x) { return x * x * x; }

// Function that takes function pointer as argument
void repeatFunction(void (*func)(), int times) {
    for(int i = 0; i < times; i++) {
        func();
    }
    printf("\n");
}

// Comparison function for qsort
int compareInt(const void *a, const void *b) {
    return (*(int*)a - *(int*)b);
}

int compareString(const void *a, const void *b) {
    return strcmp(*(const char**)a, *(const char**)b);
}

// Function that returns function pointer
int (*getOperation(char op))(int, int) {
    switch(op) {
        case '+': return &add;
        case '-': return &subtract;
        case '*': return &multiply;
        case '/': return ÷
        default: return NULL;
    }
}

// Typedef for cleaner syntax
typedef int (*ArithmeticFunc)(int, int);
typedef double (*MathFunc)(double);

int main() {
    printf("=== FUNCTION POINTERS DEMO ===\n\n");
    
    printf("1. Basic function pointers:\n");
    
    // Declare function pointer
    int (*funcPtr)(int, int);
    
    // Assign and call
    funcPtr = &add;
    printf("   add(10, 5) = %d\n", funcPtr(10, 5));
    
    funcPtr = &subtract;
    printf("   subtract(10, 5) = %d\n", funcPtr(10, 5));
    
    // Alternative syntax (without &)
    funcPtr = multiply;
    printf("   multiply(10, 5) = %d\n", (*funcPtr)(10, 5));
    
    printf("\n2. Function pointer array (dispatch table):\n");
    
    // Array of function pointers
    ArithmeticFunc operations[] = {add, subtract, multiply, divide};
    char* opNames[] = {"Add", "Subtract", "Multiply", "Divide"};
    
    int x = 20, y = 4;
    for(int i = 0; i < 4; i++) {
        printf("   %s(%d, %d) = %d\n", opNames[i], x, y, operations[i](x, y));
    }
    
    printf("\n3. Functions as arguments (callbacks):\n");
    
    printf("   Repeating printHello 3 times: ");
    repeatFunction(printHello, 3);
    
    printf("   Repeating printWorld 2 times: ");
    repeatFunction(printWorld, 2);
    
    // Using with standard library (qsort)
    printf("\n4. Using with qsort:\n");
    
    int numbers[] = {42, 17, 89, 5, 33, 71};
    int numCount = sizeof(numbers) / sizeof(numbers[0]);
    
    printf("   Original: ");
    for(int i = 0; i < numCount; i++) printf("%d ", numbers[i]);
    
    qsort(numbers, numCount, sizeof(int), compareInt);
    
    printf("\n   Sorted: ");
    for(int i = 0; i < numCount; i++) printf("%d ", numbers[i]);
    
    // String sorting
    printf("\n\n5. String sorting with function pointers:\n");
    
    char *words[] = {"banana", "apple", "cherry", "date", "elderberry"};
    int wordCount = sizeof(words) / sizeof(words[0]);
    
    printf("   Original: ");
    for(int i = 0; i < wordCount; i++) printf("%s ", words[i]);
    
    qsort(words, wordCount, sizeof(char*), compareString);
    
    printf("\n   Sorted: ");
    for(int i = 0; i < wordCount; i++) printf("%s ", words[i]);
    
    printf("\n\n6. Function returning function pointer:\n");
    
    char ops[] = {'+', '-', '*', '/'};
    for(int i = 0; i < 4; i++) {
        ArithmeticFunc opFunc = getOperation(ops[i]);
        if(opFunc) {
            printf("   %d %c %d = %d\n", x, ops[i], y, opFunc(x, y));
        }
    }
    
    printf("\n7. Using typedef for cleaner syntax:\n");
    
    MathFunc mathOps[] = {square, cube, sqrt, sin};
    char* mathNames[] = {"Square", "Cube", "Square Root", "Sine"};
    double values[] = {2.0, 3.0, 4.0, 3.14159/2};
    
    for(int i = 0; i < 4; i++) {
        printf("   %s(%.2f) = %.4f\n", mathNames[i], values[i], mathOps[i](values[i]));
    }
    
    printf("\n8. Advanced: Function pointer in structures:\n");
    
    typedef struct {
        char *name;
        ArithmeticFunc operation;
    } Calculator;
    
    Calculator calc[] = {
        {"Addition", add},
        {"Subtraction", subtract},
        {"Multiplication", multiply},
        {"Division", divide}
    };
    
    for(int i = 0; i < 4; i++) {
        printf("   %s: %d\n", calc[i].name, calc[i].operation(15, 3));
    }
    
    printf("\n9. Void pointers with function pointers (advanced):\n");
    
    // Generic operation using void pointers
    void (*genericPrint)(const char*) = (void(*)(const char*))puts;
    genericPrint("   This is printed via function pointer!");
    
    return 0;
}

Pointers as Function Arguments

Passing pointers as function arguments allows functions to modify variables in the caller's scope (pass-by-reference) and is more efficient for large data structures than copying entire structures.

Syntax:
void function(int *ptr); // Function declaration function(&variable); // Function call *ptr = value; // Modify in function

Key Concepts:

  • Pass by Reference: Functions can modify original variables
  • Efficiency: Avoid copying large structures
  • Multiple Returns: Return multiple values via pointers
  • Arrays: Arrays are always passed by reference (as pointers)
  • Const Correctness: Use const to prevent modification
Passing Method Syntax Effect on Original Use Case
Pass by Value void func(int x) Original unchanged Simple values, don't need modification
Pass by Reference void func(int *x) Original can be modified Need to modify, large structures
Const Reference void func(const int *x) Original protected Read-only access, efficiency

Pointer Arguments Examples:

Example: Comprehensive Pointer Arguments
#include <stdio.h>
#include <string.h>

// Basic swap function
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// Function with const pointer (read-only)
int findMax(const int *arr, int size) {
    if(size <= 0) return 0;
    
    int max = arr[0];
    for(int i = 1; i < size; i++) {
        if(arr[i] > max) {
            max = arr[i];
        }
    }
    return max;
}

// Function returning multiple values via pointers
void getMinMax(const int *arr, int size, int *min, int *max) {
    if(size <= 0) return;
    
    *min = *max = arr[0];
    for(int i = 1; i < size; i++) {
        if(arr[i] < *min) *min = arr[i];
        if(arr[i] > *max) *max = arr[i];
    }
}

// Modify string through pointer
void toUpperCase(char *str) {
    while(*str) {
        if(*str >= 'a' && *str <= 'z') {
            *str = *str - ('a' - 'A');
        }
        str++;
    }
}

// Array modification
void reverseArray(int *arr, int size) {
    int *start = arr;
    int *end = arr + size - 1;
    
    while(start < end) {
        swap(start, end);
        start++;
        end--;
    }
}

// 2D array processing
void processMatrix(int (*matrix)[3], int rows, int cols) {
    for(int i = 0; i < rows; i++) {
        for(int j = 0; j < cols; j++) {
            matrix[i][j] *= 2;  // Double each element
        }
    }
}

// Function pointer as argument
void applyOperation(int *a, int *b, int (*operation)(int, int)) {
    int result = operation(*a, *b);
    printf("   Result: %d\n", result);
}

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

int main() {
    printf("=== POINTERS AS FUNCTION ARGUMENTS ===\n\n");
    
    printf("1. Basic pass by reference (swap):\n");
    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);
    
    printf("\n2. Array processing with pointers:\n");
    int numbers[] = {5, 2, 8, 1, 9, 3};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    
    printf("   Original array: ");
    for(int i = 0; i < size; i++) printf("%d ", numbers[i]);
    
    int max = findMax(numbers, size);
    printf("\n   Maximum value: %d\n", max);
    
    printf("\n3. Returning multiple values:\n");
    int minVal, maxVal;
    getMinMax(numbers, size, &minVal, &maxVal);
    printf("   Min: %d, Max: %d\n", minVal, maxVal);
    
    printf("\n4. String modification:\n");
    char text[] = "Hello, World!";
    printf("   Original: %s\n", text);
    toUpperCase(text);
    printf("   Uppercase: %s\n", text);
    
    printf("\n5. Array reversal:\n");
    printf("   Before reverse: ");
    for(int i = 0; i < size; i++) printf("%d ", numbers[i]);
    
    reverseArray(numbers, size);
    
    printf("\n   After reverse: ");
    for(int i = 0; i < size; i++) printf("%d ", numbers[i]);
    
    printf("\n\n6. 2D array processing:\n");
    int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
    
    printf("   Original matrix:\n");
    for(int i = 0; i < 2; i++) {
        printf("   ");
        for(int j = 0; j < 3; j++) {
            printf("%3d ", matrix[i][j]);
        }
        printf("\n");
    }
    
    processMatrix(matrix, 2, 3);
    
    printf("   After doubling:\n");
    for(int i = 0; i < 2; i++) {
        printf("   ");
        for(int j = 0; j < 3; j++) {
            printf("%3d ", matrix[i][j]);
        }
        printf("\n");
    }
    
    printf("\n7. Function pointer as argument:\n");
    int a = 10, b = 5;
    printf("   Applying add: ");
    applyOperation(&a, &b, add);
    
    printf("   Applying multiply: ");
    applyOperation(&a, &b, multiply);
    
    printf("\n8. Pointer to pointer argument:\n");
    char *names[] = {"Alice", "Bob", "Charlie"};
    char **namePtr = names;
    
    printf("   Names via double pointer:\n");
    for(int i = 0; i < 3; i++) {
        printf("   %s\n", *(namePtr + i));
    }
    
    printf("\n9. Const correctness examples:\n");
    
    // Read-only pointer
    const int readOnly = 100;
    const int *ptr = &readOnly;
    
    // *ptr = 200;  // ERROR: Cannot modify through const pointer
    
    int mutable = 200;
    const int *ptr2 = &mutable;  // Can point to mutable, but can't modify
    
    printf("   const int*: Can't modify through pointer\n");
    printf("   int* const: Can't change pointer itself\n");
    printf("   const int* const: Can't do either\n");
    
    return 0;
}

Dynamic Memory Allocation

Dynamic memory allocation allows programs to request memory at runtime using malloc, calloc, realloc, and free. This is essential for data structures that grow/shrink during execution.

Functions:
void* malloc(size_t size); // Allocate memory void* calloc(size_t num, size_t size); // Allocate and zero void* realloc(void* ptr, size_t size); // Resize memory void free(void* ptr); // Free memory

Key Functions Comparison:

Function Purpose Initialization When to Use
malloc() Allocate raw memory Garbage values General allocation, speed critical
calloc() Allocate and zero All bits zero Arrays, need initialization
realloc() Resize allocation Preserves old data Growing/shrinking allocations
free() Release memory N/A Always when done with memory
MEMORY MANAGEMENT RULES:
  1. Always check if malloc/calloc returns NULL
  2. Always free allocated memory
  3. Never access freed memory
  4. Set pointer to NULL after free()
  5. Don't forget to free in all code paths
  6. Use sizeof() for portability

Dynamic Memory Examples:

Example: Comprehensive Dynamic Memory Usage
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Safe allocation function
void* safeMalloc(size_t size) {
    void *ptr = malloc(size);
    if(ptr == NULL) {
        fprintf(stderr, "Memory allocation failed!\n");
        exit(EXIT_FAILURE);
    }
    return ptr;
}

int main() {
    printf("=== DYNAMIC MEMORY ALLOCATION ===\n\n");
    
    printf("1. Basic malloc usage:\n");
    
    // Allocate single integer
    int *singleInt = (int*)malloc(sizeof(int));
    if(singleInt == NULL) {
        printf("   Allocation failed!\n");
        return 1;
    }
    *singleInt = 42;
    printf("   Dynamically allocated integer: %d\n", *singleInt);
    free(singleInt);
    singleInt = NULL;  // Good practice
    
    printf("\n2. Dynamic array with malloc:\n");
    
    int arraySize = 5;
    int *dynamicArray = (int*)malloc(arraySize * sizeof(int));
    
    if(dynamicArray == NULL) {
        printf("   Array allocation failed!\n");
        return 1;
    }
    
    // Initialize array
    for(int i = 0; i < arraySize; i++) {
        dynamicArray[i] = i * 10;
    }
    
    printf("   Dynamic array: ");
    for(int i = 0; i < arraySize; i++) {
        printf("%d ", dynamicArray[i]);
    }
    
    printf("\n\n3. calloc vs malloc:\n");
    
    // malloc: contains garbage values
    int *mallocArray = (int*)malloc(5 * sizeof(int));
    printf("   malloc array (garbage values): ");
    for(int i = 0; i < 5; i++) {
        printf("%d ", mallocArray[i]);  // Random values
    }
    
    // calloc: initialized to zero
    int *callocArray = (int*)calloc(5, sizeof(int));
    printf("\n   calloc array (zeros): ");
    for(int i = 0; i < 5; i++) {
        printf("%d ", callocArray[i]);  // All zeros
    }
    
    free(mallocArray);
    free(callocArray);
    
    printf("\n\n4. realloc for resizing:\n");
    
    // Start with small array
    int *resizable = (int*)malloc(3 * sizeof(int));
    for(int i = 0; i < 3; i++) {
        resizable[i] = i + 1;
    }
    
    printf("   Original (size 3): ");
    for(int i = 0; i < 3; i++) printf("%d ", resizable[i]);
    
    // Resize to larger array
    resizable = (int*)realloc(resizable, 6 * sizeof(int));
    if(resizable == NULL) {
        printf("   Reallocation failed!\n");
        free(resizable);
        return 1;
    }
    
    // Initialize new elements
    for(int i = 3; i < 6; i++) {
        resizable[i] = (i + 1) * 10;
    }
    
    printf("\n   After realloc (size 6): ");
    for(int i = 0; i < 6; i++) printf("%d ", resizable[i]);
    
    // Shrink array
    resizable = (int*)realloc(resizable, 4 * sizeof(int));
    printf("\n   After shrink (size 4): ");
    for(int i = 0; i < 4; i++) printf("%d ", resizable[i]);
    
    free(resizable);
    
    printf("\n\n5. Dynamic 2D array (array of pointers):\n");
    
    int rows = 3, cols = 4;
    
    // Allocate array of pointers (rows)
    int **matrix = (int**)malloc(rows * sizeof(int*));
    for(int i = 0; i < rows; i++) {
        // Allocate each row (columns)
        matrix[i] = (int*)malloc(cols * sizeof(int));
        for(int j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j + 1;
        }
    }
    
    printf("   Dynamic 2D array (%dx%d):\n", rows, cols);
    for(int i = 0; i < rows; i++) {
        printf("   ");
        for(int j = 0; j < cols; j++) {
            printf("%3d ", matrix[i][j]);
        }
        printf("\n");
    }
    
    // Free 2D array (must free each row)
    for(int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);
    
    printf("\n6. Dynamic string allocation:\n");
    
    char *dynamicString = (char*)malloc(50 * sizeof(char));
    if(dynamicString) {
        strcpy(dynamicString, "This is a dynamically allocated string");
        printf("   Dynamic string: %s\n", dynamicString);
        
        // Resize string
        dynamicString = (char*)realloc(dynamicString, 100 * sizeof(char));
        strcat(dynamicString, " that has been extended!");
        printf("   After realloc: %s\n", dynamicString);
        
        free(dynamicString);
    }
    
    printf("\n7. Array of dynamic strings:\n");
    
    char **stringArray = (char**)malloc(4 * sizeof(char*));
    const char *names[] = {"Alice", "Bob", "Charlie", "Diana"};
    
    for(int i = 0; i < 4; i++) {
        // Allocate exact size for each string
        stringArray[i] = (char*)malloc((strlen(names[i]) + 1) * sizeof(char));
        strcpy(stringArray[i], names[i]);
    }
    
    printf("   String array:\n");
    for(int i = 0; i < 4; i++) {
        printf("   [%d] %s (length: %lu)\n", i, stringArray[i], strlen(stringArray[i]));
    }
    
    // Free string array
    for(int i = 0; i < 4; i++) {
        free(stringArray[i]);
    }
    free(stringArray);
    
    printf("\n8. Memory leak demonstration (what NOT to do):\n");
    
    int *leaky = (int*)malloc(10 * sizeof(int));
    // Forgot to free! This is a memory leak
    
    // Proper way:
    int *proper = (int*)malloc(10 * sizeof(int));
    // Use proper...
    free(proper);
    proper = NULL;  // Prevent dangling pointer
    
    printf("   Always free allocated memory!\n");
    
    printf("\n9. Using safe allocation wrapper:\n");
    
    int *safeArray = (int*)safeMalloc(5 * sizeof(int));
    for(int i = 0; i < 5; i++) {
        safeArray[i] = i * 100;
    }
    
    printf("   Safe allocation array: ");
    for(int i = 0; i < 5; i++) printf("%d ", safeArray[i]);
    
    free(safeArray);
    
    return 0;
}

Common Mistakes and Best Practices

Common Mistake 1: Dereferencing uninitialized pointer
int *ptr; // Uninitialized *ptr = 10; // CRASH: Segmentation fault
Solution: Always initialize pointers int *ptr = NULL; // or int *ptr = &variable;
Common Mistake 2: Memory leak
int *ptr = malloc(100 * sizeof(int)); // Use ptr... // Forgot to free! Memory leak
Solution: Always free allocated memory free(ptr); ptr = NULL;
Common Mistake 3: Using pointer after free
int *ptr = malloc(sizeof(int)); *ptr = 42; free(ptr); *ptr = 100; // DANGEROUS: Use after free
Solution: Set to NULL after free free(ptr); ptr = NULL;
Common Mistake 4: Buffer overflow
char buffer[10]; strcpy(buffer, "This is too long!"); // OVERFLOW
Solution: Use bounds checking strncpy(buffer, "This is too long!", 9); buffer[9] = '\0';
Pointer Best Practices:
  1. Initialize pointers: Always set to NULL or valid address
  2. Check for NULL: Before dereferencing any pointer
  3. Use const: When pointers shouldn't modify data
  4. Free memory: Always free dynamically allocated memory
  5. Set to NULL after free: Prevent dangling pointers
  6. Pointer arithmetic: Be careful with bounds
  7. Use sizeof: For portability across architectures
  8. Document ownership: Who allocates, who frees
  9. Prefer array syntax: ptr[i] over *(ptr + i) for readability
  10. Validate input: Check sizes before memory operations

Key Takeaways

  • Pointers store memory addresses, enabling direct memory access
  • & operator gets address, * operator dereferences
  • Arrays and pointers are closely related: arr[i] ≡ *(arr + i)
  • Strings are character arrays terminated by '\0'
  • Function pointers enable callbacks and dynamic dispatch
  • Pointers as function arguments allow pass-by-reference
  • malloc/calloc allocate memory, free releases it
  • Always check if malloc/calloc returns NULL
  • Use const with pointers for read-only access
  • NULL pointers should be checked before dereferencing
  • Pointer arithmetic works based on the type size
  • Double pointers (**ptr) are used for 2D arrays and dynamic arrays
  • Void pointers (void*) can point to any type but require casting
  • Memory leaks occur when allocated memory is not freed
  • Dangling pointers point to freed memory - always set to NULL after free
Next Topics: We'll explore C dynamic memeory, its related functions.