gpt4 book ai didi

rust - 如果我调用 Vec::from_raw_parts 的容量小于指针实际容量,会发生什么?

转载 作者:行者123 更新时间:2023-12-03 11:23:26 24 4
gpt4 key购买 nike

我有一个向量 u8我想解释为 u32 的向量.假设字节顺序正确。我不想在转换后分配新内存和复制字节。我得到了以下工作:

use std::mem;

fn reinterpret(mut v: Vec<u8>) -> Option<Vec<u32>> {
let v_len = v.len();
v.shrink_to_fit();
if v_len % 4 != 0 {
None
} else {
let v_cap = v.capacity();
let v_ptr = v.as_mut_ptr();
println!("{:?}|{:?}|{:?}", v_len, v_cap, v_ptr);
let v_reinterpret = unsafe { Vec::from_raw_parts(v_ptr as *mut u32, v_len / 4, v_cap / 4) };
println!("{:?}|{:?}|{:?}",
v_reinterpret.len(),
v_reinterpret.capacity(),
v_reinterpret.as_ptr());
println!("{:?}", v_reinterpret);
println!("{:?}", v); // v is still alive, but is same as rebuilt
mem::forget(v);
Some(v_reinterpret)
}
}

fn main() {
let mut v: Vec<u8> = vec![1, 1, 1, 1, 1, 1, 1, 1];
let test = reinterpret(v);
println!("{:?}", test);
}

然而,这里有一个明显的问题。来自 the shrink_to_fit documentation :

It will drop down as close as possible to the length but the allocator may still inform the vector that there is space for a few more elements.



这是否意味着我的容量可能仍然不是 u32 大小的倍数?调用后 shrink_to_fit ?如果在 from_raw_parts我将容量设置为 v_len/4v.capacity()不是 4 的精确倍数,我会泄漏那 1-3 个字节,还是会因为 mem::forget 而回到内存池中在 v ?

我在这里忽略了其他任何问题吗?

我想搬家 v into reinterpret 保证从那时起无法访问它,因此只有一个所有者来自 mem::forget(v)继续打电话。

最佳答案

这是一个老问题,看起来在评论中有一个可行的解决方案。我刚刚写了这里到底出了什么问题,以及人们可能在今天的 Rust 中创建/使用的一些解决方案。
这是未定义的行为Vec::from_raw_parts是一个不安全的函数,因此你 必须满足它的不变量,或者你调用 undefined behavior .
引自 the documentation for Vec::from_raw_parts :

  • ptr needs to have been previously allocated via String/Vec (at least, it's highly likely to be incorrect if it wasn't).
  • T needs to have the same size and alignment as what ptr was allocated with. (T having a less strict alignment is not sufficient, the alignment really needs to be equal to satsify the dealloc requirement that memory must be allocated and deallocated with the same layout.)
  • length needs to be less than or equal to capacity.
  • capacity needs to be the capacity that the pointer was allocated with.

所以,回答你的问题,如果 capacity不等于原始 vec 的容量,那么你已经打破了这个不变量。这会给你未定义的行为。
请注意,该要求不在 size_of::<T>() * capacity 上但是,这将我们带到下一个主题。

Is there any other problem I am overlooking here?


三件事。
首先,所写的函数忽略了 from_raw_parts 的另一个要求。 .具体来说, T必须与原始对齐的大小相同 T . u32u8 的四倍,所以这再次打破了这个要求。即使 capacity*size保持不变, size不是,而且 capacity不是。这个功能在实现时永远不会健全。
其次,即使以上所有内容都是有效的,您也忽略了对齐。 u32必须对齐到 4 字节边界,而 Vec<u8>仅保证与 1 字节边界对齐。
对 OP 的评论提到:

I think on x86_64, misalignment will have performance penalty


值得注意的是,虽然这可能适用于机器语言,但不适用于 Rust。 The rust reference explicitly states “对齐 n 的值只能存储在 n 的倍数的地址中。”这是一个硬性要求。
为什么有确切的类型要求? Vec::from_raw_parts看起来很严格,这是有原因的。在 Rust 中,分配器 API 不仅操作分配大小,还操作 Layout ,它是大小、事物数量和单个元素对齐方式的组合。在 C 中使用 memalloc ,所有分配器可以依赖的是大小相同,以及一些最小对齐。然而,在 Rust 中,允许依赖整个 Layout , 否则调用未定义的行为。
所以为了正确释放内存, Vec需要知道分配给它的确切类型。通过转换 Vec<u32>进入 Vec<u8> ,它不再知道这个信息,因此它不能再正确地释放这个内存。
替代方案 - 转换切片 Vec::from_raw_parts的严格性来自于它需要释放内存这一事实。如果我们创建一个借用切片, &[u32]相反,我们不再需要处理它!转动时没有容量 &[u8]进入 &[u32] ,所以我们应该都很好,对吧?
嗯,差不多。您仍然需要处理对齐问题。 Primitives are generally aligned to their size , 所以一个 [u8]仅保证与 1 字节边界对齐,而 [u32]必须与 4 字节边界对齐。
如果你想碰碰运气,创建一个 [u32]如果可能的话,有一个函数 - <[T]>::align_to :
pub unsafe fn align_to<U>(&self) -> (&[T], &[U], &[T])

这将修剪任何开始和结束未对齐的值,然后在新类型的中间给你一个切片。这是不安全的,但您需要满足的唯一不变式是中间切片中的元素是有效的。
重新诠释 4 u8 听起来不错值作为 u32值(value),所以我们很好。
将所有这些放在一起,原始函数的声音版本将如下所示。这适用于借用而不是拥有的值(value),但考虑到重新解释拥有的 Vec在任何情况下都是即时未定义的行为,我认为可以肯定地说这是最接近的声音函数:
use std::mem;

fn reinterpret(v: &[u8]) -> Option<&[u32]> {
let (trimmed_front, u32s, trimmed_back) = unsafe { v.align_to::<u32>() };
if trimmed_front.is_empty() && trimmed_back.is_empty() {
Some(u32s)
} else {
// either alignment % 4 != 0 or len % 4 != 0, so we can't do this op
None
}
}

fn main() {
let mut v: Vec<u8> = vec![1, 1, 1, 1, 1, 1, 1, 1];
let test = reinterpret(&v);
println!("{:?}", test);
}
请注意,这也可以通过 std::slice::from_raw_parts 来完成。而不是 align_to .然而,这需要手动处理对齐,而它真正提供的只是我们需要确保我们做得正确的更多事情。嗯,这与旧编译器的兼容性 - align_to于 2018 年在 Rust 1.30.0 中推出,并且在提出此问题时不存在。
替代 - 复制
如果您确实需要 Vec<u32>对于长期数据存储,我认为最好的选择是分配新的内存。旧内存分配给 u8无论如何,并且不会起作用。
这可以通过一些函数式编程变得相当简单:
fn reinterpret(v: &[u8]) -> Option<Vec<u32>> {
let v_len = v.len();
if v_len % 4 != 0 {
None
} else {
let result = v
.chunks_exact(4)
.map(|chunk: &[u8]| -> u32 {
let chunk: [u8; 4] = chunk.try_into().unwrap();
let value = u32::from_ne_bytes(chunk);
value
})
.collect();
Some(result)
}
}
首先,我们使用 <[T]>::chunks_exact 迭代 4 的块 u8 s。接下来, try_into 转换自 &[u8][u8; 4] . &[u8]保证长度为 4,所以这永远不会失败。
我们使用 u32::from_ne_bytes 将字节转换为 u32使用本地字节序。如果与网络协议(protocol)或磁盘序列化交互,则使用 from_be_bytes from_le_bytes 可能更可取。最后,我们 collect 将我们的结果转回 Vec<u32> .
最后要注意的是,真正通用的解决方案可能会同时使用这两种技术。如果我们将返回类型更改为 Cow<'_, [u32]> ,如果有效,我们可以返回对齐的借用数据,如果无效,则分配一个新数组!不是两全其美,但很接近。

关于rust - 如果我调用 Vec::from_raw_parts 的容量小于指针实际容量,会发生什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38840330/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com