C Pointers — Address-of Operator, Dereferencing, Pointer Types, and NULL
In this tutorial, you will learn about C Pointers. We cover key concepts, practical examples, and best practices to help you master this topic.
C pointers are variables that store memory addresses instead of values. The address-of operator (&) gets a variable's address, and the dereference operator (*) accesses the value at that address.
Why It Matters
Pointers are what make C different from most other languages. They give you direct access to memory, enabling dynamic memory allocation, efficient array processing, and data structures like linked lists and trees. A thorough understanding of pointers separates a competent C programmer from a beginner. Every systems programmer must master pointers.
Real-World Use
The Linux kernel uses pointers extensively for Process scheduling, memory management, and device drivers. Every FILE* you use for file I/O is a pointer to a struct. Function pointers enable callback mechanisms. Durga Antivirus Pro uses pointers to scan memory buffers efficiently.
What You Will Learn
- What pointers are and how they store addresses
- The & (address-of) and * (dereference) operators
- Pointer types and why type matters
- NULL pointers and how to use them safely
- Pointers to different data types
Learning Path
flowchart LR A[Strings] --> B[Pointers Basics
You are here] B --> C[Pointer Arithmetic] C --> D[Dynamic Memory] D --> E[Structs] style B fill:#f90,color:#fff
What Is a Pointer?
A pointer is a variable that holds a memory address. Instead of storing a value like 42, it stores the address where 42 lives in memory:
#include <stdio.h>
int main() {
int x = 42;
int *ptr = &x; // ptr stores the address of x
printf("Value of x: %d\n", x); // 42
printf("Address of x: %p\n", &x); // e.g., 0x7fff12345678
printf("Value of ptr: %p\n", ptr); // same address as &x
printf("Value at ptr: %d\n", *ptr); // 42 (dereference)
// Change x through the pointer
*ptr = 100;
printf("New value of x: %d\n", x); // 100
return 0;
}
Expected output (addresses will vary):
Value of x: 42
Address of x: 0x7fff12345678
Value of ptr: 0x7fff12345678
Value at ptr: 42
New value of x: 100
The Address-of Operator (&)
The & operator returns the memory address of a variable:
#include <stdio.h>
int main() {
int a = 10;
double b = 3.14;
char c = 'Z';
printf("Address of a: %p\n", &a);
printf("Address of b: %p\n", &b);
printf("Address of c: %p\n", &c);
// Addresses are typically 4 or 8 bytes apart
// depending on type sizes
return 0;
}
The Dereference Operator (*)
The * operator accesses the value stored at a pointer's address. In declarations, * indicates a pointer type. In expressions, * dereferences the pointer:
#include <stdio.h>
int main() {
int x = 42;
int *ptr; // Declaration: ptr is a pointer to int
ptr = &x; // ptr now holds the address of x
// Dereference: access the value at the address
int y = *ptr; // y gets the value 42
printf("x = %d, y = %d\n", x, y);
// Modify through pointer
*ptr = 99; // Changes x to 99
printf("x = %d\n", x);
return 0;
}
Expected output:
x = 42, y = 42
x = 99
Pointer Types
Every pointer has a type that determines what kind of data it points to:
#include <stdio.h>
int main() {
int i = 10;
double d = 3.14;
char c = 'A';
int *ip = &i; // Pointer to int
double *dp = &d; // Pointer to double
char *cp = &c; // Pointer to char
printf("int pointer: %p, value: %d\n", ip, *ip);
printf("double pointer: %p, value: %.2f\n", dp, *dp);
printf("char pointer: %p, value: %c\n", cp, *cp);
// Pointer size (same for all types on a given platform)
printf("Size of int*: %zu bytes\n", sizeof(int*));
printf("Size of double*: %zu bytes\n", sizeof(double*));
printf("Size of char*: %zu bytes\n", sizeof(char*));
// All pointer types have the same size
// because they all store addresses
return 0;
}
Expected output (addresses vary):
int pointer: 0x7fff12345670, value: 10
double pointer: 0x7fff12345678, value: 3.14
char pointer: 0x7fff12345680, value: A
Size of int*: 8 bytes
Size of double*: 8 bytes
Size of char*: 8 bytes
Even though the data types have different sizes (4, 8, 1 respectively), all pointers on a 64-bit system are 8 bytes because they all store addresses.
Why Pointer Types Matter
The type of a pointer matters for two reasons:
- Dereferencing: The compiler needs to know how many bytes to read when you dereference
- Pointer arithmetic: Adding 1 to an
int*moves 4 bytes; adding 1 to achar*moves 1 byte
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *ip = arr;
char *cp = (char*)arr;
printf("int pointer: %p -> %d\n", ip, *ip);
printf("char pointer: %p -> %d\n", cp, *cp);
ip++; // Moves 4 bytes forward
cp++; // Moves 1 byte forward
printf("After increment:\n");
printf("int pointer: %p -> %d\n", ip, *ip);
printf("char pointer: %p -> %d\n", cp, *cp);
return 0;
}
NULL Pointers
A NULL pointer points to nothing. It is used to indicate that a pointer does not currently hold a valid address:
#include <stdio.h>
int main() {
int *ptr = NULL; // Pointer points to nothing
// Always check for NULL before dereferencing
if (ptr != NULL) {
printf("Value: %d\n", *ptr);
} else {
printf("Pointer is NULL, cannot dereference.\n");
}
// Dereferencing NULL causes a crash (segmentation fault)
// *ptr = 42; // CRASH!
// NULL is defined as ((void*)0) in standard libraries
printf("NULL value: %d\n", NULL == 0); // 1 (true)
return 0;
}
When to Set Pointers to NULL
- After declaring a pointer that is not immediately assigned
- After freeing memory (to prevent dangling pointer)
- Before checking if a pointer is valid
The void* Pointer
void* is a generic pointer that can point to any data type. It cannot be dereferenced directly:
#include <stdio.h>
int main() {
int x = 42;
double y = 3.14;
void *vp = &x; // void* can hold any address
printf("void* to int: %p\n", vp);
// Cannot dereference void* directly:
// printf("%d", *vp); // ERROR!
// Must cast back to the correct type
printf("Value: %d\n", *(int*)vp);
vp = &y; // Now points to double
printf("Value: %.2f\n", *(double*)vp);
return 0;
}
void* is used in standard library functions like malloc(), qsort(), and memcpy() because they work with any data type.
Pointers and Function Arguments
Pointers allow functions to modify variables outside their scope:
#include <stdio.h>
// Pass by value -- does NOT modify the original
void increment_bad(int x) {
x++;
}
// Pass by pointer -- modifies the original
void increment(int *x) {
(*x)++;
}
int main() {
int value = 10;
increment_bad(value);
printf("After bad increment: %d\n", value); // 10
increment(&value);
printf("After pointer increment: %d\n", value); // 11
return 0;
}
Expected output:
After bad increment: 10
After pointer increment: 11
Common Mistakes
1. Dereferencing Uninitialized Pointers
int *ptr;
*ptr = 42; // Dereferencing garbage address -- crash!
Always initialize pointers to NULL or a valid address.
2. Forgetting the & When Passing to scanf
int x;
scanf("%d", x); // Wrong: x is not a pointer
scanf("%d", &x); // Correct: &x is the address of x
3. Confusing Declaration with Dereference
int *p; // Declaration: p is a pointer to int
*p = 42; // Dereference: store 42 at the address in p (uninitialized!)
In declarations, * indicates pointer type. In expressions, * dereferences.
4. Using NULL Pointers
int *p = NULL;
*p = 42; // Segmentation fault
Always check for NULL before dereferencing.
5. Confusing Pointer with Pointee Type
int *p;
int *q;
p = q; // Copies the address, not the value
*p = *q; // Copies the value at the address
Practice Questions
What does
&do in C? It returns the memory address of a variable.What does
*do when used in front of a pointer? It dereferences the pointer, accessing the value at the stored address.What is a NULL pointer? A pointer that points to nothing (address 0). It is used to indicate an invalid or uninitialized pointer.
Why do all pointer types have the same size? Because they all store memory addresses, which have a fixed size (4 bytes on 32-bit, 8 bytes on 64-bit).
Challenge: Write a program that swaps two integers using pointers (a swap function).
Mini Project: Swap Function
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
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);
return 0;
}
FAQ
What is Next
Now that you understand pointer basics, proceed to Pointer Arithmetic to learn how to navigate arrays using pointers.