Pointer Arithmetic in C Language

In the C language, pointers are not merely windows to access memory addresses; they are powerful tools for efficient data processing. Pointer arithmetic allows programmers to perform addition, subtraction, multiplication, division, and bitwise shift operations directly on memory addresses, thereby achieving precise control over array traversal, structure offset calculation, and memory block management. Understanding these operation rules is key to mastering the underlying logic of C language.
Pointer addition and subtraction are not simple numerical calculations; they represent "logical" displacements calculated based on data type sizes. When performing an addition operation on a pointer, the compiler automatically multiplies the offset by the size of the type pointed to by that pointer.

For example, assume there is an integer array int arr[5] = {10, 20, 30, 40, 50};, whose memory layout is stored contiguously on the stack. If a pointer is defined as int *p = arr;, then p + 1 points to the second element, not an address value increased by 4 (assuming int is 4 bytes). This is because the compiler knows that int occupies 4 bytes, so the actual address increases by 4.

#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *p = arr;
    
    // p + 1 points to arr[1], the address increases by sizeof(int)
    printf("%d\n", *(p + 1)); 
    
    // Pointer subtraction calculates the number of elements between two addresses
    int *q = arr + 2;
    int diff = q - p; // diff is 2, representing a difference of 2 ints
    
    return 0;
}

This mechanism greatly simplifies array traversal and linked list operations. In loops, p++ actually moves the pointer to the position of the next element without manually calculating address offsets.

Multiplication and Division: Operations Spanning Memory Blocks

Although scenarios involving direct multiplication or division of pointers are relatively rare in actual development, they still play an important role when handling multidimensional arrays, accessing structure members, and managing dynamic memory allocation. Multiplying a pointer by an integer n is equivalent to moving the pointer back by n * sizeof(pointed_type) bytes.

Assume we have a two-dimensional array int matrix[3][4], which can be viewed as a large one-dimensional array. To access the starting position of the second row (index 1), we can use matrix + 4 or matrix + (1 * 4). The multiplication operation here helps convert the row index into the correct address offset.

#include <stdio.h>

int main() {
    int matrix[3][4] = {
        {0, 1, 2, 3},
        {4, 5, 6, 7},
        {8, 9, 10, 11}
    };
    
    // Access the first element of the second row, equivalent to matrix + 4
    int *row_ptr = matrix + (1 * 4); 
    printf("%d\n", *row_ptr); 
    
    return 0;
}

Furthermore, in dynamic memory allocation, if the total number of bytes total_bytes needed for allocation is known, an address can be obtained via void *ptr = malloc(total_bytes);. If this block of memory needs to be treated as a specific type array later on, division can be used to estimate the number of elements: int count = total_bytes / sizeof(element_type);.

Pointer Shift Operations: Bitwise Operations and Structure Offsets

Pointers themselves cannot undergo direct shifting (such as << or >>), but address addition and subtraction operations can be indirectly implemented through bitwise operations. Since memory addresses are stored in binary form at the computer's bottom layer, a left shift by one is equivalent to multiplying by 2, while a right shift by one is equivalent to dividing by 2 (floor division).

For example, if you want to move the pointer forward two element positions, you can subtract sizeof(type) * 2 from its address value. In certain embedded systems or scenarios requiring extreme performance optimization, using bitwise operations instead of multiplication may bring a slight performance improvement, although modern compilers typically perform this optimization automatically.

A more common and important application is in structure (Struct) operations using pointer arithmetic. By calculating the offset, we can directly access non-first members within a structure without creating copies or unpacking data.

#include <stdio.h>

struct Person {
    int age;
    char name[10];
};

int main() {
    struct Person p = {25, "Alice"};
    
    // Directly access the second member 'name' without copying the entire structure
    // The address offset is sizeof(int)
    char *name_ptr = (char *)&p + sizeof(int);
    
    printf("%s\n", name_ptr); 
    
    return 0;
}

In this example, &p obtains the starting address of the structure. Adding sizeof(int) makes the pointer point to the first address of the name field. This technique is commonly used for fast parsing during serialization/deserialization processes or in hardware drivers for directly operating on register arrays.

Precautions and Best Practices

When performing pointer arithmetic operations, one must strictly adhere to C language standard specifications. First, pointers can only be added or subtracted with their pointed-to type or its array; arbitrary memory addresses cannot be freely added or subtracted. Second, the result of pointer addition must fall within the valid range of that object or array; otherwise, it leads to undefined behavior (Undefined Behavior).

Furthermore, arithmetic operations between different types of pointers are strictly prohibited. For example, adding int * and char * is meaningless because their byte sizes differ, leading to logical confusion. When dealing with structure offsets, ensure the calculated offset is an integer multiple of sizeof type to guarantee correct memory alignment.

In summary, pointer arithmetic serves as the bridge connecting abstract logic with physical memory in C language. Mastering addition, subtraction, multiplication, division, and shift operations not only improves code execution efficiency but also helps developers deeply understand memory layout to write more robust and efficient low-level programs. In practical programming, always combine with the sizeof operator to ensure operational accuracy and remain vigilant about potential boundary overflow risks.