A confused problem about memory allocation of struct in stack

I try to figure out memory allocation of struct in stack, code as following,

const LEN: usize = 8 * 1024;

struct Test {
    a: [u8; LEN],
    b: *const u8,
}
impl Test {
    fn new()-> Self {
        let top = 0_u8;
        let a = ['0' as u8; LEN];
        let bot = 0_u8;
        println!("addr of top, bottom in new(): {:p}, {:p}", &top as *const u8, &bot as *const u8);
        println!("addr of array in new(): {:p}, sizeof a: {:x}", &a as *const u8, size_of_val(&a));
        Self {
            a,
            b: &a as *const u8,
        }
    }
}

fn main() {
    let top = 0_u8;
    let a = Test::new();
    let bot = 0_u8;
    println!("a.b:{:p}, sizeof a: {:x}", a.b, size_of_val(&a));
    println!("addr in main top:{:p}, bottom:{:p}", &top as *const u8, &bot as *const u8);
    println!("addr of a in main: {:p}", &a as *const Test);
}

result

addr of top, bottom in new(): 0x1e9666, 0x1eb667
addr of array in new(): 0x1e9667, sizeof a: 2000

a.b:0x1e9667, sizeof a: 2008
addr in main top:0x1ed817, bottom:0x1ef827
addr of a in main: 0x1ed818

It seems that member Test.a: [u8; LEN] is not alocated in stack of fn main because its address 0x1e9667 away the top(0x1ed817) and bottom(0x1ef827) of stack of fn main.

So if the memory Test.a: [u8;Len] is allocated in the stack of fn Test::new(), then these memory will not freed after the ```fn Test::new()`` returned. And will this result in a hole of space of stack?

tl;dr What you want to observe is UB.

The Rust doesn't have a formal memory model yet and usually follows "what C does" if not specified otherwise. And in C, stack memory layout is undefined. It means the variable top and bottom may not be located at the top and the bottom of the call frame of the stack. Practically the stack layout is heavily optimized, reordered and overlapped if the storage duration of the variables are not overlap.

1 Like

Thanks for reply.
I just wonder if a local variable were allocated at stack of a function call, and will not freed after this call. This will result in a hole in stack space, how will compiler optimizate it?
In case of local variable of common function, it will be allocated in stack of caller, as this post https://users.rust-lang.org/t/how-does-rust-manage-stack-space-while-allocating-arrays/50437/8
But how it works in case of a struct?

No, this is simply false. Rust won't leak stack memory on your behalf invisibly. (You can leak memory if you try really hard, but that would involve heap allocation and more complex constructs like Rc, panics, or mem::forget, and it won't happen with such simple, stack-only code.)

The fact that your array doesn't have its adress between the addresses of two other local variables is completely irrelevant. The compiler simply re-ordered the variables so that they reside at the specific addresses they do; they are still within the stack frame of their enclosing function.

1 Like

Thanks for your reply.
I am also very sure Rust won't leak stack memory, I just want to figure out how it works.

In my case, the address of Test.a is far from the address of variable top, if they all in the stack of fn main, what is the memory layout?

And when call Test::new(), will it allocate a new stack? If it was, then the problem is how compiler optimze the variable allocation in stack which will retain after call, such as the Test.a in this case.

It probably just put your immutable 8k of 0s elsewhere so it wouldn't have to initialize it on the stack all the time. Additionally, on --release, if compiled as showed, the call to new would almost surely be inlined into main.

There's no guarantees about how optimizations like stack layout and promotion are performed, either by Rust or by LLVM. You can maybe figure out what rustc does today by reading enough code or through enough experimentation, but it could also change tomorrow (or with more complicated code, different struct sizes, et cetera).

4 Likes

I loaded Test::new() into godbolt and here is what i see

The function takes 8296 bytes off stack space

        mov     eax, 8296
        call    __rust_probestack
        sub     rsp, rax

top get's stored at stack pointer + 6

        mov     byte ptr [rsp + 6], 0

a gets stored at stack pointer + 104

        lea     rdi, [rsp + 104]
        mov     edx, 8192
        mov     esi, 48
        call    qword ptr [rip + memset@GOTPCREL]

bot gets stored at stack pointer +7

        mov     byte ptr [rsp + 7], 0

And at the end of the function it copy the whole Test struct to where ever rbx points to.
Since the struct is to big to return it over registers the caller is responsible for passing a pointer
with enough space behind it to store the result. (r15 at this points holds the adreess of a so rsp + 104)

        lea     rdi, [rbx + 8]
        mov     edx, 8192
        mov     rsi, r15
        call    qword ptr [rip + memcpy@GOTPCREL]
        mov     qword ptr [rbx], r15

Edit: Also i just see that the stack position of a gets stored into Test.b in the result making it a dangling pointer after the return from Test::new()

2 Likes

@juggle-tux Thank you very much for your test and reply.
you are right, the data of Test.a allocated in fn Test::new() is copyed to stack of fn main() at the end of funtion.
And I test the release version, the memory s allocated in stack of fn main() directly. The same thanks to @quinedot

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.