Rust

2025年07月13日更新 6 人订阅
原价: ¥ 6 限时优惠
专栏简介 Rust编程语言之错误处理 Rust 语言之 flod Rust编程语言之Cargo、Crates.io详解 Rust编程语言之枚举与模式匹配 Rust语言 - 接口设计的建议之受约束(Constrained) Rust编程语言之无畏并发 Rust语言 - 接口设计的建议之灵活(flexible) Rust语言 - 接口设计的建议之显而易见(Obvious) Rust语言 - 接口设计的建议之不意外(unsurprising) Rust 实战:构建实用的 CLI 工具 HTTPie Rust编程语言学习之高级特性 Rust内存管理揭秘:深度剖析指针与智能指针 解决Rust中数组和切片的编译时大小问题 《Rust编程之道》学习笔记一 Rust Async 异步编程 简易教程 使用 Async Rust 构建简单的 P2P 节点 Rust编程语言入门之模式匹配 Rust async 编程 Rust编程语言之编写自动化测试 Rust编程语言之函数式语言特性:迭代器和闭包 《Rust编程之道》学习笔记二 Rust Tips 比较数值 使用 Rust 开发一个微型游戏 Rust编程初探:深入理解Struct结构体 深入理解Rust中的内存管理:栈、堆与静态内存详解 深入理解 Rust 结构体:经典结构体、元组结构体和单元结构体的实现 深入掌握 Rust 结构体:从模板到实例化的完整指南 深入理解Rust中的结构体:逻辑与数据结合的实战示例 深入理解 Rust 枚举:从基础到实践 掌握Rust字符串的精髓:String与&str的最佳实践 全面解析 Rust 模块系统:实战案例与应用技巧 Rust 中的 HashMap 实战指南:理解与优化技巧 掌握Rust模式匹配:从基础语法到实际应用 Rust 中的面向对象编程:特性与实现指南 深入理解 Rust 的 Pin 和 Unpin:理论与实践解析 Rust Trait 与 Go Interface:从设计到实战的深度对比 从零开始:用 Rust 和 Axum 打造高效 Web 应用 Rust 错误处理详解:掌握 anyhow、thiserror 和 snafu Rust 如何优雅实现冒泡排序 链表倒数 K 节点怎么删?Python/Go/Rust 实战 用 Rust 玩转数据存储:JSON 文件持久化实战 Rust实战:打造高效字符串分割函数 如何高效学习一门技术:从知到行的飞轮效应 Rust 编程入门:Struct 让代码更优雅 Rust 编程:零基础入门高性能开发 用 Rust 写个猜数游戏,编程小白也能上手! Rust 入门教程:变量到数据类型,轻松掌握! 深入浅出 Rust:函数、控制流与所有权核心特性解析 从零开始:用 Rust 和 Axum 打造高效 Web 服务 Rust 集合类型解析:Vector、String、HashMap 深入浅出Rust:泛型、Trait与生命周期的硬核指南 Rust实战:博物馆门票限流系统设计与实现 用 Rust 打造高性能图片处理服务器:从零开始实现类似 Thumbor 的功能 Rust 编程入门实战:从零开始抓取网页并转换为 Markdown 深入浅出 Rust:高效处理二进制数据的 Bytes 与 BytesMut 实战 Rust智能指针:解锁内存管理的进阶之道 用 Rust 打造命令行利器:从零到一实现 mini-grep 解锁Rust代码组织:轻松掌握Package、Crate与Module Rust 所有权:从内存管理到生产力释放 深入解析 Rust 的面向对象编程:特性、实现与设计模式 Rust + Protobuf:从零打造高效键值存储项目 bacon 点燃 Rust:比 cargo-watch 更爽的开发体验 用 Rust 打造微型游戏:从零开始的 Flappy Dragon 开发之旅 函数式编程的Rust之旅:闭包与迭代器的深入解析与实践 探索Rust编程之道:从设计哲学到内存安全的学习笔记 精读《Rust编程之道》:吃透语言精要,彻底搞懂所有权与借用 Rust 避坑指南:搞定数值比较,别再让 0.1 + 0.2 != 0.3 困扰你! 告别 Vec!掌握 Rust bytes 库,解锁零拷贝的真正威力 告别竞态条件:基于 Axum 和 Serde 的 Rust 并发状态管理最佳实践 Rust 异步编程实践:从 Tokio 基础到阻塞任务处理模式 Rust 网络编程实战:用 Tokio 手写一个迷你 TCP 反向代理 (minginx) 保姆级教程:Zsh + Oh My Zsh 终极配置,让你的 Ubuntu 终端效率倍增 不止于后端:Rust 在 Web 开发中的崛起之路 (2024数据解读) Rust核心利器:枚举(Enum)与模式匹配(Match),告别空指针,写出优雅健壮的代码 Rust 错误处理终极指南:从 panic! 到 Result 的优雅之道 想用 Rust 开发游戏?这份超详细的入门教程请收好! 用 Rust 实现 HTTPie:一个现代 CLI 工具的构建过程

Rust内存管理揭秘:深度剖析指针与智能指针

Rust内存管理揭秘:深度剖析指针与智能指针指针和智能指针是Rust内存管理的基石。理解它们不仅能帮助你编写更高效、安全的代码,还能让你在开发中更好地控制和管理资源。本文将引导你从基础知识开始,逐步深入探讨指针在Rust中的应用,帮助你成为一名更加出色的Rust开发者。指针什么是指针指针是

Rust内存管理揭秘:深度剖析指针与智能指针

指针和智能指针是Rust内存管理的基石。理解它们不仅能帮助你编写更高效、安全的代码,还能让你在开发中更好地控制和管理资源。本文将引导你从基础知识开始,逐步深入探讨指针在Rust中的应用,帮助你成为一名更加出色的Rust开发者。

指针

什么是指针

  • 指针是计算机引用无法立即直接访问的数据的一种方式(类比 书的目录)
  • 数据在物理内存(RAM)中是分散的存储着
  • 地址空间是检索系统
  • 指针就被编码为内存地址,使用 usize 类型的整数表示。
    • 一个地址就会指向地址空间中的某个地方
  • 地址空间的范围是 OS 和 CPU 提供的外观界面
    • 程序只知道有序的字节序列,不会考虑系统中实际 RAM 的数量

名词解释

  • 内存地址(地址),就是指代内存中单个字节的一个数
    • 内存地址是汇编语言提供的抽象
  • 指针(有时扩展称为原始指针),就是指向某种类型的一个内存地址
    • 指针是高级语言提供的抽象
  • 引用,就是指针。如果是动态大小的类型,就是指针和具有额外保证的一个整数
    • 引用是 Rust 提供的抽象

Rust 的引用

  • 引用始终引用的是有效数据
  • 引用与 usize 的倍数对齐
  • 引用可以为动态大小的类型提供上述保障

Rust 的引用 和 指针

static B: [u8; 10] = [99, 97, 114, 114, 121, 116, 111, 119, 101, 108];
static C: [u8; 11] = [116, 104, 97, 110, 107, 115, 102, 105, 115, 104, 0];

fn main() {
    let a = 42;
    let b = &B;
    let c = &C;

    println!("a: {}, b: {:p}, c: {:p}", a, b, c);
}

运行

point_demo on  master [?] is 📦 0.1.0 via 🦀 1.67.1 via 🅒 base 
➜ cargo run           
   Compiling point_demo v0.1.0 (/Users/qiaopengjun/rust/point_demo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.44s
     Running `target/debug/point_demo`
a: 42, b: 0x1023dc660, c: 0x1023dc66a

point_demo on  master [?] is 📦 0.1.0 via 🦀 1.67.1 via 🅒 base 
➜ 
  • 一个更加逼真的例子
    • 使用更复杂的类型展示指针内部的区别
use std::mem::size_of;

static B: [u8; 10] = [99, 97, 114, 114, 121, 116, 111, 119, 101, 108];
static C: [u8; 11] = [116, 104, 97, 110, 107, 115, 102, 105, 115, 104, 0];

fn main() {
    // let a = 42;
    // let b = &B;
    // let c = &C;

    // println!("a: {}, b: {:p}, c: {:p}", a, b, c);

    let a: usize = 42;
    let b: Box<[u8]> = Box::new(B);
    let c: &[u8; 11] = &C;

    println!("a (unsigned 整数):");
    println!("  地址: {:p}", &a);
    println!("  大小:    {:?} bytes", size_of::<usize>());
    println!("  值:  {:?}\n", a);

    println!("b (B 装在 Box 里):");
    println!("  地址:  {:p}", &b);
    println!("  大小:    {:?} bytes", size_of::<Box<[u8]>>());
    println!("  指向:  {:p}\n", b);

    println!("c (C 的引用):");
    println!("  地址:  {:p}", &c);
    println!("  大小:  {:?} bytes", size_of::<&[u8; 11]>());
    println!("  指向:  {:p}\n", c);

    println!("B (10 bytes 的数组):");
    println!("  地址:  {:p}", &B);
    println!("  大小:  {:?} bytes", size_of::<[u8; 10]>());
    println!("  值:  {:?}\n", B);

    println!("C (11 bytes 的数字):");
    println!("  地址:  {:p}", &C);
    println!("  大小:  {:?} bytes", size_of::<[u8; 11]>());
    println!("  值:  {:?}\n", C);
}

运行

point_demo on  master [?] is 📦 0.1.0 via 🦀 1.67.1 via 🅒 base 
➜ cargo run
   Compiling point_demo v0.1.0 (/Users/qiaopengjun/rust/point_demo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.19s
     Running `target/debug/point_demo`
a (unsigned 整数):
  地址: 0x16dda9a08
  大小:    8 bytes
  值:  42

b (B 装在 Box 里):
  地址:  0x16dda9a10
  大小:    16 bytes
  指向:  0x12b606ba0

c (C 的引用):
  地址:  0x16dda9a30
  大小:  8 bytes
  指向:  0x10208d7ba

B (10 bytes 的数组):
  地址:  0x10208d7b0
  大小:  10 bytes
  值:  [99, 97, 114, 114, 121, 116, 111, 119, 101, 108]

C (11 bytes 的数字):
  地址:  0x10208d7ba
  大小:  11 bytes
  值:  [116, 104, 97, 110, 107, 115, 102, 105, 115, 104, 0]

point_demo on  master [?] is 📦 0.1.0 via 🦀 1.67.1 via 🅒 base 
➜ 
  • 对 B 和 C 中文本进行解码的例子
    • 它创建了一个与前图更加相似的内存地址布局
use std::borrow::Cow;
use std::ffi::CStr;
use std::os::raw::c_char;

static B: [u8; 10] = [99, 97, 114, 114, 121, 116, 111, 119, 101, 108];
static C: [u8; 11] = [116, 104, 97, 110, 107, 115, 102, 105, 115, 104, 0];

fn main() {
  let a = 42;
  let b: String;
  let c: Cow<str>;

  unsafe {
    let b_ptr = &B as * const u8 as *mut u8;
    b = String::from_raw_parts(b_ptr, 10, 10);

    let c_ptr = &C as *const u8 as *const c_char;
    c = CStr::from_ptr(c_ptr).to_string_lossy();
  }
  println!("a: {}, b: {}, c: {}", a, b, c);
}

Raw Pointers(原始指针)

  • Raw Pointer (原始指针)是没有 Rust 标准保障的内存地址。
    • 这些本质上是 unsafe 的
  • 语法:
    • 不可变 Raw Pointer:*const T
    • 可变的 Raw Pointer:*mut T
    • 注意:*const T,这三个标记放在一起表示的是一个类型
    • 例子:*const String
  • const T 与mut T 之间的差异很小,相互可以自由转换
  • Rust 的引用(&mut T 和 &T)会编译为原始指针
    • 这意味着无需冒险进入 unsafe 块,就可以获得原始指针的性能
  • 例子:把引用转为原始指针
fn main() {
  let a: i64 = 42;
  let a_ptr = &a as *const i64;

  println!("a: {} ({:p})", a, a_ptr);
}
  • 解引用(dereference):通过指针从 RAM 内存提取数据的过程叫做对指针进行解引用(dereferencing a pointer)
  • 例子:把引用转为原始指针
fn main() {
  let a: i64 = 42;
  let a_ptr = &a as *const i64;
  let a_addr: usize = unsafe {std::mem::transmute(a_ptr)};

  println!("a: {} ({:p}...0x{:x})", a, a_ptr, a_addr + 7);
}

关于 Raw Pointer 的提醒

  • 在底层,引用(&T 和 &mutT)被实现为原始指针。但引用带有额外的保障,应该始终作为首选使用
  • 访问 Raw Pointer 的值总是 unsafe 的
  • Raw Pointer 不拥有值的所有权
    • 在访问时编译器不会检查数据的合法性
  • 允许多个 Raw Pointer 指向同一数据
    • Rust 无法保证共享数据的合法性

使用 Raw Pointer 的情况

  • 不可避免
    • 某些 OS 或 第三方库需要使用,例如与C交互
  • 共享对某些内容的访问至关重要,运行时性能要求高

Rust 指针生态

  • Raw Pointer 是 unsafe 的
  • Smart Pointer(智能指针)倾向于包装原始指针,附加更多的能力
    • 不仅仅是对内存地址解引用

Rust 智能指针

名称 简介 强项 弱项
Raw Pointer mut T 和const T,自由基,闪电般块,极其 Unsafe 速度、与外界交互 Unsafe
Box<T> 可把任何东西都放在Box里。可接受几乎任何类型的长期存储。新的安全编程时代的主力军。 将值集中存储在 Heap 大小增加
Rc<T> 是Rust的能干而吝啬的簿记员。它知道谁借了什么,何时借了什么 对值的共享访问 大小增加;运行时成本;线程不安全
Arc<T> 是Rust的大使。它可以跨线程共享值,保证这些值不会相互干扰 对值的共享访问;线程安全 大小增加;运行时成本
Cell<T> 变态专家,具有改变不可变值的能力 内部可变性 大小增加;性能
RefCell<T> 对不可变引用执行改变,但有代价 内部可变性;可与仅接受不可变引用的Rc、Arc嵌套使用 大小增加;运行时成本;缺乏编译时保障
Cow<T> 封闭并提供对借用数据的不可变访问,并在需要修改或所有权时延迟克隆数据 当只是只读访问时避免写入 大小可能会增大
String 可处理可变长度的文本,展示了如何构建安全的抽象 动态按需增长;在运行时保证正确编码 过度分配内存大小
Vec<T> 程序最常用的存储系统;它在创建和销毁值时保持数据有序 动态按需增长 过度分配内存大小
RawVec<T> Vec<T>和其它动态大小类型的基石;知道如何按需给你的数据提供一个家 动态按需增长;与内存分配器一起配合寻找空间 不直接适用于您的代码
Unique<T> 作为值的唯一所有者,可保证拥有完全控制权 需要独占值的类型(如 String)的基础 不适合直接用于应用程序代码
Shared<T> 分享所有权很难,但他使生活更轻松 共享所有权;可以将内存与T的宽度对齐,即使是空的时候 不适合直接用于应用程序代码

参考

点赞 1
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论