C++: Advanced Pointer Arithmetic
Last updated: 2026-08-26
In Lesson 23, we learned the basics of pointer arithmetic (
p + n,p - q).But there are many more details to pointer arithmetic — for example, can pointers be compared? What is a
void*pointer? What are pointers to pointers?
1. Pointer Comparison
(1) 1.1 Can Pointers Be Compared?
Yes! But only comparisons between pointers pointing to the same array are meaningful.
▶ Example 2: Basic Programming Practice (Difficulty ⭐)
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p1 = &arr[0];
int* p2 = &arr[3];
if (p1 < p2) { // ✅ p1 pointed element is before p2
std::cout << "p1 is before p2" << std::endl;
}
if (p2 - p1 == 3) { // ✅ two pointers are 3 elements apart
std::cout << "p1 and p2 are 3 elements apart" << std::endl;
}
return 0;
}
Output:
p1 is before p2
p1 and p2 are 3 elements apart
💡 Key point: Pointer comparisons compare address values (i.e., element positions in the array).
(2) 1.2 Pointer Equality Comparison
#include <iostream>
int main() {
int x = 5;
int* p1 = &x;
int* p2 = &x;
if (p1 == p2) { // ✅ two pointers point to the same variable
std::cout << "p1 and p2 point to the same variable" << std::endl;
}
int y = 10;
int* p3 = &x;
int* p4 = &y;
if (p3 != p4) { // ✅ two pointers point to different variables
std::cout << "p3 and p4 point to different variables" << std::endl;
}
return 0;
}
2. void* Pointers
(1) 2.1 What is a void* Pointer?
void* is a generic pointer — it can point to any type of data.
#include <iostream>
int main() {
int x = 5;
double y = 3.14;
char c = 'A';
void* p1 = &x; // ✅ void* can point to int
void* p2 = &y; // ✅ void* can point to double
void* p3 = &c; // ✅ void* can point to char
return 0;
}
💡 Characteristics:
void*cannot be dereferenced (the compiler doesn't know how many bytes to read)void*cannot do pointer arithmetic (the step size is unknown)
(2) 2.2 Uses of void*
Use 1: Writing generic functions (later you'll learn that std::memcpy uses void*)
#include <iostream>
#include <cstring>
int main() {
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
// std::memcpy Use void* to accept any type'spointer
std::memcpy(arr2, arr1, 5 * sizeof(int));
for (int i = 0; i < 5; i++) {
std::cout << arr2[i] << " ";
}
std::cout << std::endl;
return 0;
}
Use 2: Dynamic memory allocation (later you'll learn that malloc returns void*)
(3) 2.3 Converting void*
To use the value pointed to by void*, you must convert it to a pointer of a concrete type.
#include <iostream>
int main() {
int x = 5;
void* p = &x; // void* pointing to int
// ❌ Error: Cannot directly dereference void*
// std::cout << *p << std::endl;
// ✅ Correct: First convert to int*
int* intPtr = static_cast<int*>(p);
std::cout << *intPtr << std::endl; // Output 5
return 0;
}
💡 Tip: static_cast is C++'s type conversion operator (covered later), which is safer than C-style (int*)p.
3. Multi-Level Pointers (Pointers to Pointers)
(1) 3.1 What is a Pointer to a Pointer?
A pointer to a pointer is a pointer that stores the address of another pointer (nesting dolls).
#include <iostream>
int main() {
int x = 5;
int* p = &x; // p points to x
int** pp = &p; // pp points to p (pointer to pointer)
std::cout << "x = " << x << std::endl;
std::cout << "*p = " << *p << std::endl; // Dereference once: get x value
std::cout << "**pp = " << **pp << std::endl; // Dereference twice: get x value
return 0;
}
(2) 3.2 Declaring Multi-Level Pointers
| Declaration | Meaning |
|---|---|
int x; |
x is an int |
int* p; |
p is an int* (pointer to int) |
int** pp; |
pp is an int** (pointer to int*) |
int*** ppp; |
ppp is an int*** (pointer to int**) |
💡 How to read: Read from right to left.
int* p;→pis a pointer tointint** pp;→ppis a pointer to pointer toint
(3) 3.3 Uses of Multi-Level Pointers
Use 1: Dynamic 2D arrays (covered later)
// Dynamically allocate a 3x4 2D array
int** matrix = new int*[3]; // matrix is a pointer to pointer
for (int i = 0; i < 3; i++) {
matrix[i] = new int[4];
}
Use 2: Functions that need to modify a pointer (rare, just be aware)
#include <iostream>
void allocateInt(int** ptr) {
*ptr = new int(5); // Modify *ptr (i.e., modify the original pointer)
}
int main() {
int* p = nullptr;
allocateInt(&p); // Pass the pointer address
std::cout << *p << std::endl; // Output 5
delete p; // Free memory
return 0;
}
4. Pointers and const Combinations
(1) 4.1 Four Combinations
| Declaration | Meaning | Can change pointer? | Can change pointed-to value? |
|---|---|---|---|
int* p; |
Ordinary pointer | ✅ Yes | ✅ Yes |
const int* p; |
Pointer to const int | ✅ Yes | ❌ No |
int* const p = &x; |
Const pointer (must initialize) | ❌ No | ✅ Yes |
const int* const p = &x; |
Const pointer to const | ❌ No | ❌ No |
💡 Memory trick: Read from right to left.
const int* p;→pis a pointer toconst intint* const p = &x;→pis aconstpointer toint
(2) 4.2 Example: const int* (Commonly Used)
#include <iostream>
void printArray(const int* arr, int size) {
for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// ❌ Cannot modify:arr[i] = 0; // Compile error
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
printArray(arr, 5);
return 0;
}
Output:
1 2 3 4 5
💡 Key point: const int* arr is the most commonly used function parameter type — it means "I only read, I don't modify."
5. Practice: Implementing String Copy with Pointers
▶ Example 1: Hand-written strcpy (Difficulty ⭐⭐)
#include <iostream>
void myStrcpy(char* dest, const char* src) {
while (*src != '\0') {
*dest = *src; // Copy character
dest++; // Move pointer
src++;
}
*dest = '\0'; // Add null terminator
}
int main() {
char src[] = "Hello";
char dest[20];
myStrcpy(dest, src);
std::cout << "src = " << src << std::endl;
std::cout << "dest = " << dest << std::endl;
return 0;
}
Output:
src = Hello
dest = Hello
💡 Tip: This program uses pointer arithmetic and dereferencing — it's excellent practice for understanding pointers.
▶ Example 3: Traversing an Array with Pointers (Difficulty ⭐)
#include <iostream>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int* p = arr; // p points to first array element
std::cout << "Traversing array with pointers: " << std::endl;
for (int i = 0; i < 5; i++) {
std::cout << "*p = " << *p << std::endl;
p++; // Move pointer to next element
}
// pointer subtraction calculates number of elements
int* end = arr + 5;
std::cout << "Number of elements:" << end - arr << std::endl;
return 0;
}
Output:
Traversing array with pointers:
*p = 10
*p = 20
*p = 30
*p = 40
*p = 50
Number of elements:5
❓ FAQ
Q: Why can't void be dereferenced?* A: Because
voidis a type with no size — the compiler doesn't know whether to read 1 byte,4bytes, or8bytes.It must be converted to a pointer of a concrete type (like
int*,double*) before the compiler knows the step size.
Q: What are multi-level pointers used for? A: In real projects,
int**is most commonly seen in: > 1. Dynamic 2D arrays (e.g.,new int*[3]) > 2. Functions that need to modify the pointer itself (e.g.,void foo(int** p)can modify whereppoints)Pointers beyond two levels (
int***etc.) are not recommended — too hard to understand.
Q: Are const int and int const the same? A: Yes! The following two forms are equivalent: > > const int p; // Recommended > int const p; // equivalent, but less common > > > But
int* const pis different — this is aconstpointer (cannot change what it points to).
📖 Summary
- Pointers can be compared (only meaningful when pointing to the same array)
void*is a generic pointer that cannot be dereferenced or used in pointer arithmetic- Multi-level pointers are pointers to pointers (
int**) - Pointers + const have four combinations; the most commonly used is
const int*(read-only) - Implementing string copy with pointers is excellent practice for understanding pointers
📝 Exercises
-
Basic (Difficulty ⭐): Declare an
intvariablex = 5, then declare anint*pointerppointing tox, then declare anint**pointerpppointing top. Outputx,*p,**ppand verify they are equal. -
Intermediate (Difficulty ⭐⭐): Write a function
void swap(int** a, int** b)that swaps what two pointers point to.
Input: p1 points to x, p2 points to y
Call swap(&p1, &p2)
Output: p1 points to y, p2 points to x
- Challenge (Difficulty ⭐⭐⭐):
Use
void*and pointer arithmetic to hand-write amyMemcpyfunction: - Function prototype:
void myMemcpy(void* dest, const void* src, size_t n) - Functionality: copy
nbytes fromsrctodest - Hint: copy byte by byte (use
char*for pointer arithmetic)
- Pointer addition/subtraction with integers: offset by the pointed-to type's size
- Pointer subtraction: gives the difference in element count
- Pointer comparison: can be used to determine ordering
- void* pointer: no type, cannot be dereferenced or used in arithmetic
- Function pointers: store the entry address of a function
10. 🚀 Next Step
Now that you've learned advanced pointer concepts, let's move on to References (Lesson 25) — the "safer version" of pointers, an important C++ feature!