Andrea Nardi
Andrea Nardi

Reputation: 423

Use stack memory as heap memory without UB

I am working in an environment where I cannot use heap memory but only stack memory. To not be constrained by the #[no_std] enviroment I tried to use stack memory as heap memory with the linked-list-allocator crate. This was my approach.

use linked_list_allocator::LockedHeap;
use std::mem::MaybeUninit;

#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();

pub unsafe fn init_heap(heap_start: usize, heap_size: usize) {
    ALLOCATOR.lock().init(heap_start, heap_size);
}

fn main() {
    const HEAP_SIZE: usize = 2048;
    let mut heap: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::zeroed();
    unsafe { init_heap(heap.as_mut_ptr() as usize, HEAP_SIZE) }
    println!(
        "{} {} {} {} {} {}",
        "This", "String", "Has", "Been", "Dynamically", "Allocated"
    );
}

Compiling and running the following code on my laptop with an llvm-target of x86_64-unknown-linux-gnu and a rustc version 1.49.0-nightly I get the following error:

memory allocation of 4 bytes failedAborted (core dumped)

Is there some rust compiler's assumptions that I am infringing or is my usage of the linked-list-allocator wrong?

Edit: Given the answer and comments from Masklinn here is a working example:

#![feature(start)]

use linked_list_allocator::LockedHeap;

#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();

pub unsafe fn init_heap(heap_start: usize, heap_size: usize) {
    ALLOCATOR.lock().init(heap_start, heap_size);
}

#[start]
fn main(_argn: isize, _argv: *const *const u8) -> isize {
    const HEAP_SIZE: usize = 2048;
    let mut heap = [0u8; HEAP_SIZE];
    unsafe { init_heap(heap.as_mut_ptr() as usize, HEAP_SIZE) }
    std::mem::forget(heap);

    println!(
        "{} {} {} {} {} {}",
        "This", "String", "Has", "Been", "Dynamically", "Allocated"
    );
    0
}

Upvotes: 2

Views: 356

Answers (1)

Masklinn
Masklinn

Reputation: 42207

After looking into the code and finding what looks a lot like the default entry point I'll put what I think is the confirmation of my guess as an answer: the global_allocator must be fully initialised before main, because the default entry point relies on it and allocates: https://github.com/rust-lang/rust/blob/master/library/std/src/rt.rs#L40-L45


        // Next, set up the current Thread with the guard information we just
        // created. Note that this isn't necessary in general for new threads,
        // but we just do this to name the main thread and to give it correct
        // info about the stack bounds.
        let thread = Thread::new(Some("main".to_owned()));
        thread_info::set(main_guard, thread);

I first missed it and looked into sys::args::init but it doesn't seem to allocate (until you request std::args::args), but as it turns out there's an obvious allocation right there, of the main. And there's actually a second allocation in Thread::new (it creates an Arc).

So you will have to no_std, and probably use alloc directly or something. The "embedded" forums / discuss / ... may be more helpful there as that's really not something I ever touched.

Upvotes: 3

Related Questions