В чем разница между `& mut retval` и` retval как * const T как * mut T`? - PullRequest
2 голосов
/ 31 марта 2020

Мне трудно понять, почему следующий код имеет 2 разных поведения:

pub fn get(&self, idx: usize) -> &T {
    let arr = unsafe { core::slice::from_raw_parts(self.elements, self.count) };
    &arr[idx]
}

, когда я звоню:

unsafe { ptr::drop_in_place(self.get(i) as *const T as *mut T) };

, это работает, и значение сбрасывается на месте, однако изменение приведения на &mut не дает:

unsafe { ptr::drop_in_place(&mut self.get(i)) };

Я ожидал, что компилятор выдаст ошибку, поскольку T не поддерживает клонирование / копирование, но это не так. Что такое объяснение?

Минимальный пример:

use core::*;

pub struct Vec<T> {
    elements: *mut T,
    count: usize,
    capacity: usize,
}

pub fn alloc_array<T>(count: usize) -> *mut T {
    let size = mem::size_of::<T>() * count;
    let addr = unsafe { libc::memalign(mem::size_of::<usize>(), size) as *mut T };
    unsafe { libc::memset(addr as *mut libc::c_void, 0, size) };
    addr
}

pub fn free_array<T>(arr: *mut T) {
    unsafe { libc::free(arr as *mut libc::c_void) };
}

impl<T> Vec<T> {
    pub fn new() -> Self {
        Self {
            elements: ptr::null_mut(),
            count: 0,
            capacity: 0,
        }
    }

    pub fn len(&self) -> usize {
        self.count
    }

    pub fn pushBack(&mut self, t: T) {
        if self.count >= self.capacity {
            let newSize = if self.capacity == 0 {
                16
            } else {
                self.capacity * 2
            };
            let old = self.elements;
            self.elements = alloc_array(newSize);
            self.capacity = newSize;

            let oldArr = unsafe { core::slice::from_raw_parts_mut(old, self.count) };
            let newArr = unsafe { core::slice::from_raw_parts_mut(self.elements, self.count + 1) };

            for i in 0..self.count {
                let v = unsafe { ptr::read(&oldArr[i] as *const _) };
                newArr[i] = v;
            }
        }
        let arr = unsafe { core::slice::from_raw_parts_mut(self.elements, self.count + 1) };

        arr[self.count] = t;
        self.count += 1
    }

    pub fn pop(&mut self) -> Option<T> {
        if self.count == 0 {
            None
        } else {
            self.count -= 1;
            Some(unsafe { ptr::read(self.get(self.count) as *const _) })
        }
    }

    #[inline]
    pub fn get(&self, idx: usize) -> &T {
        let arr = unsafe { core::slice::from_raw_parts(self.elements, self.count) };
        &arr[idx]
    }
}

impl<T> Drop for Vec<T> {
    fn drop(&mut self) {
        println!("Dropped");
        for i in 0..self.count {
            //unsafe { ptr::drop_in_place(self.get(i) as *const T as *mut T) };   // Works
            unsafe { ptr::drop_in_place(&mut self.get(i)) }; // Doesn't Works
        }
        if self.capacity != 0 {
            free_array(self.elements);
        }
    }
}

fn main() {
    let mut v = Vec::<Vec<i32>>::new();
    for i in 0..10 {
        let mut vj = Vec::<i32>::new();
        for j in 0..10 {
            vj.pushBack(j);
        }
        v.pushBack(vj);
    }
}

Valgrind:

Dropped
Dropped
Dropped
Dropped
Dropped
Dropped
Dropped
Dropped
Dropped
Dropped
Dropped
==6887== 
==6887== HEAP SUMMARY:
==6887==     in use at exit: 640 bytes in 10 blocks
==6887==   total heap usage: 30 allocs, 20 frees, 4,433 bytes allocated
==6887== 
==6887== Searching for pointers to 10 not-freed blocks
==6887== Checked 107,320 bytes
==6887== 
==6887== 640 bytes in 10 blocks are definitely lost in loss record 1 of 1
==6887==    at 0x4C320A6: memalign (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6887==    by 0x10CB3F: minimal_test::alloc_array (main.rs:11)
==6887==    by 0x10CDDC: minimal_test::Vec<T>::pushBack (main.rs:35)
==6887==    by 0x10E1BB: minimal_test::main (main.rs:85)
==6887==    by 0x10C2DF: std::rt::lang_start::{{closure}} (rt.rs:67)
==6887==    by 0x1165B2: {{closure}} (rt.rs:52)
==6887==    by 0x1165B2: std::panicking::try::do_call (panicking.rs:305)
==6887==    by 0x117D16: __rust_maybe_catch_panic (lib.rs:86)
==6887==    by 0x116F3F: try<i32,closure-0> (panicking.rs:281)
==6887==    by 0x116F3F: catch_unwind<closure-0,i32> (panic.rs:394)
==6887==    by 0x116F3F: std::rt::lang_start_internal (rt.rs:51)
==6887==    by 0x10C2B8: std::rt::lang_start (rt.rs:67)
==6887==    by 0x10E259: main (in minimal-test/target/debug/minimal-test)
==6887== 
==6887== LEAK SUMMARY:
==6887==    definitely lost: 640 bytes in 10 blocks
==6887==    indirectly lost: 0 bytes in 0 blocks
==6887==      possibly lost: 0 bytes in 0 blocks
==6887==    still reachable: 0 bytes in 0 blocks
==6887==         suppressed: 0 bytes in 0 blocks
==6887== 
==6887== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
==6887== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

1 Ответ

4 голосов
/ 31 марта 2020

Обратите особое внимание на типы.

pub fn get(&self, idx: usize) -> &T {

self.get(i) имеет тип &T. Так что &mut self.get(i) имеет тип &mut &T. Вызов drop_in_place приведет &mut &T к *mut &T и сбросит &T, который (поскольку общие ссылки не реализуют Drop) ничего не делает.

self.get(i) as *const _ as *mut _ приводит &T к *const T, а затем *mut T. Вызов drop_in_place вызовет неопределенное поведение, когда он вызывает <T as Drop>::drop, который принимает &mut T. Это плохо.

Вы не можете изменить значение (включая его сброс) через общая ссылка. Приведение через сырые указатели не делает это возможным. См. Есть ли способ сделать неизменяемую ссылку изменяемой?

Nomicon содержит раздел о реализации Vec<T> с нуля; Предлагаю прочитать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...