Hello! Welcome to your second lesson in the "Pointers, const, and Core Operations" module.
In our last session, we established the fundamental principle of pointers: a pointer's value is a memory address, and the dereference operator (*) retrieves the value stored at that address. This distinction is crucial for everything that follows.
Today, we build directly on that foundation to address a core interview topic: pointer arithmetic. You'll learn how to perform arithmetic operations (increment, decrement, subtraction) on pointers and, most importantly, how to predict the exact results based on the pointer's type. This skill is essential for writing efficient and correct code for array and buffer manipulation, a common task in embedded systems and driver development.
Our learning outcome is to perform pointer arithmetic (increment, decrement, subtraction) on different pointer types and predict results based on type size.
1. The Cardinal Rule of Pointer Arithmetic
When you add an integer to a non-pointer variable, the operation is straightforward: 1000 + 1 is 1001. With pointers, the C compiler is much smarter. Pointer arithmetic isn't about bytes; it's about elements.
The cardinal rule is: When you add an integer n to a pointer p, the resulting address is not p + n. It is:
This automatic scaling is what allows p++ to correctly move a pointer to the next element in an array, regardless of whether it's an array of chars, ints, or large structs.
This image provides a great visual breakdown.

2. The Impact of Data Type
Let's see this rule in action. The difference between ptr++ for a character pointer and an integer pointer is a classic interview question.
30 C Programming Pointers Coding Exercises with Solutions
The following resource provides a hands-on exercise that directly contrasts the behavior of incrementing an integer pointer versus a character pointer.
Please read 'Exercise 30: Pointer Arithmetic Boundary'. Focus on the code and the explanation. The key takeaway is observing the difference in the address change when i_ptr++ is executed versus c_ptr++.
As you saw, i_ptr++ moved the address by sizeof(int) (typically 4 bytes), while c_ptr++ moved it by sizeof(char) (1 byte).
What about structs?
This rule applies to any data type, including user-defined structs. If you have a pointer to a struct, p_struct++, the address will be incremented by sizeof(struct MyStruct).
Pointer arithmetic for structs
This Stack Overflow discussion explains how pointer arithmetic applies to structs. It's a good confirmation of the core rule.
Read the first two answers. The first one confirms that p++ on a struct pointer advances the address by sizeof(struct). The second answer introduces an important related concept: memory padding. We will cover padding in detail in a later module, but it's important to know that sizeof(struct) is not always just the sum of its members.
3. Pointer Operations and Their Uses
Let's explore the common arithmetic operations.
A. Increment (++) and Decrement (--)
These are most frequently used to iterate through arrays. Let's look at a practical example of reversing an array in-place using two pointers.
30 C Programming Pointers Coding Exercises with Solutions
This exercise is a classic problem that demonstrates a powerful use of pointer increment and decrement to manipulate data within an array.
Review 'Exercise 10: Reverse an Array'. Analyze the reverse_array function. Note how start++ and end-- are used to move the pointers towards the middle of the array, and how the while (start < end) condition works by comparing addresses.
B. Pointer and Integer Addition/Subtraction
This is used to access an arbitrary element in an array. The array subscript notation arr[i] is syntactic sugar for *(arr + i). Understanding this equivalence is vital.
30 C Programming Pointers Coding Exercises with Solutions
This exercise explicitly shows the different but equivalent ways to access an array element.
Read 'Exercise 7: Accessing Array Elements'. The code demonstrates that data[2], *(data + 2), and *(ptr + 2) all yield the same result. Make sure you understand why.
C. Pointer Subtraction
When you subtract one pointer from another (of the same type), the result is not the difference in bytes. Instead, it is the number of elements between the two pointers. The result's type is ptrdiff_t, a signed integer type defined in <stddef.h>.
This is useful for calculating the number of elements in a range.
Consider this code snippet:
#include <stdio.h>
#include <stddef.h> // For ptrdiff_t
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *p_start = &arr[1]; // Points to 20
int *p_end = &arr[4]; // Points to 50
ptrdiff_t diff = p_end - p_start;
printf("Address of p_start: %p\n", (void*)p_start);
printf("Address of p_end: %p\n", (void*)p_end);
printf("Number of elements between them: %td\n", diff);
// Let's verify with byte difference
long byte_diff = (char*)p_end - (char*)p_start;
printf("Number of bytes between them: %ld\n", byte_diff);
printf("Byte difference / sizeof(int): %ld\n", byte_diff / sizeof(int));
return 0;
}
Expected Output (assuming 4-byte integers):
Address of p_start: 0x...4
Address of p_end: 0x...d0
Number of elements between them: 3
Number of bytes between them: 12
Byte difference / sizeof(int): 3
The pointer subtraction p_end - p_start directly gives 3, representing the three int elements between the pointers.
4. Code Analysis Challenge
Let's put this all together. Predict the output of the following program before you read the explanation. This is exactly the kind of "mental compilation" you'd do in an interview.
#include <stdio.h>
#include <stddef.h>
typedef struct {
double d; // 8 bytes
char c; // 1 byte
int i; // 4 bytes
} CommsPacket;
int main() {
// On many systems, sizeof(CommsPacket) will be 16, not 13, due to padding.
printf("sizeof(CommsPacket) = %zu bytes\n\n", sizeof(CommsPacket));
CommsPacket packet_buffer[3];
CommsPacket *ptr1 = &packet_buffer[0];
CommsPacket *ptr2 = ptr1 + 2; // Point to the third element
printf("Initial address of ptr1: %p\n", (void*)ptr1);
printf("Address of ptr2: %p\n", (void*)ptr2);
// Calculate the difference in elements
ptrdiff_t elem_diff = ptr2 - ptr1;
printf("Element difference (ptr2 - ptr1): %td\n", elem_diff);
// Now, let's treat the addresses as raw bytes
long byte_diff = (char*)ptr2 - (char*)ptr1;
printf("Byte difference: %ld bytes\n", byte_diff);
return 0;
}
Reasoning:
sizeof(CommsPacket): The compiler will likely add padding to align the members, so the size will be greater than 8+1+4=13. A common alignment would make it 16 bytes.ptr2 = ptr1 + 2:ptr2will point to an address that is2 * sizeof(CommsPacket)bytes afterptr1. If the size is 16, this will be 32 bytes later.elem_diff = ptr2 - ptr1: Sinceptr2points to the element at index 2 andptr1to the element at index 0, the result of the pointer subtraction will be2.byte_diff = (char*)ptr2 - (char*)ptr1: By casting tochar*, we are forcing byte-level arithmetic. The result will be the actual memory offset in bytes, which should be2 * sizeof(CommsPacket), or32if the size is 16. This confirms the relationshipbyte_diff / sizeof(type) = elem_diff.
Conclusion
You've now dissected the mechanics of pointer arithmetic. This isn't just an academic exercise; it's the basis for efficient data processing in C, especially in memory-constrained environments like the ones you work in.
Key Takeaways:
- Pointer arithmetic is always scaled by the size of the pointed-to type:
ptr + nis equivalent to(char*)ptr + n * sizeof(*ptr). - Incrementing (
++) or decrementing (--) a pointer moves it to the next or previous element, respectively. - Subtracting two pointers of the same type yields the number of elements between them, not the number of bytes. The result is of type
ptrdiff_t. - The array access
arr[i]is just syntactic sugar for the pointer operation*(arr + i).
In our next lesson, we will shift our focus from the "how-to" of pointers to common pitfalls. We will investigate NULL pointers, dangling pointers, and wild pointers, learning to identify and prevent some of the most notorious bugs in C programming.