• rust所有权


    一、堆和栈

    栈和堆都是程序运行时使用的内存,但是它们的结构不同。

    1.栈
    栈,英文是stack。是内存的一段区域。
    栈是后进先出形式的。就像薯片桶,先放进去的一片只能后拿出来。
    栈上存储的数据大小必须是已知且固定的。也就是说如果一个变量或数据要放到栈上,那么它的大小在编译是就必须是明确的。
    例如,类型为i32的变量,它的大小是固定4个字节。

    2.堆
    堆,英文是heap。是内存的另一段区域。堆内存也叫做资源。
    堆是缺乏组织的:当向堆放入数据时,你要请求一定大小的空间。内存分配器在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的指针。这个过程称作在堆上分配内存。因为指针大小是已知且固定的,所以可以将该指针存储在栈上,不过当需要实际数据时,必须去指针指向的内存读取数据。就像一个围棋棋盘,你可以把一枚棋子放到任意可以放得下的位置。
    在编译时大小未知或大小可能变化的数据,要存储在堆上。
    堆不受系统管理,由用户自己管理,因此,使用不当,内存溢出的可能性就大大增加了。

    Rust与其他语言的区别
    (1)指针超出作用域会自动释放堆内存:
    对于简单类型的栈内存(如int)超出作用域后自动释放,这个功能各个语言都有。而对于new出来的堆内存,在c/c++中要手动释放,在java中要委托垃圾回收释放。而垃圾回收不是实时的,会影响性能,手动释放又总会有人忘记释放。而Rust对栈内存和堆内存一视同仁,超出作用域一律自动释放,相当于自动delete指针。所以rust避免了内存泄漏。
    在作用域结束时释放资源的模式称作资源获取即初始化(Resource Acquisition Is Initialization (RAII))。
    rust强制使用raii,所以任何对象在离开作用域时,它的析构函数就被调用,然后它占有的资源就被释放。
    这避免了资源泄漏,所以你再也不用手动释放内存或者担心内存泄漏。
    (2)所有权:
    某段内存只能被最后的变量名所有,前面声明过的变量都作废,这样一段内存就只有一个所有者,只有所有者可以释放这块内存。这有效避免了被多个变量释放的问题,而且该操作是在编译期的,这可在编译期就能避免空指针问题。比如c++中,a和b指向同一块内存,如果delete a之后,再delete b就会出错,而rust中不会出现这种问题。

    二、所有权

    (一)所有权是什么
    所有权是指对内存资源的控制权和管理权。
    在Rust中,每个值都有一个唯一的所有者。定义一个变量就是声明这个值由这个变量所有。所有的值最终都要存储在一块内存上,变量拥有这个值其实是拥有这块内存。栈内存所有者就是声明时的变量,而堆内存所有者是分配返回的指针。
    只有所有者才能释放这块内存,其他人不能释放这块内存。
    当所有者超出作用域时,会自动释放这块内存。

    所有权的规则:
    1.Rust中的每一个值都有一个所有者。
    2.值在任一时刻有且只有一个所有者。
    3.当所有者离开作用域,这个值将被丢弃。

    比如,
    现在可以把Box当成一个指针,后面章节再讲解Box用法。

    fn create_box() {
        let _box1 = Box::new(3i32);// 在堆上分配一个整型数据
        // `_box1` 在这里被销毁,内存得到释放。如果是c++,就得手动delete,否则就会内存泄漏。这就是区别。
    }
    fn main() {
        let _box2 = Box::new(5i32);// 在堆上分配一个整型数据
    
        // 嵌套作用域:
        {
             let _box3 = Box::new(4i32);// 在堆上分配一个整型数据
             // `_box3` 在这里被销毁,内存得到释放
        }
    
        // 创建一大堆 box 完全不需要手动释放内存!
        for _ in 0u32..1000 {
             create_box();
        }
        // `_box2` 在这里被销毁,内存得到释放
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    (二)转让所有权
    所有权可以转让。转让所有权也叫move。
    就是由新变量拥有内存,旧变量变成无效的。

    s1转让给s2,就像下图所示。s2拥有堆内存,s1变无效。
    在这里插入图片描述

    Rust语言中转让所有权的方式有以下几种:
    1.把一个变量赋值给另一个变量。
    2.把变量值传递给函数作为参数。
    3.函数中返回一个变量作为返回值。

    1.把一个变量赋值给另一个变量

    fn main(){
        let a = Box::new(5i32);// a 是一个指向堆分配的整数的指针
        let b = a;// 移动a到b,把a的指针地址(而非数据)复制到b。现在是b拥有堆内存,a变无效。
        //println!("a contains: {}", a);// 报错!a无效,因为它不再拥有那部分堆上的内存。
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.把变量值传递给函数作为参数。
    值传递方式,值的所有权也会发生变更

    fn destroy_box(c: Box) {
        println!("Destroying a box that contains {}", c);
        // c 被销毁且内存得到释放
    }
    fn main() {
        let a = Box::new(5i32);// a 是一个指向堆分配的整数的指针
        destroy_box(a);// a的所有权转移给函数形参,a变无效
        //println!("a contains: {}", a);// 报错!a无效
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.函数中返回一个变量作为返回值
    函数的形参获得的所有权将在离开函数后就失效了。失效的数据就再也访问不到了。
    为了解决所有权失效的问题,我们可以让函数将所有者返回给调用者。

    fn destroy_box(c: Box) ->Box {
        println!("Destroying a box that contains {}", c);
        c
    }
    fn main() {
        let mut a = Box::new(5i32);// a 是一个指向堆分配的整数的指针
        a = destroy_box(a);// a的所有权转移给函数形参,a变无效
        println!("a contains: {}", a);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (三)复刻
    复刻,英文是clone。也叫深复制或深拷贝。
    有时候,我们需要创建一个值的完全独立的副本,而不是转让所有权。在这种情况下,可以使用复刻。

    创建s1的副本s2,就像下图所示。s1和s2都拥有了独立的所有权。
    在这里插入图片描述

    示例:

    fn main() {
         let s1 = String::from("hello");
         let s2 = s1.clone();
         println!("{} {}", s1, s2); // 正常打印 "hello hello"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    创建了字符串"hello"的副本,赋值给s2,因此s1和s2都拥有了独立的所有权。

    fn main() {
        let a = Box::new(5i32);
        let b = a.clone();
        println!("{}", a);
        println!("{}", b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    栈上的数据
    看下例

    fn main() {
        let a = 50;
        let b = a;
        println!("{}", a);
        println!("{}", b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这段代码能编译通过。按照所有权转让规则的话,它应该编译错误才对,可是为什么能编译通过?
    因为像整型这样的类型完全存储在栈上,并不需要占用那么大的内存,所以拷贝它的值是很快的。没有理由在创建变量b后使a无效。这里没有深浅拷贝的区别,所以这里不管是否调用clone,效果都一样。
    Rust有一个Copy trait,可以用于存储在栈上的类型。如果一个类型实现了Copy trait,那么就不使用所有权转让,而是使用复刻。

    那么哪些类型实现了Copy trait呢?
    Rust不允许自身或其部分实现了Drop trait的类型使用Copy trait。
    任何简单标量值的组合都可以实现Copy,任何不在堆上分配内存的类型都可以实现Copy。
    如下是一些Copy的类型:
    1.布尔类型,bool
    2.数字类型,包括整数和浮点数,比如 u32,f64。
    3.字符类型,char
    4.元组,当且仅当,其包含的类型都实现Copy的时候。比如,(i32, i32) 实现了Copy,但 (i32, String) 就没有。

  • 相关阅读:
    【C++】类和对象——中
    利用iperf网络带宽测试工具看多线程及多核编程
    【golang】context详解
    竞赛 深度学习交通车辆流量分析 - 目标检测与跟踪 - python opencv
    mysql5.7安装教程
    记一次 Flink 作业启动缓慢
    VUE模板编译的实现原理
    解决logstash插件logstash-outputs-mongodb一条数据失败后一直重复尝试
    蒲公英路由器如何设置远程打印?
    【状态估计】将Transformer和LSTM与EM算法结合到卡尔曼滤波器中,用于状态估计(Python代码实现)
  • 原文地址:https://blog.csdn.net/inxunxun/article/details/133363417