diff --git a/src/c_vec.rs b/src/c_vec.rs
index 1a335cce02cd026eb6ea63ab680249ed7230b61b..fea7aeffff73a8999b1beca1d5b52bd1ebbfc32c 100644
--- a/src/c_vec.rs
+++ b/src/c_vec.rs
@@ -1,6 +1,10 @@
-use crate::platform::{self, types::*};
+use crate::{
+    io::{self, Write},
+    platform::{self, WriteByte, types::*},
+};
 use core::{
     cmp,
+    fmt,
     iter::IntoIterator,
     mem,
     ops::{Deref, DerefMut},
@@ -67,6 +71,9 @@ impl<T> CVec<T> {
             start = start.add(1);
         }
     }
+
+    // Push stuff
+
     pub fn reserve(&mut self, required: usize) -> Result<(), AllocError> {
         let reserved_len = self
             .len
@@ -82,8 +89,8 @@ impl<T> CVec<T> {
         Ok(())
     }
     pub fn push(&mut self, elem: T) -> Result<(), AllocError> {
+        self.reserve(1)?;
         unsafe {
-            self.reserve(1)?;
             ptr::write(self.ptr.as_ptr().add(self.len), elem);
         }
         self.len += 1; // no need to bounds check, as new len <= cap
@@ -93,21 +100,26 @@ impl<T> CVec<T> {
     where
         T: Copy,
     {
+        self.reserve(elems.len())?;
         unsafe {
-            self.reserve(elems.len())?;
             ptr::copy_nonoverlapping(elems.as_ptr(), self.ptr.as_ptr().add(self.len), elems.len());
         }
         self.len += elems.len(); // no need to bounds check, as new len <= cap
         Ok(())
     }
     pub fn append(&mut self, other: &mut Self) -> Result<(), AllocError> {
+        let len = other.len;
+        other.len = 0; // move
+        self.reserve(len)?;
         unsafe {
-            self.reserve(other.len())?;
-            ptr::copy_nonoverlapping(other.as_ptr(), self.ptr.as_ptr().add(self.len), other.len());
+            ptr::copy_nonoverlapping(other.as_ptr(), self.ptr.as_ptr().add(self.len), len);
         }
         self.len += other.len(); // no need to bounds check, as new len <= cap
         Ok(())
     }
+
+    // Pop stuff
+
     pub fn truncate(&mut self, len: usize) {
         if len < self.len {
             unsafe {
@@ -126,6 +138,18 @@ impl<T> CVec<T> {
         }
         Ok(())
     }
+    pub fn pop(&mut self) -> Option<T> {
+        if self.is_empty() {
+            None
+        } else {
+            let elem = unsafe { ptr::read(self.as_ptr().add(self.len - 1)) };
+            self.len -= 1;
+            Some(elem)
+        }
+    }
+
+    // Misc stuff
+
     pub fn capacity(&self) -> usize {
         self.cap
     }
@@ -176,3 +200,28 @@ impl<'a, T> IntoIterator for &'a mut CVec<T> {
         <&mut [T]>::into_iter(&mut *self)
     }
 }
+
+impl Write for CVec<u8> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.extend_from_slice(buf).map_err(|err| io::Error::new(
+            io::ErrorKind::Other,
+            "AllocStringWriter::write failed to allocate",
+        ))?;
+        Ok(buf.len())
+    }
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+impl fmt::Write for CVec<u8> {
+    fn write_str(&mut self, s: &str) -> fmt::Result {
+        self.write(s.as_bytes()).map_err(|_| fmt::Error)?;
+        Ok(())
+    }
+}
+impl WriteByte for CVec<u8> {
+    fn write_u8(&mut self, byte: u8) -> fmt::Result {
+        self.write(&[byte]).map_err(|_| fmt::Error)?;
+        Ok(())
+    }
+}
diff --git a/src/header/stdio/mod.rs b/src/header/stdio/mod.rs
index 2b22cf4ac57a29698309c9e228cb202b028046ba..7693cd2157255e5cce08876fbaa9f5b9f4ea907d 100644
--- a/src/header/stdio/mod.rs
+++ b/src/header/stdio/mod.rs
@@ -16,6 +16,7 @@ use core::{
 
 use crate::{
     c_str::CStr,
+    c_vec::CVec,
     fs::File,
     header::{
         errno::{self, STR_ERROR},
@@ -1021,9 +1022,11 @@ pub unsafe extern "C" fn vasprintf(
     format: *const c_char,
     ap: va_list,
 ) -> c_int {
-    let mut alloc_writer = platform::AllocStringWriter(ptr::null_mut(), 0);
+    let mut alloc_writer = CVec::new();
     let ret = printf::printf(&mut alloc_writer, format, ap);
-    *strp = alloc_writer.0 as *mut c_char;
+    alloc_writer.push(0).unwrap();
+    alloc_writer.shrink_to_fit().unwrap();
+    *strp = alloc_writer.leak() as *mut c_char;
     ret
 }
 
diff --git a/src/platform/mod.rs b/src/platform/mod.rs
index 5b63f1a997aa3cceca47d8fb54fdc373bf62fa5b..3978625e4f991ee2197ecb2a3746a9118cf4d8d3 100644
--- a/src/platform/mod.rs
+++ b/src/platform/mod.rs
@@ -104,45 +104,6 @@ impl Read for FileReader {
     }
 }
 
-pub struct AllocStringWriter(pub *mut u8, pub usize);
-impl Write for AllocStringWriter {
-    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
-        let ptr = unsafe { realloc(self.0 as *mut c_void, self.1 + buf.len() + 1) as *mut u8 };
-        if ptr.is_null() {
-            return Err(io::Error::new(
-                io::ErrorKind::Other,
-                "AllocStringWriter::write failed to allocate",
-            ));
-        }
-        self.0 = ptr;
-
-        unsafe {
-            ptr::copy_nonoverlapping(buf.as_ptr(), self.0.add(self.1), buf.len());
-            self.1 += buf.len();
-            *self.0.add(self.1) = 0;
-        }
-
-        Ok(buf.len())
-    }
-    fn flush(&mut self) -> io::Result<()> {
-        Ok(())
-    }
-}
-impl fmt::Write for AllocStringWriter {
-    fn write_str(&mut self, s: &str) -> fmt::Result {
-        // can't fail
-        self.write(s.as_bytes()).unwrap();
-        Ok(())
-    }
-}
-impl WriteByte for AllocStringWriter {
-    fn write_u8(&mut self, byte: u8) -> fmt::Result {
-        // can't fail
-        self.write(&[byte]).unwrap();
-        Ok(())
-    }
-}
-
 pub struct StringWriter(pub *mut u8, pub usize);
 impl Write for StringWriter {
     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
@@ -155,11 +116,14 @@ impl Write for StringWriter {
                 self.0 = self.0.add(copy_size);
                 *self.0 = 0;
             }
-
-            Ok(copy_size)
-        } else {
-            Ok(0)
         }
+
+        // Pretend the entire slice was written. This is because many functions
+        // (like snprintf) expects a return value that reflects how many bytes
+        // *would have* been written. So keeping track of this information is
+        // good, and then if we want the *actual* written size we can just go
+        // `cmp::min(written, maxlen)`.
+        Ok(buf.len())
     }
     fn flush(&mut self) -> io::Result<()> {
         Ok(())