C Pointer Arithmetic, Arrays, String Iteration & const Qualifiers
Welcome to Phase 9 (Chapter 22): C Pointer Arithmetic, Arrays, String Traversal & const Qualifiers Masterclass! Once you understand that pointers hold memory addresses, the real engineering power comes from performing mathematical arithmetic directly on those addresses. Unlike standard integer math where 100 + 1 = 101, Pointer Arithmetic is automatically scaled by the size of the underlying data type in physical RAM. In this exhaustive textbook-grade guide, you will master the Pointer Scaling Rule, explore the deep architectural equivalence between arrays and pointers, learn how pointers traverse strings at lightning speed, master the 3 degrees of const pointer qualifiers, and understand generic void* pointers.
When you add an integer $N$ to a pointer (ptr + N), the CPU does NOT add $N$ raw bytes.
Instead, it adds $N \times \text{sizeof(*ptr)}$ Bytes:
๐ The Core Pointer Arithmetic Formula:
$$\text{New Address} = \text{Current Address} + (N \times \text{sizeof(Type)})$$
โข For char* (1 Byte): 0x1000 + 1 $\rightarrow$ 0x1001 (jumps 1 byte).
โข For int* (4 Bytes): 0x1000 + 1 $\rightarrow$ 0x1004 (jumps 4 bytes).
โข For double* (8 Bytes): 0x1000 + 1 $\rightarrow$ 0x1008 (jumps 8 bytes)!
๐ก Pointer Subtraction: Subtracting two pointers of the same type (ptr2 - ptr1) yields the Exact number of elements between them (type ptrdiff_t), NOT the raw byte count!
p: 0x5000 (Points to arr[0] = 10)
p + 1: 0x5000 + (1 * 4) = 0x5004 (Points to arr[1] = 20)
p + 2: 0x5000 + (2 * 4) = 0x5008 (Points to arr[2] = 30)
*(p + 1) dereferences value at 0x5004 -> yields 20!
In C, array bracket notation is purely syntactic sugar for pointer arithmetic! Under the hood:
๐ The Universal Array-Pointer Identity
$$\mathbf{arr[i] \equiv *(arr + i) \equiv *(i + arr) \equiv i[arr]}$$
Because addition is commutative ($a + b = b + a$), in C writing 3[arr] is 100% valid syntax and produces the exact same result as arr[3]!
#include <stdio.h>
// Lightning-fast string length using pointer subtraction
size_t fastStrLen(const char *s) {
const char *p = s;
while (*p) p++; // Advances pointer until null terminator ' '
return p - s; // Pointer subtraction yields character count!
}
int main(void) {
int numbers[] = {10, 20, 30, 40, 50};
int *ptr = numbers;
printf("First element via *ptr: %d\n", *ptr);
printf("Third element via *(ptr+2): %d\n", *(ptr + 2));
const char message[] = "Dennis Ritchie";
printf("Length of '%s' = %zu chars\n", message, fastStrLen(message));
return 0;
}
Placing the const keyword relative to the asterisk * creates 3 fundamentally different memory safety rules:
| Declaration Syntax | What is Constant? | Can Modify Value (*ptr = x)? | Can Redirect Pointer (ptr = &y)? |
|---|---|---|---|
const int* ptr; |
Data Pointed To | โ NO (Read-Only Data) | โ YES |
int* const ptr; |
Pointer Address Itself | โ YES | โ NO (Locked Address) |
const int* const ptr; |
Both Data and Address | โ NO | โ NO (Completely Locked) |
๐ก The Clockwise/Spiral Reading Rule:
โข const int *p $
ightarrow$ Read right-to-left: "p is a pointer to an int that is const".
โข int * const p $
ightarrow$ "p is a const pointer to an int".
A void* pointer (Generic Pointer) can hold the memory address of any data type (int, float, struct, array) without explicit type casting.
It powers generic system functions like malloc(), memcpy(), and qsort().
๐ The Two Rules of void* Pointers:
1. Cannot Dereference Directly: *voidPtr is a compile error because the compiler does not know whether to fetch 1, 4, or 8 bytes!
2. Cannot Perform Pointer Arithmetic: voidPtr + 1 is undefined in ISO C (though GCC allows it as an extension treating it as 1 byte).
โ
Solution: Always cast to a concrete type first: *(int*)voidPtr.
Q1: Why is *p++ different from (*p)++?
Postfix ++ has higher precedence than dereference *. *p++ yields the current value and then advances the pointer to the next memory address. (*p)++ increments the value stored at the current memory address without moving the pointer.
Q2: Can we add two pointers together (ptr1 + ptr2)?
No! Adding two memory addresses is mathematically meaningless in computer architecture and is strictly illegal in C. You can only subtract two pointers (to find the distance between them) or add an integer offset to a pointer.
Run this generic byte inspection demo in our live GCC compiler:
#include <stdio.h>
void printHexBytes(const void *ptr, size_t numBytes) {
const unsigned char *bytePtr = (const unsigned char*)ptr;
for (size_t i = 0; i < numBytes; i++) {
printf("0x%02X ", bytePtr[i]);
}
printf("\n");
}
int main(void) {
int val = 0x12345678;
printf("Raw RAM bytes of 0x12345678 (Little Endian):\n");
printHexBytes(&val, sizeof(val));
return 0;
}