I've been reading on user space and kernel space and how heap and stack grow. I'm wondering, if heap and stack memories have constant starts in memory layout, can we just use memory address difference to see where an object is initialized?
On Unix, those address differences can be pretty small (since they grow toward each other), so some "tolerance" would be required. On Windows, those address differences will be pretty large (unless they are close to the beginning of each respected memory type), so again some "tolerance" would be required. I haven't been able to find the memory layout for Mac.
On older Unices where brk(2)
was a (the only stack/heap setup) thing, it was possible. The sbrk(2)
call, when called with argument of 0, returns the current end of the heap, after which the stack is located.
Demo of the sbrk
call:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
void *heap_end = sbrk(0);
int x = 42;
int *y = malloc(sizeof(int));
printf("heap_end = %p\n", heap_end);
printf("&x = %p\n", &x);
printf("y = %p\n", y);
heap_end = sbrk(0);
printf("heap_end = %p\n", heap_end);
return 0;
}
Output with annotations:
heap_end = 0x55db9b63d000 Original memory break pos.
&x = 0x7ffe93814ef4 Stack variable.
y = 0x55db9b63d2a0 Heap variable.
heap_end = 0x55db9b65e000 Break pos after allocation.
As you can see, the value returned by the sbrk
has changed and it is higher than the maximum address contained in the heap-allocated area.
You can't cache the sbrk
value, because it can increase every time when the application performs a memory allocation.
This approach works even for current systems. The example was executed on a recent Linux system.
Unfortunately, this approach breaks (pun intended ☺) when you do not use the memory allocator from libc, but you instead use mmap(2)
(and old shm
) interface to allocate the memory. This can happen e.g. when you use alternative allocator etc. You can't be also sure that your libraries won't provide a pointer that is not on the stack, but still located above the memory break.
This applies even to the libc. If the libc does not call brk
, it won't work.
Also, when you have multiple threads, things can get complicated, but using brk
is still better that using the stack pointer of the current (!) thread that can be located anywhere.
Another problem is that you can have a program or a library that handles the stack itself and it even can locate the stack on the heap.
Although the brk(2)
syscall is available even on recent systems, it was deprecated in POSIX.1-2001, since modern operating systems, which do multithreading and more complex memory management, can't have a simple dividing line (the memory break) in the memory separating the program image and its heap from its stack.
TL;DR: There exists a legacy API for this, but it its usability depends on many factors, such as multithreading, the used libc etc. Also, it's portable only among a subset of (2001's) POSIX-compatible systems.