What does the box keyword do?

前端 未结 3 1384
故里飘歌
故里飘歌 2021-02-01 12:27

In Rust, we can use the Box type to allocate things on the heap. This type is used to safely abstract pointers to heap memory. Box

3条回答
  •  栀梦
    栀梦 (楼主)
    2021-02-01 13:05

    NOTE: This reply is a bit old. Since it talks about internals and unstable features, things have changed a little bit. The basic mechanism remains the same though, so the answer is still capable of explaining the undelying mechanisms of box.

    What does box x usually uses to allocate and free memory?

    The answer is the functions marked with lang items exchange_malloc for allocation and exchange_free for freeing. You can see the implementation of those in the default standard library at heap.rs#L112 and heap.rs#L125.

    In the end the box x syntax depends on the following lang items:

    • owned_box on a Box struct to encapsulate the allocated pointer. This struct does not need a Drop implementation, it is implemented automatically by the compiler.
    • exchange_malloc to allocate the memory.
    • exchange_free to free the previously allocated memory.

    This can be effectively seen in the lang items chapter of the unstable rust book using this no_std example:

    #![feature(lang_items, box_syntax, start, no_std, libc)]
    #![no_std]
    
    extern crate libc;
    
    extern {
        fn abort() -> !;
    }
    
    #[lang = "owned_box"]
    pub struct Box(*mut T);
    
    #[lang = "exchange_malloc"]
    unsafe fn allocate(size: usize, _align: usize) -> *mut u8 {
        let p = libc::malloc(size as libc::size_t) as *mut u8;
    
        // malloc failed
        if p as usize == 0 {
            abort();
        }
    
        p
    }
    #[lang = "exchange_free"]
    unsafe fn deallocate(ptr: *mut u8, _size: usize, _align: usize) {
        libc::free(ptr as *mut libc::c_void)
    }
    
    #[start]
    fn main(argc: isize, argv: *const *const u8) -> isize {
        let x = box 1;
    
        0
    }
    
    #[lang = "stack_exhausted"] extern fn stack_exhausted() {}
    #[lang = "eh_personality"] extern fn eh_personality() {}
    #[lang = "panic_fmt"] fn panic_fmt() -> ! { loop {} }
    

    Notice how Drop was not implemented for the Box struct? Well let's see the LLVM IR generated for main:

    define internal i64 @_ZN4main20hbd13b522fdb5b7d4ebaE(i64, i8**) unnamed_addr #1 {
    entry-block:
      %argc = alloca i64
      %argv = alloca i8**
      %x = alloca i32*
      store i64 %0, i64* %argc, align 8
      store i8** %1, i8*** %argv, align 8
      %2 = call i8* @_ZN8allocate20hf9df30890c435d76naaE(i64 4, i64 4)
      %3 = bitcast i8* %2 to i32*
      store i32 1, i32* %3, align 4
      store i32* %3, i32** %x, align 8
      call void @"_ZN14Box$LT$i32$GT$9drop.103617h8817b938807fc41eE"(i32** %x)
      ret i64 0
    }
    

    The allocate (_ZN8allocate20hf9df30890c435d76naaE) was called as expected to build the Box, meanwhile... Look! A Drop method for the Box (_ZN14Box$LT$i32$GT$9drop.103617h8817b938807fc41eE)! Let's see the IR for this method:

    define internal void @"_ZN14Box$LT$i32$GT$9drop.103617h8817b938807fc41eE"(i32**) unnamed_addr #0 {
    entry-block:
      %1 = load i32** %0
      %2 = ptrtoint i32* %1 to i64
      %3 = icmp ne i64 %2, 2097865012304223517
      br i1 %3, label %cond, label %next
    
    next:                                             ; preds = %cond, %entry-    block
      ret void
    
    cond:                                             ; preds = %entry-block
      %4 = bitcast i32* %1 to i8*
      call void @_ZN10deallocate20he2bff5e01707ad50VaaE(i8* %4, i64 4, i64 4)
      br label %next
    }
    

    There it is, deallocate (ZN10deallocate20he2bff5e01707ad50VaaE) being called on the compiler generated Drop!

    Notice even on the standard library the Drop trait is not implemented by user-code. Indeed Box is a bit of a magical struct.

提交回复
热议问题