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(void) {
    int x = 42;
    int *p = &x;
    printf("x = %d, *p = %d\n", x, *p);
    *p = 100;
    printf("after *p=100: x = %d\n", x);
    return 0;
}
Output:
x = 42, *p = 42
after *p=100: x = 100
Example 2: NULL Pointers and Pointer Safety
#include <stdio.h>

int main(void) {
    int *p = NULL;
    int x = 5;
    if (p != NULL)
        printf("%d\n", *p);
    else
        printf("p is NULL — safe to skip dereference\n");
    p = &x;
    printf("*p = %d\n", *p);
    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(void) {
    int arr[] = {10, 20, 30};
    int *p = arr;
    printf("%d %d %d\n", *p, *(p+1), *(p+2));
    return 0;
}
Example 2: 2D Arrays and Pointers
#include <stdio.h>

int main(void) {
    int m[2][3] = {{1,2,3}, {4,5,6}};
    printf("%d\n", m[1][2]);
    printf("%d\n", *(*(m+1)+2));
    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>

int main(void) {
    char s[] = "Hello";
    char *p = s;
    while (*p) {
        putchar(*p++);
    }
    putchar('\n');
    printf("len = %zu\n", strlen(s));
    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 Examples:

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

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

int main(void) {
    int (*op)(int, int) = add;
    printf("%d\n", op(5, 3));
    op = sub;
    printf("%d\n", op(5, 3));
    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>

void swap(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
}

int main(void) {
    int x = 3, y = 7;
    swap(&x, &y);
    printf("x=%d y=%d\n", x, y);
    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>

int main(void) {
    int *p = (int *)malloc(3 * sizeof(int));
    if (!p) return 1;
    p[0] = 1; p[1] = 2; p[2] = 3;
    printf("%d %d %d\n", p[0], p[1], p[2]);
    free(p);
    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.