Rust 切片(Slice)在内存中是如何表示的?
在 Rust 中,切片(Slice,例如 &[T] 或 &str)在内存中的表示被称为 “胖指针” (Fat Pointer)。
与 C 语言中只包含内存地址的普通指针不同,Rust 的切片包含两个部分的数据。
内存结构
一个切片在底层实际上是一个结构体(虽然在语言层面是内置类型),它占用两个机器字长(Two machine words):
- 指针 (Pointer): 指向数据序列中第一个元素的内存地址。
- 长度 (Length): 切片中包含的元素个数(注意是元素的个数,不是字节数,除非元素类型是
u8)。
在 64 位系统上,一个切片占用 16 字节(8 字节指针 + 8 字节长度)。
图解表示
假设我们有一个 Vec<i32>,数据存储在堆上,然后我们创建了一个指向其中一部分数据的切片。
plaintext
let data = vec![10, 20, 30, 40, 50]; // data 在堆上
let slice = &data[1..4]; // slice 包含 [20, 30, 40]
内存布局如下:
plaintext
栈 (Stack) 堆 (Heap)
+-------------------+ +-------------------+
| slice | | data |
+---------+---------+ +-------------------+
| ptr | ●----+--------------> | 10 (index 0) |
+---------+---------+ +-------------------+
| len | 3 | | 20 (index 1) <---- start of slice
+---------+---------+ +-------------------+
| 30 (index 2) |
+-------------------+
| 40 (index 3) |
+-------------------+
| 50 (index 4) |
+-------------------+
- ptr: 指向堆内存中
20的地址(即切片的起始位置)。 - len: 值为
3,表示该切片包含 3 个i32元素。
代码验证
我们可以通过 std::mem::size_of 来验证切片的大小是普通指针的两倍,也可以通过 unsafe 代码查看其原始部分。
plaintext
use std::mem;
fn main() {
// 1. 验证大小
let ptr_size = mem::size_of::<&i32>(); // 普通引用
let slice_size = mem::size_of::<&[i32]>(); // 切片引用
println!("普通指针大小: {} bytes", ptr_size); // 64位系统通常是 8
println!("切片大小: {} bytes", slice_size); // 64位系统通常是 16 (8 ptr + 8 len)
// 2. 查看内部结构
let arr = [1, 2, 3, 4, 5];
let slice = &arr[1..3]; // 包含 [2, 3]
// 将切片强转为原始部分 (ptr, len)
// 在 Rust 内部,切片可以用 std::slice::from_raw_parts 重组
let (ptr, len) = (slice.as_ptr(), slice.len());
println!("切片指针地址: {:p}", ptr);
println!("切片长度: {}", len);
println!("数组元素2地址: {:p}", &arr[1]); // 应该与切片指针地址相同
}
特殊情况:字符串切片 (&str)
字符串切片 &str 的内存表示与 &[u8] 完全相同,也是一个胖指针:
- ptr: 指向 UTF-8 字节序列的起始位置。
- len: 字节的长度(注意:这里是字节数,因为
str底层是u8,但必须保证是合法的 UTF-8 序列)。
总结与对比
| 类型 | 组成部分 | 说明 |
|---|---|---|
普通引用 (&T) |
ptr |
只是一个内存地址。 |
切片 (&[T]) |
ptr + len |
地址 + 元素个数。不拥有数据的所有权。 |
向量 (Vec<T>) |
ptr + len + cap |
地址 + 元素个数 + 容量。拥有数据的所有权,可以增长。 |
核心理解: 切片是一个视图 (View)。它不拥有数据,只是通过 ptr 和 len 描述了“从哪里开始,到哪里结束”的一段连续内存。