< 返回版块

JasonkayZK 发表于 2022-12-12 12:33

我在测试一段代码:

use std::ptr;

pub unsafe fn from_buf_raw<T: Sized>(ptr: *const T, cap: usize) -> Vec<T> {
    let mut dst = Vec::with_capacity(cap);
    ptr::copy_nonoverlapping(ptr, dst.as_mut_ptr(), cap);
    dst.set_len(cap);
    dst
}

#[derive(PartialEq, Debug)]
struct Foo {
    data: String,
}

fn main() {
    let a = [
        Foo {
            data: String::from("a"),
        },
        Foo {
            data: String::from("b"),
        },
        Foo {
            data: String::from("c"),
        },
    ];

    let v = unsafe { from_buf_raw(&a[1], 2) };
    println!("{:?}", v);
    assert_eq!(
        v,
        vec![
            Foo {
                data: String::from("b")
            },
            Foo {
                data: String::from("c")
            },
        ]
    );
}

playground:

  • https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=cf3f580196ab31820703c6000c39577a

是可以正常输出的,但是改成测试:

use std::ptr;

pub unsafe fn from_buf_raw<T: Sized>(ptr: *const T, cap: usize) -> Vec<T> {
    let mut dst = Vec::with_capacity(cap);
    ptr::copy_nonoverlapping(ptr, dst.as_mut_ptr(), cap);
    dst.set_len(cap);
    dst
}

#[derive(PartialEq, Debug)]
struct Foo {
    data: String,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_no_copy_trait() {
        #[derive(PartialEq, Debug)]
        struct Foo {
            data: String,
        }

        let a = [
            Foo { data: String::from("a") },
            Foo { data: String::from("b") },
            Foo { data: String::from("c") },
        ];

        let v = unsafe { from_buf_raw(&a[1], 2) };
        println!("{:?}", v);
        assert_eq!(v, vec![Foo { data: String::from("b") },
                           Foo { data: String::from("c") }, ]);
    }
}

就报错:

free(): double free detected in tcache 2
error: test failed, to rerun pass `--lib`

Caused by:
  process didn't exit successfully

这里的 v 不是 copy 来的吗?为什么会报错 free 了两次呢?

评论区

写评论
作者 JasonkayZK 2022-12-12 20:13

学习了学习了; 这里实际的类型可能不是String,而是一个范型。 我在研究如何复制任意包含一个范型类型的Vector,结果发现不行。

--
👇
Pikachu: 最好的办法是给T加上T: Copy的约束。

copy_nonoverlapping的文档都写了,对非Copy类型使用这个函数可能导致内存问题。

If T is not Copy, using both the values in the region beginning at *src and the region beginning at *dst can violate memory safety.

我的建议是,写unsafe代码时,确保以下几点

  • 读完rustonomicon;
  • 完成nugine大佬的unsafe自测题
  • 读了所用到的unsafe函数的safety文档;
  • 用Miri验证过自己的代码。

如果没满足这些条件的话,最好还是想办法用safe rust实现。比如说,我就不太理解你这里为什么不直接写a[1..].to_vec()。

Pikachu 2022-12-12 16:40

最好的办法是给T加上T: Copy的约束。

copy_nonoverlapping的文档都写了,对非Copy类型使用这个函数可能导致内存问题。

If T is not Copy, using both the values in the region beginning at *src and the region beginning at *dst can violate memory safety.

我的建议是,写unsafe代码时,确保以下几点

  • 读完rustonomicon;
  • 完成nugine大佬的unsafe自测题
  • 读了所用到的unsafe函数的safety文档;
  • 用Miri验证过自己的代码。

如果没满足这些条件的话,最好还是想办法用safe rust实现。比如说,我就不太理解你这里为什么不直接写a[1..].to_vec()。

作者 JasonkayZK 2022-12-12 16:02

懂了,就是Copy的整个区间,我要用forget标注一下。

--
👇
Grobycn: 你用了 unsafe, 就要靠自己保证安全。

--
👇
JasonkayZK: 嗯,我也怀疑是因为只是复制了 String 底层的指针导致的;

那我要怎么通过 copy_nonoverlapping 来完整复制这个String呢?貌似不行。

那如果某些结构存在这种底层引用的指针,是不是就不能使用 copy_nonoverlapping 了?

--
👇
Grobycn: 明白 String 的内存布局是怎么样的,你就知道为什么会 double free.

StringVec 差不多,大概是这样的:

struct Underlying {
  len: usize,
  cap: usize,
  data: *u8
}

它们 drop 的时候,会负责回收 data 指向的内存。而你的复制只是复制了 data 这个指针,而没有复制 data 指向的数据。

Grobycn 2022-12-12 14:47

你用了 unsafe, 就要靠自己保证安全。

--
👇
JasonkayZK: 嗯,我也怀疑是因为只是复制了 String 底层的指针导致的;

那我要怎么通过 copy_nonoverlapping 来完整复制这个String呢?貌似不行。

那如果某些结构存在这种底层引用的指针,是不是就不能使用 copy_nonoverlapping 了?

--
👇
Grobycn: 明白 String 的内存布局是怎么样的,你就知道为什么会 double free.

StringVec 差不多,大概是这样的:

struct Underlying {
  len: usize,
  cap: usize,
  data: *u8
}

它们 drop 的时候,会负责回收 data 指向的内存。而你的复制只是复制了 data 这个指针,而没有复制 data 指向的数据。

作者 JasonkayZK 2022-12-12 14:34

嗯,我也怀疑是因为只是复制了 String 底层的指针导致的;

那我要怎么通过 copy_nonoverlapping 来完整复制这个String呢?貌似不行。

那如果某些结构存在这种底层引用的指针,是不是就不能使用 copy_nonoverlapping 了?

--
👇
Grobycn: 明白 String 的内存布局是怎么样的,你就知道为什么会 double free.

StringVec 差不多,大概是这样的:

struct Underlying {
  len: usize,
  cap: usize,
  data: *u8
}

它们 drop 的时候,会负责回收 data 指向的内存。而你的复制只是复制了 data 这个指针,而没有复制 data 指向的数据。

Grobycn 2022-12-12 14:15

明白 String 的内存布局是怎么样的,你就知道为什么会 double free.

StringVec 差不多,大概是这样的:

struct Underlying {
  len: usize,
  cap: usize,
  data: *u8
}

它们 drop 的时候,会负责回收 data 指向的内存。而你的复制只是复制了 data 这个指针,而没有复制 data 指向的数据。

PaiGack 2022-12-12 14:07

感觉是不是被优化了

--
👇
JasonkayZK: 最下面加了 mem::forget(v); 之后,避免 v 释放内存后可以执行了。

PaiGack 2022-12-12 14:00

试了一下,貌似异常退出了 Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running target\debug\ol.exe [Foo { data: "b" }, Foo { data: "c" }] error: process didn't exit successfully: target\debug\ol.exe (exit code: 0xc0000374, STATUS_HEAP_CORRUPTION)

  • The terminal process "D:\ProgramData\Rust.cargo\bin\cargo.exe 'run', '--package', 'ol', '--bin', 'ol'" terminated with exit code: 3221226356.
  • Terminal will be reused by tasks, press any key to close it.
作者 JasonkayZK 2022-12-12 12:44

最下面加了 mem::forget(v); 之后,避免 v 释放内存后可以执行了。

1 共 9 条评论, 1 页