Definition
Pointer arithmetic is the process of performing addition, subtraction, increment, and decrement operations on memory addresses. Unlike integer arithmetic, pointer operations automatically scale by the size of the pointed-to data type, enabling safe and efficient traversal of contiguous memory blocks such as arrays, strings, and dynamically allocated buffers.
Core Mechanics & Scaling
When an integer n is added to or subtracted from a pointer ptr of type T*, the compiler calculates the actual byte offset as:
new_address = original_address + (n * sizeof(T))
This scaling ensures that pointer arithmetic operates in terms of logical elements rather than raw bytes.
Valid vs Invalid Operations
| Operation | Valid? | Result Type | Notes |
|---|---|---|---|
ptr + n / ptr - n | ✅ | T* | Moves n elements forward/backward |
ptr++ / ptr-- | ✅ | T* | Post/pre increment/decrement by one element |
ptr1 - ptr2 | ✅ | ptrdiff_t | Signed distance in elements. Both pointers must belong to the same array or one-past-the-end |
ptr1 == ptr2 | ✅ | int | Compares memory addresses |
ptr1 < ptr2 | ✅ | int | Valid only within same array/object |
ptr + ptr | ❌ | N/A | Undefined behavior |
ptr * n / ptr / n | ❌ | N/A | Undefined behavior |
void_ptr + n | ❌ | N/A | Standard C forbids arithmetic on void* (GCC/Clang allow as extension) |
Code Examples
#include <stdio.h>
#include <stddef.h>
int main(void) {
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // Points to arr[0]
// Addition and scaling
printf("%d\n", *(ptr + 2)); // 30 (advances 2 * sizeof(int) bytes)
// Iteration with increment
for (int *p = arr; p < arr + 5; p++) {
printf("%d ", *p);
}
printf("\n");
// Pointer subtraction (yields element count)
int *end = arr + 5;
ptrdiff_t count = end - arr; // 5
// One-past-the-end is valid for comparison but invalid for dereference
// *end; // ❌ Undefined behavior
return 0;
}
Rules & Constraints
- Array Boundary Rule: Pointers may legally point to any element within an array or to the one-past-the-end position. Dereferencing the one-past-the-end address invokes undefined behavior.
- Same Object Requirement: Pointer subtraction is only defined when both pointers reference elements of the same array (or one-past-the-end). Comparing pointers from unrelated objects yields unspecified results.
- Signed Result: Pointer subtraction returns
ptrdiff_t, a signed integer type capable of representing negative distances. Overflow in pointer subtraction invokes undefined behavior. - Contiguous Memory Only: Pointer arithmetic assumes sequential, homogeneous layout. It fails predictably on non-contiguous structures, linked nodes, or fragmented allocations.
- No Void Arithmetic: Standard C explicitly disallows arithmetic on
void*becausesizeof(void)is undefined. Cast tochar*or specific type first.
Best Practices
- Use
ptrdiff_tfor differences: Never store pointer subtraction results inintorlong.ptrdiff_tguarantees correct signed sizing across platforms. - Prefer one-past-the-end for bounds: Loop conditions like
p < arr + lenare standard, efficient, and idiomatic C. - Validate before dereference: Ensure pointers remain within allocated bounds before accessing
*ptr. - Prefer array indexing when possible:
arr[i]is often clearer than*(arr + i)and compiles to identical machine code. - Cast
void*explicitly: When working with generic memory buffers, cast tochar*oruint8_t*for byte-level arithmetic. - Document ownership and lifetime: Pointer arithmetic amplifies risks of use-after-free or buffer overflows. Track allocation boundaries rigorously.
Common Pitfalls
- 🔴 Off-by-one errors: Looping
p <= arr + leninstead ofp < arr + lendereferences one-past-the-end → undefined behavior. - 🔴 Cross-array subtraction:
ptr_a - ptr_bwhere pointers belong to different arrays invokes undefined behavior even if addresses are close. - 🔴 Signed overflow in distance: Large arrays can cause
ptrdiff_toverflow during subtraction, especially on 32-bit systems. - 🔴 Assuming
void*math works standardly:ptr + 1onvoid*compiles with GCC but fails on strict C compilers or changes behavior when migrating. - 🔴 Pointer decay confusion: Passing
&arr(typeint (*)[5]) vsarr(decays toint*) changes arithmetic scaling and breaks indexing. - 🔴 Ignoring alignment requirements: Arithmetic on misaligned pointers (e.g., casting
char*toint*and advancing by odd bytes) causes hardware traps on strict architectures.
Standards & Type Safety
- C89 through C23: Core pointer arithmetic rules remain stable. C23 strengthens pointer provenance rules and clarifies undefined behavior around out-of-bounds traversal.
<stddef.h>: Definesptrdiff_tandsize_t. Mandatory for portable pointer difference calculations.- Strict Aliasing: Pointer arithmetic cannot bypass type aliasing rules. Accessing
intdata through afloat*pointer (even with correct byte offset) violates strict aliasing and triggers undefined behavior. - Compiler Diagnostics: Enable
-Warray-bounds,-Wpointer-arith, and-Wallto catch scaling mistakes, out-of-bounds arithmetic, and invalid void pointer usage at compile time. - Static Analysis:
clang-tidy,cppcheck, andCoveritydetect unsafe pointer arithmetic patterns, potential overflows, and provenance violations before runtime.
C Preprocessor, Macros & Compilation Directives (Complete Guide)
https://macronepal.com/aws/mastering-c-variadic-macros-for-flexible-debugging/
Explains variadic macros in C, allowing functions/macros to accept a variable number of arguments for flexible logging and debugging.
https://macronepal.com/aws/mastering-the-stdc-macro-in-c/
Explains the __STDC__ macro, which indicates compliance with the C standard and helps ensure portability across compilers.
https://macronepal.com/aws/c-time-macro-mechanics-and-usage/
Explains the __TIME__ macro, which provides the compilation time of a program and is often used for logging and debugging.
https://macronepal.com/aws/understanding-the-c-date-macro/
Explains the __DATE__ macro, which inserts the compilation date into programs for tracking builds.
https://macronepal.com/aws/c-file-type/
Explains the __FILE__ macro, which represents the current file name during compilation and is useful for debugging.
https://macronepal.com/aws/mastering-c-line-macro-for-debugging-and-diagnostics/
Explains the __LINE__ macro, which provides the current line number in source code, helping in error tracing and diagnostics.
https://macronepal.com/aws/mastering-predefined-macros-in-c/
Explains all predefined macros in C, including their usage in debugging, portability, and compile-time information.
https://macronepal.com/aws/c-error-directive-mechanics-and-usage/
Explains the #error directive in C, used to generate compile-time errors intentionally for validation and debugging.
https://macronepal.com/aws/understanding-the-c-pragma-directive/
Explains the #pragma directive, which provides compiler-specific instructions for optimization and behavior control.
https://macronepal.com/aws/c-include-directive/
Explains the #include directive in C, used to include header files and enable code reuse and modular programming.
HTML Online Compiler
https://macronepal.com/free-html-online-code-compiler/
Python Online Compiler
https://macronepal.com/free-online-python-code-compiler/
Java Online Compiler
https://macronepal.com/free-online-java-code-compiler/
C Online Compiler
https://macronepal.com/free-online-c-code-compiler/
C Online Compiler (Version 2)
https://macronepal.com/free-online-c-code-compiler-2/
Node.js Online Compiler
https://macronepal.com/free-online-node-js-code-compiler/
JavaScript Online Compiler
https://macronepal.com/free-online-javascript-code-compiler/
Groovy Online Compiler
https://macronepal.com/free-online-groovy-code-compiler/
J Shell Online Compiler
https://macronepal.com/free-online-j-shell-code-compiler/
Haskell Online Compiler
https://macronepal.com/free-online-haskell-code-compiler/
Tcl Online Compiler
https://macronepal.com/free-online-tcl-code-compiler/
Lua Online Compiler
https://macronepal.com/free-online-lua-code-compiler/