Categories
Tags
algorithms APIT arm assembly asynchronous base64 Blogging box c clang-format cmake compiler concurrency const_fn contravariant cos covariant cpp Customization cybersecurity DataStructure db Demo deserialization discrete doc DP Dynamic Example FFI flat_map FP Functional functions futures Fuwari GATs gccrs generics gitignore GUI hacking hashmap haskell heap interop invariant iterator justfile kernel LaTeX LFU linux MachineLearning Markdown math ML OnceLock optimization OS parallels perf physics pin postgresql release RPIT rust science Science serialization shift sin SmallProjects std String surrealdb swisstable synchronous tan traits triangulation utf16 utf8 Video x86_64 xilem zig
1078 words
5 minutes
240704_Array_vs_Vec_part2_memory
link
array와 Vec를 메모리를 찍어보며 구성을 예측해보자|🔝|
벡터 인덱스별 메모리 주소 조회해 보기
fn main() {
let my_vec = vec![1, 2, 3, 4, 5];
for (i, val) in my_vec.iter().enumerate() {
println!("index [{}], = {} , mem = {:p}", i, val, val)
}
}- result
index [0], = 1 , mem = 0xa9f0009a0
index [1], = 2 , mem = 0xa9f0009a4
index [2], = 3 , mem = 0xa9f0009a8
index [3], = 4 , mem = 0xa9f0009ac
index [4], = 5 , mem = 0xa9f0009b0Debug the memory with Rust vector and create an example of how it works.
Below is a hands-on, “debug-the-memory” explanation of Vec
in Rust, with real code, pointer addresses, and CLI-style diagrams so you can see how it works. I’ll proceed step by step, like a debugger walkthrough.
1. What is a Vec<T> in memory?
- A
Vec<T>is not the elements themselves. It is a small struct on the stack that points to heap memory.
Conceptually (simplified):
struct Vec<T> {
ptr: *mut T, // heap pointer
len: usize, // number of initialized elements
cap: usize, // allocated capacity
}2. Minimal example: observing memory addresses
fn debug_vec(v: &Vec<i32>) {
println!("len = {}", v.len());
println!("cap = {}", v.capacity());
println!("ptr = {:p}", v.as_ptr());
for i in 0..v.len() {
unsafe {
println!(
" [{}] addr = {:p}, value = {}",
i,
v.as_ptr().add(i),
*v.as_ptr().add(i)
);
}
}
}
fn main() {
let mut v: Vec<i32> = Vec::new();
println!("== After Vec::new() ==");
debug_vec(&v);
v.push(10);
println!("\n== After push(10) ==");
debug_vec(&v);
v.push(20);
println!("\n== After push(20) ==");
debug_vec(&v);
v.push(30);
println!("\n== After push(30) ==");
debug_vec(&v);
}3. Sample output (annotated)
== After Vec::new() ==
len = 0
cap = 0
ptr = 0x0- Interpretation
STACK HEAP
+------------------+
| Vec |
| ptr = null | -----> (nothing allocated)
| len = 0 |
| cap = 0 |
+------------------+- after
== After push(10) ==
len = 1
cap = 4
ptr = 0x600003f8c000
[0] addr = 0x600003f8c000, value = 10What happened?
Rust allocated heap memory automatically.
STACK HEAP
+------------------+ +---------------------------+
| Vec | ---> | 10 | ?? | ?? | ?? |
| ptr = 0x6000... | +---------------------------+
| len = 1 | ↑
| cap = 4 | ptr
+------------------+
- Capacity is 4, not 1 → amortized growth
- Only index 0 is initialized
== After push(20) ==
len = 2
cap = 4
ptr = 0x600003f8c000
[0] addr = 0x600003f8c000, value = 10
[1] addr = 0x600003f8c004, value = 20- Notice
- Pointer did not change
- Elements are contiguous
- i32 = 4 bytes → addresses increase by 4
HEAP
addr+0x00 : 10
addr+0x04 : 20
addr+0x08 : ??
addr+0x0C : ??== After push(30) ==
len = 3
cap = 4
ptr = 0x600003f8c000- Still no reallocation.
4. Triggering reallocation (capacity growth)
fn main() {
let mut v = Vec::with_capacity(2);
for i in 0..10 {
v.push(i);
println!(
"push {:2} | len = {:2}, cap = {:2}, ptr = {:p}",
i,
v.len(),
v.capacity(),
v.as_ptr()
);
}
}- Typical output
push 0 | len = 1, cap = 2, ptr = 0x600003f8c000
push 1 | len = 2, cap = 2, ptr = 0x600003f8c000
push 2 | len = 3, cap = 4, ptr = 0x600003f90000 <-- moved!
push 3 | len = 4, cap = 4, ptr = 0x600003f90000
push 4 | len = 5, cap = 8, ptr = 0x600003f98000 <-- moved!WARNINGmoved를 재현하려고 했으나 실패 좀 더 검증이 필요함! ChatGPT는 반만 믿자.!
5. Reallocation visualized
- Before growth (cap = 2)
HEAP (old)
+----+----+
| 0 | 1 |
+----+----+- After push causes overflow
1) allocate new bigger block
2) memcpy old data
3) free old blockHEAP (new)
+----+----+----+----+
| 0 | 1 | 2 | ?? |
+----+----+----+----+- 🚨 All old pointers are now INVALID
6. Common debugging mistake (use-after-realloc)
fn main() {
let mut v = vec![1, 2];
let p = v.as_ptr(); // raw pointer to element[0]
v.push(3); // may reallocate
unsafe {
println!("{}", *p); // ❌ Undefined Behavior
}
}- Why?
p ---> old heap (freed!)
v ---> new heap- Rust prevents this in safe code, but raw pointers bypass checks
7. Vec vs Array [T; N] (memory-debug view)
Fixed array
let a = [1, 2, 3, 4];STACK ONLY
+----+----+----+----+
| 1 | 2 | 3 | 4 |
+----+----+----+----+Vec
let v = vec![1, 2, 3, 4];STACK HEAP
+------------------+ +----+----+----+----+
| ptr ------------ | --> | 1 | 2 | 3 | 4 |
| len = 4 | +----+----+----+----+
| cap = 4 |
+------------------+8. Debugging tips for Vec<T>
1. Print internal state
println!("len={}, cap={}, ptr={:p}", v.len(), v.capacity(), v.as_ptr());2. Watch for pointer changes
- If
as_ptr()changes → reallocation happened
3. Avoid storing raw pointers across push
- Store indices, not pointers.
4. Pre-allocate if you know the size
let mut v = Vec::with_capacity(1_000_000);9. Mental model (important)
Vec<T> = (stack metadata) + (heap buffer)
push() = may move the heap buffer
indexing = pointer arithmetic + bounds checkrust 실습코드
fn main() {
// 0 , 1, 2, 3
let arr = [1, 2, 3, 4, 5];
let arr02 = [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13];
println!("arr []: memory address : {:p}", &arr);
for (i, val) in arr.iter().enumerate() {
println!("arr[{}]: value={}, memory address : {:p}", i, val, val);
}
let my_vec: Vec<i32> = Vec::new();
let my_vec02 = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let my_vec03 = vec![0; 10];
println!("{my_vec03:?}");
println!("vec {} bytes", size_of_val(&my_vec03));
println!("my_vec02 32 bytes? : {}", size_of_val(&my_vec02));
println!("arr : {} bytes", size_of_val(&arr));
println!("arr02 : {} bytes", size_of_val(&arr02));
println!("my_vec 02 adress : {:p}", &my_vec02);
for (i, val) in my_vec02.iter().enumerate() {
println!("my_vec 02 [{}] adress : {:p}", i, &val);
}
println!("my_vec 02 [{}]: {:p}", 0, &my_vec02[0]);
println!("my_vec 02 [{}]: {:p}", 1, &my_vec02[1]);
}- result
arr []: memory address : 0x7fffecbadf90
arr[0]: value=1, memory address : 0x7fffecbadf90
arr[1]: value=2, memory address : 0x7fffecbadf94
arr[2]: value=3, memory address : 0x7fffecbadf98
arr[3]: value=4, memory address : 0x7fffecbadf9c
arr[4]: value=5, memory address : 0x7fffecbadfa0
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
vec 24 bytes
my_vec02 32 bytes? : 24
arr : 20 bytes
arr02 : 44 bytes
my_vec 02 adress : 0x7fffecbae0f0
my_vec 02 [0] adress : 0x7fffecbae290
my_vec 02 [1] adress : 0x7fffecbae290
my_vec 02 [2] adress : 0x7fffecbae290
my_vec 02 [3] adress : 0x7fffecbae290
my_vec 02 [4] adress : 0x7fffecbae290
my_vec 02 [5] adress : 0x7fffecbae290
my_vec 02 [6] adress : 0x7fffecbae290
my_vec 02 [7] adress : 0x7fffecbae290
my_vec 02 [8] adress : 0x7fffecbae290
my_vec 02 [9] adress : 0x7fffecbae290
my_vec 02 [0]: 0x5c6c0857bb50
my_vec 02 [1]: 0x5c6c0857bb54240704_Array_vs_Vec_part2_memory
https://younghakim7.github.io/blog/posts/240704_array_vs_vec_part2_memory/