int nums[4] = {
10, 20, 30, 40
}
;
int *p = nums;
// points at nums[0]Pointer Arithmetic in C: Moving Through Memory With Pointers
What is Pointer Arithmetic in C? Core Concept & Direct Summary
How do pointer addition (`ptr + n`), subtraction (`ptr - n`), and difference (`ptr1 - ptr2`) scale automatically by `sizeof(type)`?
Adding one to a variable normally adds one. But add one to a pointer, and something stranger happens: the address jumps forward by four bytes, or eight, or twenty-four — depending on what the pointer points at.
That's pointer arithmetic, and it's the reason C's arrays work the way they do. It's also the reason arr[i] and *(arr + i) are the same thing, why loops can be written with pointers instead of indexes, and why the compiler needs to know the pointed-to type at all.
This page covers the mechanics: what +, -, ++, -- actually do to pointers, how to subtract one pointer from another, what comparisons are legal, the one-past-the-end rule, and the operations C simply forbids. Then we'll use it to write some loops that read like the low-level C of decades past — which is still, in some contexts, the clearest way to express an algorithm.
(Suggested image: the hero graphic — an int array of four cells with pointers p, p+1, p+2, p+3 above them, and the byte offsets 0, 4, 8, 12 marked below. The "type scales the step" idea in one image.)
How Does Pointer Arithmetic in C Work? In-Depth Mechanics & Architecture
The Core Rule: Arithmetic Scales by Type Size
Now:
The pointer moves by whole elements, not by bytes. That's because the compiler knows the type:
The actual address arithmetic is:new_address = old_address + (n × sizeof(*pointer)).
This is why pointer types matter, even though all pointers are the same size themselves. The type is what tells the compiler how big a "step" is. char * walks byte by byte; int * walks four at a time; a struct pointer strides by the whole struct's size.
(Suggested image: three parallel arrays in the diagram — char pointer stepping 1 byte, int stepping 4, double stepping 8 — the type-scaling rule made visual)
The Five Legal Operations
C permits exactly these, and nothing else:
And comparisons (<, >, ==, !=) between pointers into the same array are also legal — we'll cover those separately since they behave differently from the arithmetic itself.
What is NOT legal:
Adding addresses is meaningless — you'd be adding two locations together. So C forbids it, and the compiler will tell you.
Increment and Decrement: The Walking Pattern
Sample output:
Look at the address increments: 4 bytes for each int step, never 1. That's the type scaling in action.
Choosing an idiomatic shorthand:p += 2 moves forward two elements — it is not "add 2 bytes." This is where the intuition clicks for most people: p += n means "skip n elements."
(Suggested image: the array with a pointer marker stepping across it, addresses below changing by 4 each time)
The Famous Equivalence: arr[i] ≡ *(arr + i)
This is the payoff moment. The C standard defines array indexing in terms of pointer arithmetic:
Not "similar to" — defined as equivalent. The compiler translates the first into the second.
That last line is real: since arr[i] is *(arr + i) and addition is commutative, 2[nums] is valid C that does exactly what nums[2] does. Nobody writes it, but every C programmer should see it once — it proves the equivalence isn't a teaching device, it's literally the mechanism.
Two practical consequences:
- You can use either style freely. Index-based loops and pointer-based loops produce identical machine code with optimization on. Choose by readability.
- Everything makes sense at last. Passing arrays to functions (decay = passing
&arr[0]),sizeoffailing on parameters,fgets(buf, ...)filling a buffer,scanf("%d", &x)storing through a pointer — all the same mechanism.
(Suggested image: the equivalence shown as an equation over the memory diagram — nums[2] on the left transforming into *(nums + 2) on the right with the same cell highlighted. This is the page's key teaching graphic.)
Traversal: Index Loops vs Pointer Loops
Same task, two styles:
Both print 10 20 30 40 50 and compile to essentially the same code. The pointer version has one detail worth understanding: nums + 5 is legal to compute and use as a bound, even though it points one past the array. You just must never dereference it.
The one-past-the-end rule: you may form a pointer to one element beyond an array (and compare against it, and use it as a stopping boundary), but you may not read or write there:
This rule exists precisely to make the p < end loop above well-defined. It's a small, slightly odd corner of the standard, and knowing it marks you as someone who's actually read the rules.
(Suggested image: the array with a dashed "one past the end" box marked legal-as-a-boundary but not-dereferenceable)
Pointer Subtraction: Measuring Distance
Subtract one pointer from another and you get the number of elements between them:
Output:
Note the units again: fifth - first is 4, not 16, even though the addresses differ by 16 bytes. Pointer subtraction divides by the element size — it reports distances in elements, which is what you actually want.
This is the standard way to turn a pointer back into an index. You saw it in the string functions tutorial:
Search functions return pointers because that's the natural way to walk memory; subtraction converts back to a position when you need one. (%ld or %td for the difference — technically ptrdiff_t.)
One critical restriction: both pointers must point into the same array (or one past its end). Subtracting pointers into unrelated objects is undefined — the result is meaningless.
Comparisons: What's Legal
Comparisons (<, >, <=, >=, ==, !=) are meaningful between pointers into the same array — earlier elements compare "less than" later ones. That's how p < end loops work.
Comparing pointers into different objects is undefined by the standard (in practice you sometimes get the numeric comparison, but don't rely on it). Comparing against NULL is always fine — if (p != NULL) is universal.
A Complete Example: Rewriting String Functions With Pointers
The best way to internalize pointer arithmetic is to reimplement library functions using it. Here are strlen and strcpy written in the classic pointer style:
Sample Run:
Several techniques packed in here:
myStrlensaves the start pointer, walks to the terminator, then subtracts to get the length. That's pointer subtraction used exactly as intended.myStrcpyreturns the originaldest— because you destroyed your pointer by walking it. Saving the start before advancing is the essential habit with pointer loops.while ((*dest++ = *src++));— the compact copy idiom. Copy the character, advance both pointers, and stop when the copied value is'\0'(which is falsy). It looks cryptic the first time and becomes perfectly readable once you can parse*dest++as "dereference dest, then advance dest." Worth noting: some compilers warn about assignments in conditions; the extra parentheses are what suppress that.- The in-place reverse with two pointers meeting in the middle —
*left++ = *rightassigns and steps in one expression. This pattern (two pointers converging) is a genuinely common algorithm technique.
(Suggested image: the reverse operation step by step — left and right pointers moving toward each other, characters swapping, with the *left++ expansion labeled)
The Precedence Trap: *p++ vs (*p)++ vs ++*p
These three look nearly identical and do three different things. This is a favorite interview question for a reason:
The key: postfix ++ binds tighter than *, so *p++ means *(p++). To increment the pointed-to value instead, you need the parentheses: (*p)++.
When in doubt, add parentheses. Explicit beats clever, and this is exactly the kind of expression where an extra pair clarifies for both you and the next reader.
(Suggested image: a small table graphic showing the four expressions with arrows indicating what moves — the pointer or the value)
Arithmetic on Typed Arrays: When Types Matter Most
Pointer arithmetic on different types, side by side:
You can verify this yourself — the printed addresses will differ by exactly sizeof(element) each time. It's a good exercise for building intuition: the same + 1, three different distances.
Common Pointer Arithmetic Mistakes
- Assuming
p + 1adds one byte — it adds one element (4 bytes for int, 8 for double). The address numbers show it clearly. - Losing the base pointer — after
p++repeatedly, you can no longerfreeor return to the start. Savechar *start = p;orint *base = arr;before walking. - Dereferencing one-past-the-end —
*(arr + n)is undefined behavior. Compute it as a bound, never dereference it. - Adding two pointers — meaningless, and a compile error. Only add integers to pointers, subtract pointers from pointers.
- Subtracting pointers from different arrays — undefined. The result is only meaningful within one array.
- The
*p++precedence trap —*p++advances the pointer;(*p)++modifies the value. Parenthesize when it matters. - Walking off the end in pointer loops — the
p < endform withend = arr + nis safest. Avoidwhile (*p)style loops on non-string data — they only stop at a zero value, which may be data rather than a terminator. - Ignoring compiler warnings — pointer type mismatches produce warnings for a reason; they signal real size errors. Compile with
-Wall -Wextra. - Mixing up pointer and value in function calls —
swap(*a, *b)vsswap(a, b). (From the pointers pillar page.)
(Suggested image: "9 Pointer Arithmetic Mistakes" checklist — with the p+1 scaling and the one-past-the-end rule as the two emphasized items)
Final Thoughts
Pointer arithmetic is where C's memory model becomes tangible. One rule covers nearly everything: arithmetic scales by the pointed-to type, so p + 1 means "the next element," not "the next byte." From that single fact, the arr[i] ≡ *(arr + i) equivalence follows, and the equivalence explains arrays, function parameters, sizeof behavior, and half the standard library.
The habits worth keeping: save the base pointer before walking, use arr + n as a one-past-the-end bound and never dereference it, subtract same-array pointers to get distances in elements, and parenthesize when mixing * with ++. Those four habits prevent essentially every pointer-arithmetic bug I've listed.
Honest note to close on: modern, readable C usually prefers indexing (arr[i]) over pointer walking, because it's clearer and optimizes identically. The value in learning pointer arithmetic isn't so you'll write *dest++ = *src++ everywhere — it's that you'll be able to read it when other people do, and understand what the machine is actually doing underneath.
Next in this cluster: pointers and arrays in depth — including the two-dimensional case, where pointer arithmetic gets genuinely interesting.
Runnable C Code Example & Practical Implementation
#include <stdio.h>
int main() {
int arr[] = {
10, 20, 30, 40, 50
};
int *ptr = arr;
// Points to arr[0]
printf("Initial value (*ptr): %d (Address: %p)\
", *ptr,(void*)ptr);
ptr++;
// Advances by 4 bytes
printf("After ptr++ (*ptr): %d (Address: %p)\
", *ptr,(void*)ptr);
ptr += 2;
// Advances by 8 bytes
printf("After ptr+=2 (*ptr): %d (Address: %p)\
", *ptr,(void*)ptr);
return 0;
}Key Takeaways, Best Practices & Memory Safety Rules
Frequently Asked Questions & Technical Interview Insights
Technical Interview & Viva Questions
Frequently asked C programming interview questionsQ: If `int *p = (int*)0x1000;`, what is the address value of `p + 2` assuming 4-byte integers?
Q: Can you perform pointer arithmetic on a `void*` pointer in standard ISO C?
Related Tools, Tutorials & Authoritative References
Recommended Interactive Tools
- Online C Compiler & Runner — Interactive online GCC sandbox to execute C code in real time.
- Online C Code Editor & IDE — Multi-file C programming environment with code formatting.
- C++ Programming Tutorial — Transition from procedural C to Object-Oriented C++ with STL.
- JavaScript Programming Tutorial — Master modern JavaScript fundamentals, DOM APIs, and async patterns.
Authoritative Standards & Citations
- ISO/IEC 9899 (C Language Standard) — Official international C specification by ISO/IEC JTC 1/SC 22/WG 14.
- GNU GCC Documentation — Official reference manual for the GNU Compiler Collection and C libraries.
- The C Programming Language (K&R) — Definitive classic work by Brian Kernighan & Dennis Ritchie at Bell Labs.
- C Reference Documentation (cppreference.com) — Verified reference covering C89 through C23 language features.