
























参数传递模型决定了数据如何在“上层调用者(Caller)”与“被调用函数(Callee)”之间流转。它不仅关乎代码的编写方式,更直接决定了程序的内存开销、数据安全性以及外部变量是否会被意外篡改。
我们将以底层的 C 语言 与现代的 Rust 语言 为例,从内存、权限和应用场景三个维度进行系统化拆解。
当一个变量以“值”的形式传进函数时,系统会在当前函数的栈帧(Stack Frame)中创建该数据的一个全新拷贝(副本)。函数内部所有的操作,都是在这个独立的“沙盒副本”中进行的。
void modify_value(int val) {
val = 20; // 修改的是局部栈帧中的副本
}
int main() {
int num = 10;
modify_value(num);
// 结果:num 依然是 10
}
modify_value 时,内存中复制出了一个新的 val(值为 10)。mut val: i32在 Rust 中,值传递的行为取决于数据是否实现了 Copy 特征(Trait)。对于基础类型(如 i32, u16),它的底层同样是复制一个新副本,但 Rust 引入了更严苛的局部可变性控制。
fn modify_value(mut val: i32) {
val = 20; // 允许修改!因为有 mut 修饰
}
fn main() {
let num = 10;
modify_value(num);
// 结果:num 依然是 10
}
mut 的真实含义:这里的 mut 属于局部权限修饰符。它不是在改变上层调用者的变量权限,而是函数在向编译器申请:“在这个函数内部,我允许自己修改这个新复制出来的变量副本”。num 甚至不需要是 mut 的。函数不再复制原始数据,而是接收原始数据在内存中的地址(指针)。借由这个地址,函数内部的操作可以穿透当前的栈帧,直接对上层调用者内存空间里的真实数据进行改写。
void modify_by_pointer(int *ptr) {
*ptr = 20; // 显式解引用,直接修改地址对应的内存
}
int main() {
int num = 10;
modify_by_pointer(&num); // 传入 num 的内存地址
// 结果:num 变成了 20
}
ptr: &mut i32Rust 在底层硬件行为上与 C 语言的指针完全一致,但在语义安全层面引入了极其严格的“借用检查器(Borrow Checker)”规则。
fn modify_by_reference(ptr: &mut i32) {
*ptr = 20; // 显式解引用(在很多上下文里 Rust 也会自动隐式解引用)
}
fn main() {
let mut num = 10; // 外部变量必须也是可变的
modify_by_reference(&mut num); // 显式传入可变引用
// 结果:num 变成了 20
}
&mut)。这意味着当这个函数在操作 num 时,任何其他线程或代码都无法读取或修改 num,从根本上在编译期杜绝了“数据竞争(Data Race)”和“并发死锁”。| 特性 | C: int val / Rust: val: i32 | Rust: mut val: i32 | C: int *ptr / Rust: ptr: &mut i32 |
|---|---|---|---|
| 内存本质 | 复制一个新副本 | 复制一个新副本 | 传递原始数据的内存地址 |
| 内部修改权限 | ❌ 无法在函数内修改该参数 | 允许在函数内修改副本 | 允许直接修改外部原始数据 |
| 外部可见性 | ❌ 外部变量毫无变化 | ❌ 外部变量毫无变化 | 外部变量同步被修改 |
| 典型应用场景 | 仅需要读取入参进行计算。 | 临时将入参作为函数内部的局部变量、计数器使用。 | 需要将函数执行的结果通过参数“带回”给上层调用者。 |
参考资料:
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。