C Pointers Simplified: Memory Addresses, Arithmetic and Structures

Table of Contents

What Exactly Is a Pointer in C?

In C, a pointer is a specialized variable designed to hold the direct memory address of another data object. Instead of holding a value directly, it points to where the value is stored in memory. The size of a pointer depends on the system architecture.  For example 32-bit systems have 4 bytes, 64-bit systems have 8 bytes.

 
int x = 10;
int *p = &x;
  • x stores the value 10
  • p stores the address of x
  • *p accesses the value stored at that address

This indirect access is what makes pointers powerful—and dangerous if misunderstood.

Why Pointers Exist in C

Advantages of Pointers

  • Direct memory access
  • Efficient parameter passing
  • Dynamic memory management
  • Hardware-level control

Pointers make all of this possible. Without pointers:

  • Arrays couldn’t be passed efficiently to functions
  • Dynamic data structures (linked lists, trees) wouldn’t exist
  • System programming (OS, drivers, compilers) would be impossible

Pointer Declaration and Initialization

Declaration Syntax

 
int *ptr;
  • int → type of data the pointer points to
  • * → indicates pointer
  • ptr → pointer variable name

Initialization

 
int a = 5;
int *ptr = &a;

Important rule:
A pointer must always point to a valid memory location before dereferencing.

Dereferencing a Pointer

Dereferencing means accessing the value stored at the address held by the pointer.

 
int a = 20;
int *p = &a;
printf("%d", *p); // 20

p → address of a

*p → value of a

The code below demonstrates Declaration, Initialization and Dereferencing a Pointer

				
					#include <stdio.h>

int main() {
    int num = 10;
    int *ptr = &num;  // Stores the address of 'num' in 'ptr'

    printf("Value of num: %d\n", num);
    printf("Address of num: %p\n", (void*)&num);
    printf("Value of ptr (address it holds): %p\n", (void*)ptr);
    printf("Value pointed to by ptr: %d\n", *ptr);

    return 0;
}

// Value of num: 10
// Address of num: 0x7ffeb239a324
// Value of ptr (address it holds): 0x7ffeb239a324
// Value pointed to by ptr: 10

				
			

Think of the variable num as a house and &num as its street address. The pointer ptr is a piece of paper where you wrote that address down. When you use *ptr, you are effectively driving to that address to see what is inside the house.

 

Pointer Size and Architecture

The code below gives the size of the pointer.

				
					#include <stdio.h>

int main() {
    int *ptr;     
    char *charPtr; 
    printf("Size of int pointer: %zu bytes\n", sizeof(ptr));
    printf("Size of char pointer: %zu bytes\n", sizeof(charPtr));
    return 0;
}

// Size of int pointer: 8 bytes
// Size of char pointer: 8 bytes

				
			

Pointer Arithmetic

Pointer arithmetic is not about adding simple numbers, it is about jumping blocks of memory.

 
#include 
int main() {
    int arr[3] = {10, 20, 30};
    int *p = arr;                     // Points to arr[0] (10)
    p = p + 1;                // Moves pointer forward by 1 integer size to arr[1]
    printf("%d", *p);                 // 20
    return 0;
}

The above code demonstrates the pointer arithmetic. Here an integer is 4 bytes and the array starts at memory address 1000:

  • int *p = arr; → p points to 1000(Value: 10).
  • p = p + 1; → p does notbecome 1001. It jumps 1 integer forward (4 bytes).

Calculation: 1000 + (1 * 4) = 1004.

  • printf(“%d”, *p); → Reads the value at 1004, which is 20.
OperationStatusWhy?
p + nValidYou are moving n items forward in the list.
ValidCalculates the distance (number of elements) between two pointers.
p + pInvalidAdding two memory addresses is meaningless (like adding two phone numbers).
p * 3InvalidMultiplying an address puts you in a random, unrelated memory location.

Pointers and Arrays

In C, the name of an array acts as a constant pointer to its first element. arr[i] is exactly the same as *(arr + i).  Array name is not modifiable but the Pointer variable is modifiable.

 
int arr[5];
int *p = arr;
  • arr is the address of the first element
  • arr[i] is equivalent to *(arr + i)

 

The code accesses the array elements by using pointers

				
					#include <stdio.h>

int main() {
    int arr[3] = {10, 20, 30};
    int *ptr = arr; // ptr points to arr[0]

    printf("Elements: ");
    for (int i = 0; i < 3; i++) {
        // Print the value at the current pointer address
        printf("%d ", *ptr);
        
        // Move the pointer to the next element
        ptr++; 
    }
    return 0;
}

// Elements: 10 20 30 
				
			

Pointers to Pointers

This concept is known as a Pointer to a Pointer (or double pointer), where one pointer holds the reference to another pointer’s memory location.

 
int x = 10;
int *p = &x;
int **pp = &p;
				
					#include <stdio.h>

int main() {
    int a = 10;
    int *p = &a;    // Pointer 'p' stores address of 'a'
    int **pp = &p;  // Double pointer 'pp' stores address of 'p'

    // Accessing 'a' through the double pointer
    printf("Value of a: %d", **pp); 

    return 0;
}

// Value of a: 10
				
			

This code demonstrates a double pointer (pp) which stores the memory address of the pointer p, requiring you to dereference it twice (**pp) to access the actual value of a. These are mainly used in Dynamic 2D arrays, Command-line arguments and Complex data structures

 

NULL Pointers

A NULL pointer is a pointer that is explicitly assigned to point to the memory address 0. In C, address 0 is reserved for the operating system, so pointing there effectively means this pointer points to nowhere.

Why Is It Important? (The 3 Rules)

1. Prevents “Wild” Pointers:

  • If you declare int *p; without assigning it, it points to a random garbage address (a “wild” pointer). Accessing it crashes your program.
  • Assigning int *p = NULL makes it safe because you know it’s empty.

 

2. Sentinel Value (The “Stop” Sign):

  • In data structures (like Linked Lists or Trees) or functions (like the findCritical example earlier), returning NULL is the standard way to say “Not Found” or “End of List”.

 

3. Error Handling:

  • Functions like malloc return NULL if they run out of memory. Checking for this prevents your program from trying to write data where no memory exists.

A NULL pointer points to nothing.

 
int *p = NULL;

You must never try to read the value (*p) of a NULL pointer. Doing so causes a Segmentation Fault (immediate crash).

The code snippet provided shows the golden rule of pointer safety:

				
					#include <stdio.h>

int main() {
    // Scenario 1: The pointer is NULL
    int *p = NULL; 

    // safety check prevents a crash
    if (p != NULL) {
        printf("Value: %d\n", *p);
    } else {
        printf("Pointer is NULL. Cannot access memory.\n");
    }
    // Scenario 2: The pointer is valid
    int value = 42;
    p = &value;
    if (p != NULL) {
        printf("Pointer is valid. Value: %d\n", *p);
    }
    return 0;
}

// Pointer is NULL. Cannot access memory.
// Pointer is valid. Value: 42

				
			

Void Pointers

A void pointer (often called a generic pointer) is a special pointer that has no data type associated with it. Think of it as a generic box that can hold the address of anything—an integer, a float, a character, or a struct. Because the compiler doesn’t know what is inside the box (Is it 4 bytes? 1 byte?), it cannot read the value directly. You must explicitly tell it what type to expect before reading.

 
void *vp;      // A generic pointer
int a = 10;
vp = &a;   
				
					#include <stdio.h>
int main() {
    int n = 10;
    char c = 'A';
    void *vp;
    // 1. Pointing to an Integer
    vp = &n;
    // We must cast (void*) to (int*) before dereferencing
    printf("Integer: %d\n", *(int*)vp);

    // 2. Pointing to a Character
    vp = &c;
    // We must cast (void*) to (char*) before dereferencing
    printf("Character: %c\n", *(char*)vp);

    return 0;
}

// Integer: 10
// Character: A

				
			

Pointer to a Structure

A Pointer to a Structure stores the memory address of a struct variable. This is extremely common in C because structures can get very large (imagine a struct with 50 fields). Passing the address (pointer) is instant, whereas copying the whole structure (passing by value) is slow.

The Arrow Operator (->)

This is the most important syntax rule here.

  • . (Dot Operator): Used when you have the actual variable (e.g. s.id).
  • -> (Arrow Operator): Used when you have a pointer to the variable (e.g. p->id).
				
					#include <stdio.h>

struct Student {
    int id;
    float marks;
};

int main() {
    struct Student s;
    struct Student *p = &s; // 'p' holds the address of 's'

    // Using the Arrow Operator to assign values
    p->id = 101;       // Go to address in 'p', find 'id', set to 101
    p->marks = 95.5;   // Go to address in 'p', find 'marks', set to 95.5

    // Printing using both methods to show they are the same
    printf("ID via Pointer: %d\n", p->id);
    printf("ID via Variable: %d\n", s.id);

    return 0;
}

// Integer: 10
// Character: A
				
			

When to Use Pointers (and When Not To)

Use Pointers When:

  • Working with arrays and strings
  • Dynamic memory is required
  • Large data structures are passed to functions
  • Low-level memory manipulation is needed

Avoid Pointers When:

  • Simple value passing is enough
  • Safer abstractions exist
  • Readability is more important than performance

 

Applications of Pointers

1. Dynamic Memory Allocation
Pointers are used with malloc(), calloc(), realloc() and free() to allocate and manage memory at runtime.

2. Efficient Function Parameter Passing
Using pointers allows functions to modify actual variables (call by reference) and avoids copying large data structures.

3. Implementation of Data Structures
Linked lists, stacks, queues, trees, and graphs rely heavily on pointers to connect nodes dynamically.

4. Array and String Handling
Arrays and strings are accessed and manipulated efficiently using pointers instead of index-based traversal.

5. Low-Level Memory and Hardware Access
Pointers allow direct access to memory locations, making them essential in system programming, embedded systems and OS development.

 

Frequently Asked Questions (FAQs)

1) What is a NULL Pointer in C?

A NULL pointer is a pointer that does not point to any valid memory location.

 
int *ptr = NULL;
vp = &a;   
  • It represents “points to nothing”
  • Prevents accidental memory access
  • Used as a safety check before dereferencing

 

2) What is Pointer to Pointer?

This concept is known as a Pointer to a Pointer (or double pointer), where one pointer holds the reference to another pointer’s memory location.

 
int x = 10;
int *p = &x;
int **pp = &p;

Memory Access:

  • p -> address of x
  • *p -> value of x
  • pp -> address of p
  • **pp -> value of x

 

3) What is Pointer Arithmetic?

Pointer arithmetic is the process of calculating memory addresses by adding or subtracting values scaled by the size of the data type.

 
int arr[] = {10, 20, 30};
int *p = arr;
p = p + 1;

Now p points to arr[1].

Important Rule:

Pointer arithmetic moves by size of the data type, not by bytes.

 

4) Disadvantages of Pointers in C

  1. Complexity
    Pointers make code harder to understand and debug.
  2. Memory Leaks
    Forgetting to free() allocated memory wastes system resources.
  3. Dangling Pointers
    Accessing memory after it has been freed leads to undefined behavior.
  4. Security Risks
    Buffer overflows and invalid memory access can cause crashes or vulnerabilities.
  5. Undefined Behavior
    Dereferencing NULL or uninitialized pointers can crash programs.

 

5) What is the Use of an array of Pointers?

An array of pointers stores multiple addresses instead of values.

Uses:

  • Efficient handling of strings
  • Dynamic storage of variable-length data
  • Implementing tables and lists
  • Reduces memory usage compared to 2D arrays
  • Used in command-line arguments.