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); // 20p → address of a
*p → value of a
The code below demonstrates Declaration, Initialization and Dereferencing a Pointer
#include
int main() {
int num = 10;
int *ptr = # // 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
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.
| Operation | Status | Why? |
|---|---|---|
| p + n | Valid | You are moving n items forward in the list. |
| Valid | Calculates the distance (number of elements) between two pointers. | |
| p + p | Invalid | Adding two memory addresses is meaningless (like adding two phone numbers). |
| p * 3 | Invalid | Multiplying 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
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
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
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
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
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
- Complexity
Pointers make code harder to understand and debug. - Memory Leaks
Forgetting to free() allocated memory wastes system resources. - Dangling Pointers
Accessing memory after it has been freed leads to undefined behavior. - Security Risks
Buffer overflows and invalid memory access can cause crashes or vulnerabilities. - 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.