Skip to content

指针与内存底层逻辑

1.指针基础

指针大小与架构的关系

  • 指针本身是一个变量,用于存储内存地址.
  • 指针的宽度总是与 CPU 的寻址能力(即 usize 类型的大小)一致:
架构指针大小位数寻址能力
64 位(x86_64, Apple Silicon)8 字节64 bits2⁶⁴ 个内存地址
32 位(WASM, 老式 ARM)4 字节32 bits2³² 个内存地址
16 位(某些嵌入式设备)2 字节16 bits2¹⁶ 个内存地址

瘦指针与胖指针

Rust 中有两种主要指针形态:

瘦指针 (Thin Pointers)

  • 指向普通具体类型(&i32*const u8Box<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 bit64 bit
理论内存上限4 GB18.4 EB
usize 大小4 字节8 字节
指针大小4 字节8 字节

:现代 64 位 CPU 实际仅使用 48-57 位物理寻址(已足够支撑数百 TB),但 Rust 在语言层面统一按 64 位处理,保持逻辑一致性.

3.指针的位置与大小表示

普通指针 (Thin Pointer)

  • 栈占用:8 字节
  • 存储内容:仅起始地址
  • 大小确定:
    • 固定类型(*const i32):编译器硬编码读取 4 字节
    • 数组指针:由程序员手动记录长度(C 风格)
  • 典型代表:&i32Box<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(内存管理单元)将虚拟地址映射到物理地址
  • 意义:实现进程隔离,每个程序都觉得拥有独立的完整地址空间

5.Rust 指针变化案例

rust
let mut x = vec![1, 2, 3];
let y = &mut x;
y.push(4);

以上面的代码为例,追踪指针变化

指针变化示例

为什么安全: y 是唯一的可变引用,搬家后由 y 将新地址 0xB000 原子回写到 x 的栈信息.没有任何其他引用持有旧地址 0xA000,所以不存在野指针.

基于 MIT 协议发布