Rust создает строку со смещением указателя - PullRequest
2 голосов
/ 23 июня 2019

Допустим, у меня есть 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 (грузовой рейс; грузовой рейс - выпуск):

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 без выделения новой памяти.

Любая помощь приветствуется.

1 Ответ

3 голосов
/ 23 июня 2019

Причиной ошибок является способ работы распределителя. Неопределенное поведение - попросить распределитель освободить указатель, который он не дал вам в первую очередь.В этом случае распределитель выделил 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 "
    }
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...