Understanding Memory Addressing with Pointers
When programming in lower-level languages like C or C++, you often need to work directly with memory addresses and pointers. This gives you a lot of power and flexibility, but it also requires a solid understanding of how memory is organized and addressed. One key concept is addressing objects relative to different pointers or registers.
First, let’s review some basics. The stack is an area of memory used for storing function call information like parameters, local variables, and return addresses. The stack pointer (ESP on 32-bit x86, RSP on 64-bit) points to the current top of the stack.
The frame pointer (EBP on 32-bit x86, RBP on 64-bit) is another commonly used register that points to the base of the current function’s stack frame. This allows parameters and local variables to be accessed at fixed offsets from the frame pointer.
Addressing Relative to the Stack Pointer
One way to access objects in memory by addressing them relative to the stack pointer register. For example:
int x;
x = 10;
int *ptr = &x;
ptr = ptr - 1; // Points to 4 bytes before x
*ptr = 20; // Modifies a different variable below xHere we are offsetting a pointer to x by -1, which points to the 4 bytes directly before x in memory (ints are 4 bytes). We can then dereference this pointer to overwrite whatever resides at that memory location.
This stack pointer relative addressing is sometimes used when creating stack-allocated objects like arrays:
int arr[10];
int *ptr = arr; // Points to start of arr
ptr += 5; // Now points to &arr[5]However, addressing relative to the stack pointer can be error-prone since the stack pointer moves around as functions are called and return. This can cause pointers to become invalid if you return from the function where they were defined.
Addressing Relative to the Frame Pointer
To avoid these issues, it’s often better to address objects relative to the frame pointer instead. This way you have a stable pointer to the base of the current frame that doesn’t move.
void foo(int x) {
int *ptr = &x; // Pointer to x
}Here, by decrementing the pointer to y by 2 (assuming 32-bit ints), we can access x at a known offset from the frame pointer. This allows us to reliably access x and other local variables and parameters at fixed offsets.
On the stack, local variables are typically allocated at positive offsets from the frame pointer, while parameters have negative offsets since they are pushed on the stack before the function prolog. This behavior depends on the specific calling convention used for the function.
Other Pointer Addressing
In some situations you may address memory relative to other pointers as well. For example, you could allocate a dynamically sized buffer with malloc and then address elements within that buffer by offsetting from the malloc’d pointer.
Or when working with structs, you can use the -> operator to access fields relative to a struct pointer.
Conclusion
When programming in low-level languages, understanding how memory is laid out and addressed is critical. Addressing objects relative to the stack pointer provides flexibility but can be unsafe. Addressing relative to the stable frame pointer is generally preferred to reliably access local variables and parameters. And other pointer addressing modes are used for working with dynamic memory and data structures.


