No history yet

Pointer Logic and Arithmetic

The Memory Map

At its core, a computer's memory is a vast, linear sequence of billions of tiny storage units called bytes. Each byte has a unique, numbered address, like houses on a very long street. When your C program runs, the operating system allocates a portion of this street for your variables to live on. Pointers are special variables that don't store data like numbers or characters; they store these memory addresses.

These addresses are typically represented in notation. Instead of base-10 (0-9), hexadecimal is base-16 (0-9 and A-F). This format is more compact than binary and maps cleanly to the way computer hardware is organized.

Addresses and Values

In C, two operators are fundamental to working with pointers: the address-of operator (&) and the indirection or dereference operator (*).

  • &variable: This gives you the memory address where variable is stored.
  • *pointer: This retrieves the value stored at the address the pointer is holding.

Think of & as getting the street address for a house, and * as opening the door to see who lives inside.

#include <stdio.h>

int main() {
    int score = 95;      // A regular integer variable.
    int *score_ptr;    // A pointer that can hold the address of an integer.

    score_ptr = &score;  // Store the address of 'score' in 'score_ptr'.

    // Print the address stored in the pointer
    // The %p format specifier is for printing pointer addresses
    printf("Address of score: %p\n", score_ptr);

    // Use the dereference operator (*) to get the value at that address
    printf("Value at that address: %d\n", *score_ptr);

    // You can also modify the original variable through the pointer
    *score_ptr = 100;    // Go to the address and change the value to 100.
    printf("New score value: %d\n", score);

    return 0;
}

Running this code would show the memory address of score (like 0x7ffc...), the value 95, and then the updated value 100. The key takeaway is that score_ptr and score are linked. The pointer gives you an alternative, powerful way to access and manipulate the variable's data.

Smart Arithmetic

Here's where pointers reveal their true power. When you perform arithmetic on a pointer, you aren't just adding or subtracting from the raw memory address. The compiler performs type scaling. It knows the size of the data type the pointer points to, and adjusts the arithmetic accordingly.

If you have an integer pointer int *p, adding 1 to it (p + 1) doesn't increment the address by one byte. Instead, it advances the pointer by sizeof(int) bytes (usually 4), effectively moving it to the location of the next integer in memory. This makes navigating arrays incredibly efficient.

Pointer Typep + 1 moves the address by...
char *psizeof(char) (1 byte)
int *psizeof(int) (4 bytes)
double *psizeof(double) (8 bytes)
struct MyStruct *psizeof(struct MyStruct) bytes

This scaling is why C can iterate through an array arr using either arr[i] or *(arr + i). The latter is pure pointer arithmetic. The compiler translates both into the same efficient machine code that calculates the address of the i-th element and retrieves its value.

Generic and Function Pointers

Sometimes you need to handle data without knowing its specific type. This is common in library functions that operate on generic data, like sorting or memory copy routines. For this, C provides the void pointer, void*. A void pointer is a generic pointer that can hold the address of any data type. However, because it has no type, the compiler doesn't know how to scale arithmetic or dereference it. You must cast it to a specific pointer type before you can use it.

int num = 42;
double price = 99.99;

void *generic_ptr;

generic_ptr = &num; // OK
// To use it, we must cast it back
int *int_ptr = (int *)generic_ptr;
printf("The integer is: %d\n", *int_ptr);

generic_ptr = &price; // Also OK
double *double_ptr = (double *)generic_ptr;
printf("The price is: %.2f\n", *double_ptr);

Just as pointers can store the address of data, they can also store the address of code. A holds the memory address of a function. This is extremely useful for implementing and creating flexible, dynamic systems. You can pass a function as an argument to another function, allowing the receiving function to call back to the code you provided.

#include <stdio.h>

// A function we want to point to
void say_hello(char *name) {
    printf("Hello, %s!\n", name);
}

// Another function with the same signature
void say_goodbye(char *name) {
    printf("Goodbye, %s!\n", name);
}

int main() {
    // Declare a function pointer named 'greeting_func'
    void (*greeting_func)(char *);

    // Point it to the 'say_hello' function
    greeting_func = &say_hello;
    greeting_func("Alice"); // Calls say_hello("Alice")

    // Now point it to 'say_goodbye'
    greeting_func = &say_goodbye;
    greeting_func("Bob");   // Calls say_goodbye("Bob")

    return 0;
}

Time to test your knowledge of how C handles memory and pointers.

Quiz Questions 1/6

What is the primary role of a pointer variable in C?

Quiz Questions 2/6

Given the following C code, what will be printed to the console?

int value = 42;
int *ptr = &value;
*ptr = 100;
printf("%d", value);

Pointers are C's way of giving you direct, fine-grained control over memory. Mastering them is the key to writing efficient, low-level code and building complex data structures from scratch.