从内存视角看 Rust 字符串复制:引用、Clone 与移动的奥秘

内容分享1周前发布
0 0 0

从内存视角看 Rust 字符串复制:引用、Clone 与移动的奥秘

Rust 复制字符串:栈和堆的 “复制大战”

一、字符串复制?不只是 “Ctrl+C” 那么简单

想象你有一本《编程圣经》(字符串数据):


  • 如果你只是在扉页写个 “借阅人:张三”(引用),书还是那本,只是多了个标记。
  • 如果你复印了一整本(深复制),那就有两本完全一样的书,改其中一本不影响另一本。
  • 如果你只复印了封面(浅复制),里面的内容还是共享的,改原书内容,复印件也会变(这就坑了)。


Rust 里复制字符串就像处理这本书,有三种 “复制姿势”,每种对栈和堆的操作都不一样。今天咱们就扒开内存看究竟,让栈和堆的 “小动作” 无所遁形~

二、准备工作:搭个 “字符串复制实验室”

  1. 创建项目(终端操作,老规矩):


bash

cargo new rust_string_copy  # 新建项目
cd rust_string_copy         # 进入项目


  1. 咱们所有代码都写在src/main.rs里,边写边看内存变化。

三、案例实操:三种复制方式的 “内存大戏”

案例 1:最 “抠门” 的复制 —— 引用(&str)

特点:不复制数据,只分享访问权,栈上多一个指针,堆上数据不动。


代码:


rust

fn main() {
    // 原字符串:栈上有String控制块,堆上有数据
    let s1 = String::from("Rust字符串");
    println!("s1: 地址={:p}, 内容={}", &s1, s1);

    // 引用复制:栈上新增一个&str,指向s1的堆数据
    let s2 = &s1;  // s2是引用,类似"看一眼s1的书"
    println!("s2: 地址={:p}, 指向的数据地址={:p}, 内容={}", &s2, s2.as_ptr(), s2);

    // 验证:s1和s2指向同一个堆数据
    println!("s1的堆地址: {:p}, s2的堆地址: {:p}", s1.as_ptr(), s2.as_ptr());
}


运行命令:cargo run


输出(地址是示例,你的会不一样):


plaintext

s1: 地址=0x7ffd9a5b9a70, 内容=Rust字符串
s2: 地址=0x7ffd9a5b9a90, 指向的数据地址=0x55f8d9a52b60, 内容=Rust字符串
s1的堆地址: 0x55f8d9a52b60, s2的堆地址: 0x55f8d9a52b60


内存解析


  • 栈上:s1(String 控制块:ptr=0x55f8d9a52b60, len=9, cap=9)和s2(&str:ptr=0x55f8d9a52b60, len=9)
  • 堆上:只有一份数据”Rust字符串”(地址 0x55f8d9a52b60)
  • 就像s1买了本书,s2只是借来看,书还是那本,只是多了个读者。

案例 2:”半复制”——Clone(深复制)

特点:栈上新建控制块,堆上复制数据,两份完全独立。


代码:


rust

fn main() {
    let s1 = String::from("Rust字符串");
    println!("s1: 栈地址={:p}, 堆地址={:p}, 内容={}", &s1, s1.as_ptr(), s1);

    // Clone复制:栈上新建String,堆上复制数据
    let s2 = s1.clone();  // 相当于"复印全书"
    println!("s2: 栈地址={:p}, 堆地址={:p}, 内容={}", &s2, s2.as_ptr(), s2);

    // 改s2不影响s1
    let mut s2 = s2;  // 加mut让s2可变
    s2.push_str("Clone");
    println!("改后s1: {}, 改后s2: {}", s1, s2);
}


运行输出:


plaintext

s1: 栈地址=0x7ffd9a5b9a70, 堆地址=0x55f8d9a52b60, 内容=Rust字符串
s2: 栈地址=0x7ffd9a5b9a90, 堆地址=0x55f8d9a52ba0, 内容=Rust字符串
改后s1: Rust字符串, 改后s2: Rust字符串Clone


内存解析


  • 栈上:s1和s2各有自己的控制块(地址不同)
  • 堆上:s1的数据在 0x55f8d9a52b60,s2的复制数据在 0x55f8d9a52ba0(新地址!)
  • 就像s1有本书,s2复印了一本,改复印本完全不影响原书。

案例 3:”偷梁换柱”—— 移动(Move)

特点:栈上控制权转移,堆数据不变,原变量失效(Rust 的 “独占欲”)。


代码:


rust

fn main() {
    let s1 = String::from("Rust字符串");
    println!("s1: 栈地址={:p}, 堆地址={:p}, 内容={}", &s1, s1.as_ptr(), s1);

    // 移动:s1的栈控制块"搬家"到s2,堆数据不变
    let s2 = s1;  // s1被"掏空",不能再用了
    println!("s2: 栈地址={:p}, 堆地址={:p}, 内容={}", &s2, s2.as_ptr(), s2);

    // 下面这行会报错!由于s1已经"无家可归"了
    // println!("s1还能用吗?{}", s1);
}


运行输出:


plaintext

s1: 栈地址=0x7ffd9a5b9a70, 堆地址=0x55f8d9a52b60, 内容=Rust字符串
s2: 栈地址=0x7ffd9a5b9a90, 堆地址=0x55f8d9a52b60, 内容=Rust字符串


内存解析


  • 栈上:s1的控制块被 “转移” 到s2,s1变成无效(编译器不让用了)
  • 堆上:数据地址不变(还是 0x55f8d9a52b60),但所有权从s1转到s2
  • 就像s1把书送给s2,s1再也不能说 “这是我的书” 了(Rust 的 “独生子女政策”,数据只能有一个 owner)

案例 4:特殊情况 —— 字符串字面量(&'static str)

特点:数据存在常量区,栈上复制引用,既不是深复制也不是移动。


代码:


rust

fn main() {
    // 字符串字面量:数据在常量区,栈上是&'static str
    let s1 = "Rust字面量";  // 注意:没有String::from
    println!("s1: 栈地址={:p}, 数据地址={:p}, 内容={}", &s1, s1.as_ptr(), s1);

    // 复制引用:栈上多一个指针,指向同一个常量区数据
    let s2 = s1;
    println!("s2: 栈地址={:p}, 数据地址={:p}, 内容={}", &s2, s2.as_ptr(), s2);

    // 两者都能修改吗?不能,由于是&str(不可变)
}


运行输出:


plaintext

s1: 栈地址=0x7ffd9a5b9ab0, 数据地址=0x55f8d9a3e0c0, 内容=Rust字面量
s2: 栈地址=0x7ffd9a5b9ab8, 数据地址=0x55f8d9a3e0c0, 内容=Rust字面量


内存解析


  • 常量区:”Rust字面量”在编译时就存在,地址固定
  • 栈上:s1和s2都是指向常量区的引用(类似两个路标指向同一个景点)
  • 就像公园的地图上有两个箭头都指向 “喷泉”,箭头可以复制,但喷泉只有一个。

四、三种复制方式的 “内存开销” 对比

方式

栈操作

堆操作

特点

适用场景

引用(&str)

新增指针(8 字节)

零成本,共享数据

临时访问,不修改

Clone

新增控制块(24 字节)

复制整个数据

成本高,独立数据

需要修改副本

移动(Move)

控制块转移

零成本,所有权转移

数据传递,唯一所有者


简单说:引用像 “借看”,Clone 像 “复印”,移动像 “赠送”—— 根据钱包(性能)和需求选就行~

五、编译运行全指南(小白也能看懂)

  1. 安装 Rust(已安装跳过):
    打开 PowerShell(Windows)或终端(Mac/Linux):
  2. bash
  3. curl –proto '=https' –tlsv1.2 -sSf https://sh.rustup.rs | sh
  4. 按提示操作,最后输入rustc –version看到版本号就成功了。
  5. 写代码:
    把上面的案例代码复制到src/main.rs,每次运行一个案例(注释掉其他的)。
  6. 运行:
  7. bash
  8. cargo run # 自动编译并运行
  9. 看报错(很重大!):
    列如案例 3 中如果强行打印s1,会得到:
  10. plaintext
  11. error[E0382]: use of moved value: `s1`
  12. 这是 Rust 在保护你,避免 “double free” 错误(就像防止你把同一本书送给两个人)。

总结:Rust 的 “复制哲学”

Rust 对待字符串复制就像对待贵重物品:


  • 能共享就不复制(引用)
  • 必须独立就深复制(Clone)
  • 转移所有权就彻底移交(移动)


这种 “斤斤计较” 的内存管理,让 Rust 程序既安全又高效 —— 毕竟,谁也不想为没必要的复制浪费内存,更不想由于共享数据搞出 bug 来~


标题

  1. 《Rust 字符串复制:栈堆联动的 “复制三部曲” 全解析》
  2. 《从内存视角看 Rust 字符串复制:引用、Clone 与移动的奥秘》

简介

本文用 “书籍复制” 的趣味类比,详解 Rust 中字符串复制的三种方式(引用、Clone、移动)在栈和堆中的具体操作。通过四个实战案例,直观展示每种方式的内存变化、适用场景及性能差异,附完整代码和编译步骤,让你轻松理解 Rust 独特的内存管理哲学。

关键词

#Rust #字符串复制 #栈和堆 #内存管理 #所有权

© 版权声明

相关文章

暂无评论

none
暂无评论...