Appearance
Pointers & Free Store II
Video: Everything You Thought You Knew About Arrays is Wrong (in C Programming). This is Why.
Building on your understanding of pointers, let's explore how pointers work with arrays and the concept of pointer arithmetic.
Arrays and Pointers
In C++, arrays and pointers are closely related. When you declare an array, you're actually getting a pointer to the first element:
cpp
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr; // ptr points to the first element (1)
std::cout << *ptr << std::endl; // 1
std::cout << arr[0] << std::endl; // 1 (same thing!)Key insight: arr is a pointer to the first element, and arr[i] is equivalent to *(arr + i).
Array Decay to Pointers
When you pass an array to a function or use it in certain expressions, it automatically "decays" to a pointer to its first element:
cpp
void printArray(int* arr, int size) {
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
// Array decays to pointer when passed to function
printArray(arr, 5); // arr decays to int*
// Array decays to pointer in assignment
int* ptr = arr; // arr decays to int*
// Array decays to pointer in arithmetic
int* end = arr + 5; // arr decays to int*
}Why Does Array Decay Happen?
Array decay occurs because:
- Functions can't receive arrays by value - C++ would need to copy the entire array
- Arrays don't carry size information - the size is lost during decay
- Historical C compatibility - this behavior comes from C
Examples of Array Decay
cpp
int arr[5] = {1, 2, 3, 4, 5};
// 1. Function parameters
void func(int* ptr) { } // arr decays to int*
func(arr);
// 2. Assignment
int* ptr = arr; // arr decays to int*
// 3. Arithmetic expressions
int* end = arr + 5; // arr decays to int*
// 4. Comparison
if (arr == nullptr) { } // arr decays to int*
// 5. sizeof behavior changes
std::cout << sizeof(arr) << std::endl; // 20 (5 * 4 bytes)
std::cout << sizeof(ptr) << std::endl; // 8 (pointer size)The sizeof Trap
A common mistake is using sizeof on a decayed array:
cpp
void printSize(int* arr) {
// WRONG! This gives pointer size, not array size
std::cout << "Size: " << sizeof(arr) << std::endl; // Always 8 (or 4 on 32-bit)
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
std::cout << sizeof(arr) << std::endl; // 20 (correct - array size)
printSize(arr); // 8 (wrong - pointer size)
}Solution: Always pass the size as a separate parameter.
Preventing Array Decay
You can prevent array decay in some cases:
cpp
// 1. Using references (C++ only)
void func(int (&arr)[5]) {
// arr is still an array, not a pointer
std::cout << "Size: " << sizeof(arr) << std::endl; // 20
}
// 2. Using std::array (C++11)
#include <array>
std::array<int, 5> arr = {1, 2, 3, 4, 5};
// arr doesn't decay to pointer
// 3. Using templates
template<size_t N>
void func(int (&arr)[N]) {
// N is the array size
std::cout << "Size: " << N << std::endl;
}Practical Implications
Understanding array decay is crucial because:
- Function design: You must pass size separately
- Memory safety: Decayed arrays lose size information
- Performance: No copying occurs during decay
- Debugging: sizeof behavior can be confusing
Pointer Arithmetic
Pointer arithmetic allows you to move through memory by adding or subtracting integers from pointers:
cpp
int arr[5] = {10, 20, 30, 40, 50};
int* ptr = arr;
std::cout << *ptr << std::endl; // 10
std::cout << *(ptr + 1) << std::endl; // 20
std::cout << *(ptr + 2) << std::endl; // 30
std::cout << *(ptr - 1) << std::endl; // Undefined behavior!Important rules:
- Adding
nto a pointer moves itn * sizeof(type)bytes forward - Subtracting
nmoves it backward - You can only access memory within the array bounds
- Pointer arithmetic works with any pointer type
Array Access with Pointers
There are multiple ways to access array elements:
cpp
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr;
// All of these are equivalent:
std::cout << arr[2] << std::endl; // Array notation
std::cout << *(arr + 2) << std::endl; // Pointer arithmetic
std::cout << *(ptr + 2) << std::endl; // Pointer arithmetic with variable
std::cout << ptr[2] << std::endl; // Pointer with array notationDynamic Arrays with Pointers
You can create dynamic arrays using pointers:
cpp
int size = 5;
int* dynamicArr = new int[size];
// Initialize with values
for (int i = 0; i < size; ++i) {
dynamicArr[i] = i + 1;
}
// Access elements
for (int i = 0; i < size; ++i) {
std::cout << *(dynamicArr + i) << " ";
}
deletedynamicArr; // Don't forget to free memory!Pointer Arithmetic Operations
Addition and Subtraction
cpp
int arr[5] = {10, 20, 30, 40, 50};
int* ptr = arr;
ptr = ptr + 2; // Move 2 elements forward
std::cout << *ptr << std::endl; // 30
ptr = ptr - 1; // Move 1 element backward
std::cout << *ptr << std::endl; // 20Increment and Decrement
cpp
int arr[5] = {10, 20, 30, 40, 50};
int* ptr = arr;
std::cout << *ptr++ << std::endl; // Print 10, then move to 20
std::cout << *ptr << std::endl; // 20
std::cout << *++ptr << std::endl; // Move to 30, then print 30
std::cout << *ptr << std::endl; // 30Pointer Differences
You can subtract two pointers to get the distance between them:
cpp
int arr[5] = {10, 20, 30, 40, 50};
int* start = arr;
int* end = arr + 5;
std::cout << "Distance: " << (end - start) << std::endl; // 5
std::cout << "Elements between: " << (end - start) << std::endl; // 5Common Patterns
Iterating with Pointers
cpp
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr;
int* end = arr + 5;
while (ptr < end) {
std::cout << *ptr << " ";
++ptr;
}Finding Array Length
cpp
int arr= {1, 2, 3, 4, 5};
int* end = arr + sizeof(arr) / sizeof(arr[0]);
std::cout << "Array size: " << (end - arr) << std::endl; // 5Safety Considerations
Bounds Checking
Always ensure you don't access memory outside the array:
cpp
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr;
// Safe access
if (ptr + 2 < arr + 5) {
std::cout << *(ptr + 2) << std::endl; // Safe
}
// Dangerous access
std::cout << *(ptr + 10) << std::endl; // Undefined behavior!Null Pointer Checks
cpp
int* ptr = nullptr;
if (ptr != nullptr) {
std::cout << *ptr << std::endl; // Safe
} else {
std::cout << "Pointer is null" << std::endl;
}Your Tasks
Task 1: Array Reversal
Implement a function that reverses an array using pointer arithmetic. Use two pointers - one at the beginning and one at the end - and swap elements while moving them toward the center.
Task 2: Finding Maximum Element
Implement a function that finds the maximum element in an array using pointer arithmetic. Traverse the array using a pointer and keep track of the maximum value and its position.
Task 3: Array Copying
Implement a function that copies elements from one array to another using pointer arithmetic. Use two pointers to traverse both arrays simultaneously.
Key Takeaways
- Arrays decay to pointers to their first element
- Pointer arithmetic moves through memory by type size
arr[i]is equivalent to*(arr + i)- Always check bounds to avoid undefined behavior
- Pointer arithmetic is powerful but requires careful memory management
Implement a function that finds the maximum element in an array using pointer arithmetic.
cpp
void findMax(int* arr, int size, int& maxValue, int& maxIndex) {
// TODO: Implement function to find maximum element using pointer arithmetic
}