指针与内存底层逻辑
1.指针基础
指针大小与架构的关系
- 指针本身是一个变量,用于存储内存地址.
- 指针的宽度总是与 CPU 的寻址能力(即 usize 类型的大小)一致:
| 架构 | 指针大小 | 位数 | 寻址能力 |
|---|---|---|---|
| 64 位(x86_64, Apple Silicon) | 8 字节 | 64 bits | 2⁶⁴ 个内存地址 |
| 32 位(WASM, 老式 ARM) | 4 字节 | 32 bits | 2³² 个内存地址 |
| 16 位(某些嵌入式设备) | 2 字节 | 16 bits | 2¹⁶ 个内存地址 |
瘦指针与胖指针
Rust 中有两种主要指针形态:
瘦指针 (Thin Pointers)
- 指向普通具体类型(
&i32、*const u8、Box<u32>) - 只包含内存地址,64 位系统上是 8 字节
- 类型系统在编译阶段硬编码大小
胖指针 (Fat Pointers)
- 指向动态大小类型(DST),如切片
&[T]或特征对象&dyn Trait - 包含内存地址 + 元数据,64 位系统上通常是 16 字节(2 个
usize) - 切片:元数据是长度
- 特征对象:元数据是虚函数表指针
代码验证
rust
use std::mem::size_of;
fn main() {
println!("普通指针: {} 字节", size_of::<&i32>()); // 输出: 8
println!("切片(胖指针): {} 字节", size_of::<&[i32]>()); // 输出: 16
}2.寻址能力与 usize
寻址能力概念
内存可视为一条超长街道,每个字节是一间房子:
- 32 位系统:最多管理 2³² ≈ 4 GB 内存
- 64 位系统:理论寻址空间达 2⁶⁴ ≈ 18 EB(足够未来使用)
usize:随架构变化的类型
usize 是"变色龙"类型:
- 64 位机器:
usize=u64(8 字节) - 32 位机器:
usize=u32(4 字节)
设计原因:usize 用于数组索引和指针偏移.指针大小取决于寻址能力,索引类型也必须同步.若 64 位系统用 u32 做下标,无法访问 4GB 以外的数据.
指针与 usize 的关系
在底层,指针本质上就是一个存储了内存编号的 usize,代表内存的"门牌号".因此在 64 位系统上,普通指针大小正好是 8 字节.
系统对比
| 特性 | 32 位系统 | 64 位系统 |
|---|---|---|
| CPU 寄存器宽度 | 32 bit | 64 bit |
| 理论内存上限 | 4 GB | 18.4 EB |
usize 大小 | 4 字节 | 8 字节 |
| 指针大小 | 4 字节 | 8 字节 |
注:现代 64 位 CPU 实际仅使用 48-57 位物理寻址(已足够支撑数百 TB),但 Rust 在语言层面统一按 64 位处理,保持逻辑一致性.
3.指针的位置与大小表示
普通指针 (Thin Pointer)
- 栈占用:8 字节
- 存储内容:仅起始地址
- 大小确定:
- 固定类型(
*const i32):编译器硬编码读取 4 字节 - 数组指针:由程序员手动记录长度(C 风格)
- 固定类型(
- 典型代表:
&i32、Box<i64>、*mut u8
胖指针 (Fat Pointer)
- 栈占用:16 字节(2 个
usize) - 存储内容:起始地址 + 元数据
- 切片:长度,总字节数 = 长度 ×
size_of::<T>() - 特征对象:虚函数表地址(vtable),包含该类型的尺寸和对齐方式)
- 切片:长度,总字节数 = 长度 ×
- 典型代表:
&[u8]、&str、&dyn Debug
对比表
| 指针类型 | 占用空间 | 记录内容 | 结束位置确定方式 |
|---|---|---|---|
| 普通指针 | 8 字节 | 起始地址 | 编译期固定类型或手动管理 |
| 切片胖指针 | 16 字节 | 起始地址 + 长度 | 起始地址 + (长度 × 元素大小) |
| 特征胖指针 | 16 字节 | 起始地址 + vtable | 从 vtable 元数据读取 |
设计原则
- 固定大小类型:用普通指针(8 字节)
- 动态大小类型:用胖指针(16 字节,携带元数据)
按需付费,灵活高效.
4.虚拟地址与物理内存
- 物理内存:实际插在主板上的内存条
- 虚拟地址:程序看到的地址
- 转换机制:操作系统通过 MMU(内存管理单元)将虚拟地址映射到物理地址
- 意义:实现进程隔离,每个程序都觉得拥有独立的完整地址空间