【问题标题】:Why does calling Vec::resize before calling Vec::set_len cause the Vec to have data?为什么在调用 Vec::set_len 之前调用 Vec::resize 会导致 Vec 有数据?
【发布时间】:2017-08-20 20:12:33
【问题描述】:

我有一个不明白的问题:

fn cipher_with(key: &[u8], data: &[u8]) -> Vec<u8> {
    let data_len = 16;

    let mut data = data.to_vec();
    data.resize(data_len, 2);

    let mut output = Vec::<u8>::with_capacity(data_len);
    unsafe { output.set_len(data_len) }

    output
}

fn main() {
    let key = "blabla".as_bytes();
    let data = "lorem ipsum.".as_bytes();
    println!("{:?}", cipher_with(&key, &data));
}

打印出来:

[108, 111, 114, 101, 109, 32, 105, 112, 115, 117, 109, 46, 0, 0, 0, 0]

但是它是怎么做的呢?我从来没有把这个值给output

【问题讨论】:

  • "我从来没有把这个值给output" => 正确。事实上,您从未向output 提供任何数据。 with_capacity()set_len() 都没有初始化内存,这就是为什么后者甚至被标记为 unsafe。为什么您希望output 保存任何有用的数据? [顺便说一句:欢迎来到 StackOverflow!如果您对此格式有任何疑问,请在 cmets 中询问]
  • @LukasKalbertodt 包含lorem ipsum. 只是巧合吗?
  • @zerkms 哇,我没注意到。答案可能就是弗朗西斯在下面所说的。但它让我“wat”了一会儿......°_°
  • @LukasKalbertodt 他们的回答听起来并不令人信服,因为.resize 应该就位。特别是当你缩小它的时候。
  • 如果有人提出文档问题 - 请在此处放置链接。

标签: arrays vector rust slice


【解决方案1】:

您正在使用不安全的 Rust,这会给您带来不可预测的结果。

在这种特殊情况下,您将Vec 的大小扩展到未初始化的内存中。这些值是已经存在的任何东西。

那么让我们看一些代码:

let mut data = data.to_vec();

这会复制数据“lorem ipsum”。以向量的形式放到堆上。

data.resize(data_len, 2); // data_len = 16

这将Vec 的容量从 12 个增加到 16 个,在这种情况下恰好是字节。但实际上,根据我们所看到的,看起来实现(或者可能是优化器)认为最好放弃第一个分配的内存范围并将数据复制到新内存。

let mut output = Vec::<u8>::with_capacity(data_len);
unsafe { output.set_len(data_len) }

这会创建一个新向量,不安全地给它一个长度。但是你没有初始化它,所以数据会是以前的。

看起来data.resize() 实际上复制了值,而不是仅仅删除向量的末尾。当output 被分配时,它被分配了与之前使用相同的内存块,这就是它包含"lorem ipsum." 的原因。

【讨论】:

  • data.resize 可能会分配具有请求长度的新存储,复制数据,然后释放旧存储。在创建 output 时,它看起来像是重用了 data 向量的旧存储。
  • @FrancisGagné doc.rust-lang.org/std/vec/struct.Vec.html#method.resize "就地调整 Vec 的大小,使 len() 等于 new_len。"
  • @zerkms 我不确定这里的文档,但在某些时候它必须分配。如果它比旧容量长,它就不能就位。可能是文档错误(或至少不够清楚)。如果您查看the source code,您可以看到reserve() 将被调用。
  • @LukasKalbertodt 是的,在这种情况下,新的长度更短,所以它必须截断。
  • @Shepmaster 那么这没什么意义。集合的就地操作是一个既定术语,这意味着它在内存方面是次线性的。
【解决方案2】:

要向Peter's answer 添加一些详细信息,请查看这个带注释的版本:

fn cipher_with(key: &[u8], data: &[u8]) -> Vec<u8> {
    let data_len = 16;

    let mut data = data.to_vec();
    println!("{:?}", data.as_ptr());
    data.resize(data_len, 2);
    println!("{:?}", data.as_ptr());

    let mut output = Vec::<u8>::with_capacity(data_len);
    println!("{:?}", output.as_ptr());
    unsafe { output.set_len(data_len) }

    output
}
0x7fa6dba27000
0x7fa6dba1e0c0
0x7fa6dba27000

创建第一个向量时,它的长度为 12。当它的大小调整为 16 时,会进行新的分配并复制数据。这可能是由于分配器的实现,它通常将分配分块到桶中。 16 是一个合理的桶大小。

当第二个向量被创建时,分配器返回第一个向量刚刚放弃的指针。由于同时没有其他任何东西改变了这个内存,它仍然包含data中的任何数据。

【讨论】:

    猜你喜欢
    • 2020-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-28
    • 1970-01-01
    相关资源
    最近更新 更多