吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 192|回复: 1
收起左侧

[学习记录] rust学习记录6-所有权与函数

  [复制链接]
comewithyou1996 发表于 2024-12-28 23:34
本帖最后由 comewithyou1996 于 2024-12-28 23:44 编辑

所有权是rust最独特的特性,它让rust无需GC就可以保证内存安全。rust通过生命周期、所有权、借用检查在内存安全方面远远优于其他语言。

什么是所有权:
所有程序在运行时都必须管理他们使用计算机内存的方式,程序员必须显式地分配和释放内存。内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检查的规则。当程序运行时所有权特性不会减慢程序运行速度。

在介绍所有权前先介绍几个其他概念。
数据在内存中的存储分为栈内存(stack)存储和堆内存(heap)存储两种,存储在栈内存空间上的数据后进先出,数据必须拥有已知的固定的大小。存储在堆内存上的数据先进先出,编译时大小未知的数据或运行时大小可能发生变化的数据必须存放在heap上。
heap内存组织性差一些,当你把数据放入heap时,会请求一定数量的空间,操作系统在heap里找到一块足够大的空间,把它标记为“在用”,并返回一个指针,也就是这个空间的地址,这个过程叫做在heap上进行分配。
因为指针是已知固定大小的,可以把指针存放在stack上。
把数据压到stack上要比在heap上分配快的多,因为操作系统不需要寻找用来存储新数据的空间,那个位置永远在stack的顶端。
访问heap中的数据要比访问stack中的数据慢,因为需要通过指针才能找到heap中的数据。还有一个原因是处理器往往有对栈专门的指令,heap没有。

关于函数调用:
当代码调用函数的时候,值被传入到函数(也包括指向heap的指针)。函数本地的变量被压到stack上,当函数结束后,这些值会从stack上弹出。

所有权要解决的问题:
跟踪代码的哪些部分正在使用heap的哪些数据,
最小化heap上的重复数据量,
清理heap上未使用的数据以免空间不足,
一旦懂了所有权就不需要去想stack或heap了。

所有权规则:
每个值都有一个变量,这个变量是该值的所有者,每个值同时只能有一个所有者,当所有者超出作用于scope时,该值将被删除。变量作用域就是程序中一个项目的有效范围。

String类型与作用域:
字符串字面值是不可变的,分配在heap上,能存储在编译时未知数量的文本。
可以使用from函数从字符串字面值创建出String类型:
let s = String::from(“hello”);
这里::表示from是String类型下的函数。这种定义方法定义的字符串是可以被修改的。那么为什么String类型的值可以修改但是字符串字面值却不能修改?因为他们处理内存的方式不同。
字符串字面值在编译时就知道它的内容了,其文本内容直接被硬编码到最终的可执行文件里,这样速度快,更高效,但是不可变。
String类型为了支持可变性,需要在heap上分配内存来保存编译时未知的文本内容:
-操作系统必须在运行时来请求内存,这通过调用String::from来实现。
-当用完String之后需要用某种方式将内存返回给操作系统,这在拥有GC垃圾回收器的语言中,GC会跟踪并清理不再使用的内存。但是rust不使用GC,rust采用了不同的方式,对于某个值来说,当拥有它的变量走出作用范围时,内存会立即自动交还给操作系统,变量离开作用于时会自动调用drop函数。

变量和数据交互的方式:移动(move)
多个变量可以与同一个数据使用同一种独特的方式来交互:
let x = 5;
let y = x;
整数是已知且固定大小的简单的值,这两个5被压到stack中

let s1 = String::from(“hello”);
let s2 = s1;
一个String由3部分组成:一个指向存放字符串内容的指针,一个长度,一个容量,这些东西放在stack上,存放字符串内容的部分在heap上。长度len就是存放字符串内容所需的字节数,容量是指string从操作系统总共获得内存的总字节数。
当把s1赋给s2,String的数据被复制了一份:在stack上复制了一份指针、长度、容量,但并没有复制指针所指向的heap上的数据。
当变量离开作用域时,rust会自动调用drop函数,并将变量使用的heap内存释放。当s1,s2离开作用域时,他们都会尝试释放相同的内存:二次释放bug。二次释放也就是释放野指针,野指针就是指针指向的位置是不可知的。
为了保证内存安全,rust没有尝试复制被分配的内存,rust让s1失效,当s1离开作用域的时候,rust不需要释放任何东西。

在rust中有深拷贝和浅拷贝,复制指针、长度、容量可以视为浅拷贝,但由于rust让s1失效了,所以我们用一个新的术语:移动move(rust不会自动创建数据的深拷贝)

变量和数据的交互方式:克隆(Clone)
如果真的想对heap上的String数据进行深度拷贝,而不仅仅是stack上的数据,可以使用clone方法,这个以后再说。

stack上的数据:复制
复制就是使用Copy trait,可以用于像整数这样完全存放在stack上的数据。如果一个类型实现了Copy这个trait,那么旧的变量在赋值后仍然可用。如果一个类型或者该类型的一部分实现了Droptrait,那么rust不允许让它再实现Copy trait了
所有权与函数
在语义上,将值传递给函数和把值赋给变量是类似的,将值传递给函数将发生移动或复制
fn main() {
    let s = String::from("hello world");
    take_ownership(s);
    let x = 5;
    makes_copy(x);
    println!("x:{}", x);
}
fn take_ownership(some_string: String) {
    println!("{}", some_string);
}
fn makes_copy(some_number: i32) {
    println!("{}", some_number);
}

函数在返回值的过程中同样也会发生所有权的转移
fn main() {
    let s1 = gives_ownership();
    let s2 = String::from("hello");
    let s3 = takes_and_gives_back(s2);
}
fn gives_ownership() -> String {
    let some_string = String::from("hello");
    some_string
}
fn takes_and_gives_back(a_string: String) -> String {
    a_string
}

一个变量的所有权总是遵循同样的模式:把一个值赋给其他变量时就会发生移动;当一个包含heap数据的变量离开作用域时,它的值就会被drop函数清理,除非数据的所有权移动到了另一个变量上。
如何让函数使用某个值,但不获得其所有权?
fn main() {
    let s1 = String::from("hello");
    let (s2, len) = calculate_length(s1);
    println!("The length of '{}' is {}.", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
    let length = s.len();
    (s, length)
}

引用和借用
fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
    s.len()
}

参数的类型是&String而不是String。&符号就表示引用:允许你引用某些值而不取得其所有权
解除引用使用*,这里暂时用不到,后面再说
我们把引用作为函数参数这个行为叫做借用,不能修改借用的东西。和变量一样,引用默认也是不可变的。
引用和变量前都加mut修饰:
fn main() {
    let mut s1 = String::from("hello");
    let len = calculate_length(&mut s1);
    println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &mut String) -> usize {
    s.push_str(", world");
    s.len()
}

可变引用有一个重要的限制:在特定作用域内,对某一块数据,只能有一个可变引用,这样可以在编译时防止数据竞争。以下三种行为会发生数据竞争:
1.两个或多个指针同时访问同一个数据。2.至少有一个指针用于写入数据。3.没有使用任何机制来同步对数据的访问。
可以通过创建新的作用域,来允许非同时的创建多个可变引用
fn main() {
    let mut s = String::from("hello");
    {
        let s1 = &mut s;
    }
    let s2 = &mut s;
}

另一个限制:不可以同时拥有一个可变引用和一个不可变引用。多个不变引用是可以的
fn main() {
    let mut s = String::from("hello");
    let r1 = &s;
    let r2 = & s;
    let s1 = &mut s;
    println!("{}, {}, {}", r1, r2, s1);
}

上面这个程序会报错
悬空引用Dangling References
悬空指针Dangling Pointer是指一个指针引用了内存中的某个地址,而这块内存可能已经释放并分配给其他人使用了。
在rust里,编译器可保证引用永远都不是悬空引用,如果你引用了某些数据,编译器将保证在引用离开作用域之前数据不会离开作用域。
fn main() {
    let r = dangle();
}
fn dangle() -> &String {
    let s = String::from("hello");
    &s
}

上面这个程序是有错误的

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

bai1276 发表于 2024-12-29 12:28
rust官方有图解解释,大家学习的话可以参考
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-1-2 20:18

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表