如何创建一个智能指针,指向带有嵌入切片的未调整大小的类型?

问题描述 投票:0回答:2

我试图通过使用 C 的灵活数组成员之类的东西来避免多次堆分配。为此,我需要分配一个未调整大小的结构,但我没有找到任何方法通过智能指针来做到这一点。我对

Rc
特别感兴趣,但
Box
也是如此,所以这就是我将在示例中使用的。

这是迄今为止我得到的最接近的:

use std::alloc::{self, Layout};

struct Inner {/* Sized fields */}

#[repr(C)] // Ensure the array is always last
// Both `inner` and `arr` need to be allocated, but preferably not separately
struct Unsized {
    inner: Inner,
    arr: [usize],
}

pub struct Exposed(Box<Unsized>);

impl Exposed {
    pub fn new(capacity: usize) -> Self {
        // Create a layout of an `Inner` followed by the array
        let (layout, arr_base) = Layout::array::<usize>(capacity)
            .and_then(|arr_layout| Layout::new::<Inner>().extend(arr_layout))
            .unwrap();
        let ptr = unsafe { alloc::alloc(layout) };
        // At this point, `ptr` is `*mut u8` and the compiler doesn't know the size of the allocation
        if ptr.is_null() {
            panic!("Internal allocation error");
        }
        unsafe {
            ptr.cast::<Inner>()
                .write(Inner {/* Initialize sized fields */});
            let tmp_ptr = ptr.add(arr_base).cast::<usize>();
            // Initialize the array elements, in this case to 0
            (0..capacity).for_each(|i| tmp_ptr.add(i).write(0));
            // At this point everything is initialized and can safely be converted to `Box`
            Self(Box::from_raw(ptr as *mut _))
        }
    }
}

这无法编译:

error[E0607]: cannot cast thin pointer `*mut u8` to fat pointer `*mut Unsized`
  --> src/lib.rs:32:28
   |
32 |         Self(Box::from_raw(ptr as *mut _))
   |                            ^^^^^^^^^^^^^

我可以直接使用

*mut u8
,但这似乎非常容易出错,需要手动删除。

有没有办法从

ptr
创建一个胖指针,因为我实际上知道分配大小,或者从复合的无大小类型创建一个智能指针?

rust smart-pointers
2个回答
10
投票

问题是指针

*mut Unsized
是一个宽指针,所以不仅仅是一个地址,而是一个地址和切片中元素的数量。另一方面,指针
*mut u8
不包含有关切片长度的信息。标准库提供了

  • std::ptr::slice_from_raw_parts
    并且,
  • std::ptr::slice_from_raw_parts_mut

对于这种情况。所以你首先创建一个假的(而且是错误的)

*mut usize

ptr as *mut usize

然后允许

slice_from_raw_parts_mut(ptr as *mut usize, capacity)

创建一个假的(仍然是错误的)

*mut [usize]
,并在宽指针中使用正确的长度字段,然后我们毫不客气地对其进行强制转换

slice_from_raw_parts_mut(ptr as *mut usize, capacity) as *mut Unsized

除了更改类型(值不变)之外什么也不做,因此我们得到了正确的指针,现在我们终于可以将其输入到

Box::from_raw

演示这篇文章的完整示例:

use std::alloc::{self, Layout};

struct Inner {/* Sized fields */}

#[repr(C)] // Ensure the array is always last
           // Both `inner` and `arr` need to be allocated, but preferably not separately
struct Unsized {
    inner: Inner,
    arr: [usize],
}

pub struct Exposed(Box<Unsized>);

impl Exposed {
    pub fn new(capacity: usize) -> Self {
        // Create a layout of an `Inner` followed by the array
        let (layout, arr_base) = Layout::array::<usize>(capacity)
            .and_then(|arr_layout| Layout::new::<Inner>().extend(arr_layout))
            .unwrap();
        let ptr = unsafe { alloc::alloc(layout) };
        // At this point, `ptr` is `*mut u8` and the compiler doesn't know the size of the allocation
        if ptr.is_null() {
            panic!("Internal allocation error");
        }
        unsafe {
            ptr.cast::<Inner>()
                .write(Inner {/* Initialize sized fields */});
            let tmp_ptr = ptr.add(arr_base).cast::<usize>();
            // Initialize the array elements, in this case to 0
            (0..capacity).for_each(|i| tmp_ptr.add(i).write(0));
        }

        // At this point everything is initialized and can safely be converted to `Box`
        unsafe {
            Self(Box::from_raw(
                std::ptr::slice_from_raw_parts_mut(ptr as *mut usize, capacity) as *mut Unsized,
            ))
        }
    }
}

游乐场


旁注:您不需要

#[repr(C)]
来确保未调整大小的切片字段位于末尾,这是可以保证的。您需要它的目的是了解字段的偏移量。


0
投票

Box 适用于尺寸不定的 T;因此 Box、Box<[T]> 等等。 Box 和 String 之间需要注意的重要区别是,后者也有一个容量成员,将其内存使用量增加一个字,但允许高效附加,因为它可能不需要为每次推送重新分配,而 Box 上的类似方法需要。 Box<[T]> 与 Vec 也是如此,前者是固定大小的切片,而后者可以方便地增长。与 Box 不同的是,Box<[T]> 是在现实生活中实际使用的;向量!宏使用它来提高效率,因为 Box<[T]> 可以按字面写出,然后免费转换为 Vec。

© www.soinside.com 2019 - 2024. All rights reserved.