Pointers in C
Introduction
Welcome to Pointers, often considered the most formidable hurdle for C programming students. But fear not—once you understand the mental model, pointers become an incredibly powerful tool.
Imagine you are staying in a massive hotel. Your friend Alice is in room 402. If you want to give Alice a gift, you have two options:
- Pass by Value: You clone Alice, bring the clone to your room, and give the clone the gift. The real Alice in room 402 never gets the gift.
- Pass by Reference (Pointers): You write "Room 402" on a piece of paper. This piece of paper is a pointer. It doesn't contain Alice; it contains the address of where Alice lives. By following the address on the paper, you walk to room 402 and hand the gift to the real Alice.
In C, variables are stored in computer memory. A pointer is simply a variable that stores the memory address of another variable. Instead of holding data like 42 or 'A', a pointer holds a location like 0x7ffee21b4a0.
Core Concept: First Principles
Every byte of memory in your computer has a unique numerical address, typically represented in hexadecimal format (e.g., 0x1A4B). When you declare a standard variable, the compiler reserves memory at a specific address and binds the variable name to it.
To work with pointers, you need two fundamental operators:
- The Address-of Operator (
&): Extracts the memory address of a variable. Ifxis a variable,&xis the address wherexlives. - The Dereference Operator (
*): When placed before a pointer variable, it acts as a portal. It travels to the address stored in the pointer and accesses the actual value stored there.
Pointer Declaration:
To declare a pointer, you must specify the type of data it points to, followed by an asterisk *.
int *p; means: p is a pointer that holds the address of an int.
Memory Diagram: Imagine memory as a series of mailboxes.
Variable: score p (the pointer)
+-------+ +--------------+
Value: | 95 | | 0x1004 | -----> Points to score!
+-------+ +--------------+
Address: 0x1004 0x2008
In code:
int score = 95;
int *p = &score;
Here, p equals 0x1004. *p (dereferencing) equals 95.
Beginner Code Example
Let's look at the basic syntax of creating, assigning, and dereferencing pointers.
#include <stdio.h>
int main() {
int score = 95;
// Declare a pointer to an integer and assign it the address of 'score'
int *p = &score;
// Print the value of the variable
printf("Value of score: %d\n", score);
// Print the memory address of 'score' (using %p format specifier)
printf("Address of score (&score): %p\n", (void*)&score);
// Print the value stored inside the pointer 'p'
printf("Value of p (should be same as address above): %p\n", (void*)p);
// Use the dereference operator (*) to access the value at the address
printf("Value pointed to by p (*p): %d\n", *p);
// We can change 'score' by dereferencing 'p'
*p = 100;
printf("\nAfter executing '*p = 100':\n");
printf("New value of score: %d\n", score); // Output will be 100
return 0;
}
Intermediate Code Example
The most common use of pointers is passing variables to functions so the function can modify the original variable. This is called Pass by Reference. C only supports Pass by Value natively, so we simulate Pass by Reference using pointers.
Let's write a function to swap two numbers.
#include <stdio.h>
// This function takes POINTERS to integers, not integers themselves.
void swap(int *a, int *b) {
int temp = *a; // Store the value at address 'a' in temp
*a = *b; // Copy the value at address 'b' into address 'a'
*b = temp; // Copy the temp value into address 'b'
}
int main() {
int x = 10;
int y = 20;
printf("Before swap: x = %d, y = %d\n", x, y);
// We pass the ADDRESSES of x and y using '&'
swap(&x, &y);
printf("After swap: x = %d, y = %d\n", x, y);
return 0;
}
If we had passed x and y normally (without pointers), swap would only swap its own local copies, and the original x and y in main would remain unchanged.
Advanced Code Example: Pointer Arithmetic and Arrays
In C, arrays and pointers are deeply connected. The name of an array acts as a constant pointer to its first element. Furthermore, you can perform math on pointers! If you add 1 to an int pointer, it doesn't add 1 byte to the address; it adds sizeof(int) bytes, moving exactly to the next integer in memory.
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
// 'arr' is equivalent to '&arr[0]'
int *ptr = arr;
printf("Array traversal using pointer arithmetic:\n");
for (int i = 0; i < 5; i++) {
// ptr + i calculates the address of the i-th element.
// *(ptr + i) dereferences that address to get the value.
printf("Address: %p | Value: %d\n", (void*)(ptr + i), *(ptr + i));
}
printf("\nPointer modification:\n");
printf("Current *ptr: %d\n", *ptr); // Prints 10
ptr++; // Moves pointer forward by one int (typically 4 bytes)
printf("After ptr++, *ptr: %d\n", *ptr); // Prints 20
return 0;
}
This demonstrates how arr[i] is internally translated by the C compiler into *(arr + i).
Common Mistakes
1. Uninitialized Pointers (Wild Pointers)
Declaring a pointer without giving it a valid address means it points to a random location in memory. Dereferencing it will likely crash the program (Segmentation Fault). Wrong:
int *p;
*p = 10; // CRASH! p is pointing to garbage memory.
Correct:
int x;
int *p = &x;
*p = 10; // Safe.
2. Confusing Pointer Type with Dereference
The asterisk * means two different things depending on context. In a declaration, it means "this is a pointer." In an expression, it means "dereference this pointer."
Wrong Mental Model:
int x = 5;
int *p = x; // Error: Trying to assign an int to a pointer.
Correct:
int x = 5;
int *p = &x; // Declaration: p is a pointer, initialized with address of x.
3. Pointer Type Mismatch
A pointer must match the type of the variable it points to. If you use a char pointer to read an int, you will only read 1 byte instead of 4, yielding incorrect data.
Wrong:
int num = 1024;
char *p = (char*)#
printf("%d", *p); // Will print 0 (the first byte of 1024 in little-endian), not 1024.
Interview Q&A
Q1: What is a Null Pointer?
A: A null pointer is a pointer that does not point to any valid memory location. In C, it is represented by the macro NULL. It is standard practice to initialize pointers to NULL if they don't yet have a valid address to prevent wild pointers.
Q2: What is a segmentation fault (segfault)?
A: A segmentation fault is a specific kind of error caused by accessing memory that "does not belong to you." It commonly happens when dereferencing an uninitialized pointer, a NULL pointer, or trying to write to read-only memory.
Q3: Explain void pointers.
A: A void * is a generic pointer. It can hold the address of any data type. However, because the compiler doesn't know the size of the data it points to, a void pointer cannot be dereferenced directly. It must first be cast to a specific pointer type (e.g., (int *)).
Q4: Can you subtract two pointers? A: Yes, if both point to elements within the same array. Subtracting them yields the number of elements (not bytes) between them. Adding two pointers, however, is illegal and meaningless.
Q5: What does const int *p mean versus int * const p?
A:
const int *p: Pointer to a constant integer. You cannot change the integer value through the pointer, but you can make the pointer point to a different address.int * const p: Constant pointer to an integer. You can change the integer value, but the pointer itself must always point to the same address.
Practice Problems
-
Find the Maximum: Write a function
void find_max(int *arr, int size, int *max_val)that finds the maximum value in an array and stores it in the variable pointed to bymax_val. -
String Length with Pointers: Write your own version of
strlen(char *str)using only pointer arithmetic. Start a pointer at the beginning of the string, increment it until you hit the null terminator\0, and return the difference between the final address and the start address. -
Reverse Array: Write a function that reverses an array in place using two pointers. One pointer starts at the beginning (
arr), the other at the end (arr + size - 1). Swap their values, increment the start pointer, decrement the end pointer, and repeat until they meet in the middle.
Summary
- A Pointer is a variable that stores a memory address.
- The
&operator gets the address of a variable. - The
*operator dereferences a pointer to access the data at that address. - Pointers are essential for modifying variables passed to functions (simulating pass by reference).
- Pointer arithmetic moves pointers based on the
sizeoftheir data type. - Arrays and pointers are closely related; array names act as constant pointers to their first element.
- Always initialize pointers (use
NULLif necessary) to avoid segmentation faults.