Skip to content

20.2 内存扩容

接下来我们分析一下 Vec::push 这个方法是如何实现的。源码如下:


rust
pub fn push(&mut self, value: T) {
    if self.len == self.buf.cap() {
        self.buf.double();
    }
    unsafe {
        let end = self.as_mut_ptr().offset(self.len as isize);
        ptr::write(end, value);
        self.len += 1;
    }
}

首先判断当前是否还有空余容量,如果不够,就调用 RawVec 的 double 方法;如果足够,直接走下面的逻辑。接下来就是把数据插入 Vec 的做法。这里直接使用了 ptr::write 方法,它做的事情其实就是简单地把数据按位复制到目标位置。请注意,在 Rust 中这么做是完全正确的,因为它没有“复制构造函数”“移动构造函数”“复制运算符重载”之类的东西,如果我们要把一个对象 move 到另外一个地方,那就只需要把这个对象按位复制到目的地址即可。当然我们还要防止对象在原来那个地方调用析构函数,恰好 ptr::write 方法可以满足这个要求。

接下来继续看一下 RawVec::double 方法是怎么实现的:


rust
pub fn double(&mut self) {
  unsafe {
      let elem_size = mem::size_of::<T>();

      let (new_cap, uniq) = match self.current_layout() {
          Some(cur) => {
              let new_cap = 2 * self.cap;
              let new_size = new_cap * elem_size;
              let new_layout = Layout::from_size_align_unchecked(new_size, cur. align());
              alloc_guard(new_size);
              let ptr_res = self.a.realloc(self.ptr.as_ptr() as *mut u8,
                                      cur,
                                      new_layout);
              match ptr_res {
                  Ok(ptr) => (new_cap, Unique::new_unchecked(ptr as *mut T)),
                  Err(e) => self.a.oom(e),
              }
          }
          None => {
              let new_cap = if elem_size > (!0) / 8 { 1 } else { 4 };
              match self.a.alloc_array::<T>(new_cap) {
                  Ok(ptr) => (new_cap, ptr),
                  Err(e) => self.a.oom(e),
              }
          }
      };
      self.ptr = uniq;
      self.cap = new_cap;
  }
}

其中 RawVec::current_layout 方法的实现如下:


rust
fn current_layout(&self) -> Option<Layout> {
    if self.cap == 0 {
        None
    } else {
        unsafe {
            let align = mem::align_of::<T>();
            let size = mem::size_of::<T>() * self.cap;
            Some(Layout::from_size_align_unchecked(size, align))
        }
    }
}

由此可见,如果当前 capacity 是 0,即一开始用 Vec::new()方法初始化的情况下,新的容量一般设置为 4,除非这个元素特别大。对于当前 capacity 不是 0 的情况,会调用 allocator 的 realloc 方法,申请一个两倍于当前大小的空间。

Released under the MIT License