首页 > 解决方案 > Rust 创建带有指针偏移的字符串

问题描述

所以假设我有一个String,"Foo Bar"并且我想创建一个子字符串"Bar"而不分配新内存。

所以我将原始字符串的原始指针移动到子字符串的开头(在这种情况下将其偏移 4)并使用 String::from_raw_parts() 函数来创建字符串。

到目前为止,我有以下代码,据我所知,它应该可以做到这一点。我只是不明白为什么这不起作用。

use std::mem;

fn main() {
    let s = String::from("Foo Bar");

    let ptr = s.as_ptr();

    mem::forget(s);

    unsafe {
        // no error when using ptr.add(0)
        let txt = String::from_raw_parts(ptr.add(4) as *mut _, 3, 3);

        println!("{:?}", txt); // This even prints "Bar" but crashes afterwards

        println!("prints because 'txt' is still in scope");
    }

    println!("won't print because 'txt' was dropped",)
}

我在 Windows 上收到以下错误:

error: process didn't exit successfully: `target\debug\main.exe` (exit code: 0xc0000374, STATUS_HEAP_CORRUPTION)

这些在 Linux 上(cargo run;cargo run --release):

munmap_chunk(): invalid pointer

free(): invalid pointer

我认为它与String的析构函数有关,因为只要txt在范围内,程序就可以正常运行。

需要注意的另一件事是,当我使用ptr.add(0)而不是ptr.add(4)它运行时没有错误。

另一方面,创建切片并没有给我带来任何问题。丢弃效果很好。

let t = slice::from_raw_parts(ptr.add(4), 3);

String最后,我想在不分配新内存的情况下将拥有的 String 拆分为多个拥有的 s。

任何帮助表示赞赏。

标签: stringpointersrustunsaferaw-pointer

解决方案


错误的原因是分配器的工作方式。要求分配器释放一个它一开始没有给你的指针是未定义的行为。在这种情况下,分配器为第一个分配了 7 个字节s并返回了一个指针。然而,当txt被删除时,它告诉分配器释放一个指向字节 4 的指针,这是它以前从未见过的。这就是为什么当你add(0)而不是add(4).

正确使用unsafe困难的,你应该尽可能避免它。


该类型的部分目的&str是允许string共享拥有的部分,因此我强烈建议您尽可能使用它们。

如果您不能单独使用的原因&str是因为您无法将生命周期追溯到原始String,那么仍然有一些解决方案,但需要权衡取舍:

  1. 泄漏内存,因此它实际上是静态的:

    let mut s = String::from("Foo Bar");
    let s = Box::leak(s.into_boxed_str());
    
    let txt: &'static str = &s[4..];
    let s: &'static str = &s[..4];
    

    显然,您只能在应用程序中执行此操作几次,否则您将使用太多无法取回的内存。

  2. 使用引用计数来确保原始数据String保留足够长的时间以使所有切片都保持有效。这是一个草图解决方案:

    use std::{fmt, ops::Deref, rc::Rc};
    
    struct RcStr {
        rc: Rc<String>,
        start: usize,
        len: usize,
    }
    
    impl RcStr {
        fn from_rc_string(rc: Rc<String>, start: usize, len: usize) -> Self {
            RcStr { rc, start, len }
        }
    
        fn as_str(&self) -> &str {
            &self.rc[self.start..self.start + self.len]
        }
    }
    
    impl Deref for RcStr {
        type Target = str;
        fn deref(&self) -> &str {
            self.as_str()
        }
    }
    
    impl fmt::Display for RcStr {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            fmt::Display::fmt(self.as_str(), f)
        }
    }
    
    impl fmt::Debug for RcStr {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            fmt::Debug::fmt(self.as_str(), f)
        }
    }
    
    fn main() {
        let s = Rc::new(String::from("Foo Bar"));
    
        let txt = RcStr::from_rc_string(Rc::clone(&s), 4, 3);
        let s = RcStr::from_rc_string(Rc::clone(&s), 0, 4);
    
        println!("{:?}", txt); // "Bar"
        println!("{:?}", s);  // "Foo "
    }
    

推荐阅读