Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 8

Pointer Arithmetic and Type Sizes

Recommended resources

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.

Pointer Arithmetic Explained with Type Size Dependence
This diagram illustrates how pointer arithmetic is scaled by the size of the data type. Incrementing a pointer `y` to a `double` array `x` correctly moves the address by `sizeof(double)` bytes, so `y+3` points to the element `x[3]`.

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:

  1. 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.
  2. ptr2 = ptr1 + 2: ptr2 will point to an address that is 2 * sizeof(CommsPacket) bytes after ptr1. If the size is 16, this will be 32 bytes later.
  3. elem_diff = ptr2 - ptr1: Since ptr2 points to the element at index 2 and ptr1 to the element at index 0, the result of the pointer subtraction will be 2.
  4. byte_diff = (char*)ptr2 - (char*)ptr1: By casting to char*, we are forcing byte-level arithmetic. The result will be the actual memory offset in bytes, which should be 2 * sizeof(CommsPacket), or 32 if the size is 16. This confirms the relationship byte_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 + n is 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.