Assembly - stack vs heap memory - Box<T>

I want to visually prove that the Box is on the heap.
How to demonstrate that 'val' is in the stack and 'boxed' is on the heap?

fn main() {
   let val: u8 = 42;
   let boxed: Box<u8> = Box::new(val);
}

I can print the addresses - but this is not a clear proof.

I tried Compiler Explorer - but somehow this is not showing anything ...

fn main() {
    let val: u8 = 42; // Stored on the stack
    let boxed: Box<u8> = Box::new(val); // Allocates data on the heap

    // Print stack memory address of `val`
    println!("{:p}", &val);

    // Print stack memory address of `boxed` (the pointer itself is on the stack)
    println!("{:p}", &boxed);

    // Print the heap memory address where `42` is stored (the value in the `Box`)
    println!("{:p}", boxed.as_ref());
}

1 Like

You need to make your main function public for Godbolt to show any generated assembly. If you enable optimizations, you should use the boxed value to avoid the compiler removing it as dead code.

You can proxy global allocator and add logging

Additionally, here is citation from the docs:

You must not rely on allocations actually happening, even if there are explicit heap allocations in the source. The optimizer may detect unused allocations that it can either eliminate entirely or move to the stack and thus never invoke the allocator.

So

prove that the Box is on the heap.

Is not generally true, even if we assume default global allocator.

2 Likes

main must be pub.

unused var is optimized out, but once you try

pub fn main() {
   let val: u8 = 42;
   println!("{val}");
}

you see

sub    rsp,0x68
mov    BYTE PTR [rsp+0x7],0x2a

it reserves some space in the stack, and then stores 42 there.

and with Box a lot of things happen including

 call   c4 <alloc::alloc::Global::alloc_impl+0xc4>
   R_X86_64_PLT32 .text._ZN5alloc5alloc12alloc_zeroed17h66527717e35bb0e5E-0x4

which locales memory on the heap (well, it locates it "somewhere" depending on allocator settings, but it is usually heap)

Try also

fn main() {
    let a = 1;
    let b = 2;
    let heap = Box::new(3);
    let heap2 = Box::new(34);
    println!("{:p}\n{:p}\n{:p}\n{:p}", &a, &b, heap, heap2);
}

to see their addresses.

If you want to compare them with stack address, you can use sbrk on Linux and read TIB on Windows.

What goes on the stack is related to the local variables in your original program, but not in a one-to-one way once optimization transforms it.

On Linux, the way heaps work depend on the allocator you use but you can get useful information by looking at /proc/PID/maps. Run your program through strace and you should be able to see when the allocator asks for memory from the OS (by calling sbrk to extend the stack segment or mmap to map a new region of memory).