【问题标题】:How can you allocate a raw mutable pointer in stable Rust?如何在稳定的 Rust 中分配原始可变指针?
【发布时间】:2017-12-31 13:45:40
【问题描述】:

我试图通过小字符串优化构建自定义String 类结构的天真实现。现在稳定的 Rust 中允许联合,我想出了以下代码:

struct Large {
    capacity: usize,
    buffer: *mut u8,
}

struct Small([u8; 16]);

union Container {
    large: Large,
    small: Small,
}

struct MyString {
    len: usize,
    container: Container,
}

我似乎找不到分配*mut u8 的方法。可以在稳定的 Rust 中做吗?看起来使用alloc::heap 会起作用,但它只在夜间可用。

【问题讨论】:

  • FWIW,String 本身无法使用 SSO,因为它公开了某些方法。您将无法复制完整的 API,但希望这不会妨碍您的用例。

标签: rust heap-memory dynamic-memory-allocation


【解决方案1】:

从 Rust 1.28 开始,std::alloc::alloc 是稳定的。

这里有一个例子,它概括地展示了如何使用它。

use std::{
    alloc::{self, Layout},
    cmp, mem, ptr, slice, str,
};

// This really should **not** be copied
#[derive(Copy, Clone)]
struct Large {
    capacity: usize,
    buffer: *mut u8,
}

// This really should **not** be copied
#[derive(Copy, Clone, Default)]
struct Small([u8; 16]);

union Container {
    large: Large,
    small: Small,
}

struct MyString {
    len: usize,
    container: Container,
}

impl MyString {
    fn new() -> Self {
        MyString {
            len: 0,
            container: Container {
                small: Small::default(),
            },
        }
    }

    fn as_buf(&self) -> &[u8] {
        unsafe {
            if self.len <= 16 {
                &self.container.small.0[..self.len]
            } else {
                slice::from_raw_parts(self.container.large.buffer, self.len)
            }
        }
    }

    pub fn as_str(&self) -> &str {
        unsafe { str::from_utf8_unchecked(self.as_buf()) }
    }

    // Not actually UTF-8 safe!
    fn push(&mut self, c: u8) {
        unsafe {
            use cmp::Ordering::*;

            match self.len.cmp(&16) {
                Less => {
                    self.container.small.0[self.len] = c;
                }
                Equal => {
                    let capacity = 17;
                    let layout = Layout::from_size_align(capacity, mem::align_of::<u8>())
                        .expect("Bad layout");

                    let buffer = alloc::alloc(layout);

                    {
                        let buf = self.as_buf();
                        ptr::copy_nonoverlapping(buf.as_ptr(), buffer, buf.len());
                    }

                    self.container.large = Large { capacity, buffer };

                    *self.container.large.buffer.offset(self.len as isize) = c;
                }
                Greater => {
                    let Large {
                        mut capacity,
                        buffer,
                    } = self.container.large;
                    capacity += 1;

                    let layout = Layout::from_size_align(capacity, mem::align_of::<u8>())
                        .expect("Bad layout");

                    let buffer = alloc::realloc(buffer, layout, capacity);

                    self.container.large = Large { capacity, buffer };

                    *self.container.large.buffer.offset(self.len as isize) = c;
                }
            }

            self.len += 1;
        }
    }
}

impl Drop for MyString {
    fn drop(&mut self) {
        unsafe {
            if self.len > 16 {
                let Large { capacity, buffer } = self.container.large;
                let layout =
                    Layout::from_size_align(capacity, mem::align_of::<u8>()).expect("Bad layout");
                alloc::dealloc(buffer, layout);
            }
        }
    }
}

fn main() {
    let mut s = MyString::new();

    for _ in 0..32 {
        s.push(b'a');
        println!("{}", s.as_str());
    }
}

我相信这段代码在分配方面是正确的,但在其他方面则不然。像所有unsafe 代码一样,自己验证一下。它也完全低效,因为它为每个额外的字符重新分配。


如果您想分配u8 的集合而不是单个u8,您可以创建一个Vec,然后将其转换为组成部分,例如通过调用as_mut_ptr

use std::mem;

fn main() {
    let mut foo = vec![0; 1024]; // or Vec::<u8>::with_capacity(1024);

    let ptr = foo.as_mut_ptr();
    let cap = foo.capacity();
    let len = foo.len();
    mem::forget(foo); // Avoid calling the destructor!

    let foo_again = unsafe { Vec::from_raw_parts(ptr, len, cap) }; // Rebuild it to drop it
    // Do *NOT* use `ptr` / `cap` / `len` anymore
}

重新分配有点痛苦;您必须转换回 Vec 并向前和向后跳整个舞蹈

话虽如此,您的Large 结构似乎缺少length,这与容量不同。你可以只使用Vec 而不是写出来。我现在看到它在层次结构中有点上升了。

我想知道如果有一个完整的String 会不会容易很多,即使它的效率有点低,因为长度被重复计算......

union Container {
    large: String,
    small: Small,
}

另见:

【讨论】:

  • 长度字段在MyString中,其中还包含LargeSmall
  • @interjay 啊,确实。
  • 这正是我想要的!谢谢!不知何故,我没有意识到我可以使用 Vec 进行分配和释放。
【解决方案2】:

Box::into_raw() 呢?

struct TypeMatches(*mut u8);
TypeMatches(Box::into_raw(Box::new(0u8)));

但是很难从您的代码 sn-p 中判断这是否是您真正需要的。你可能想要一个真正的分配器,你可以使用libc::mallocas 演员,如this example

【讨论】:

  • 分配一个u8。 OP 想要一个以上的缓冲区。
  • @interjay 确实如此,但所问的问题只是如何分配 指针,这是一个有效的答案。我希望来自搜索引擎的人会发现这个问题和答案很有用。
【解决方案3】:

有一个memalloc crate,它提供了一个稳定的分配API。它是通过Vec::with_capacity分配内存,然后提取指针来实现的:

let vec = Vec::with_capacity(cap);
let ptr = buf.as_mut_ptr();
mem::forget(vec);

要释放内存,请使用Vec::from_raw_parts

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多