Rust实战(4):防御简单C++ vector容器的越界问题例子分析

这篇具有很好参考价值的文章主要介绍了Rust实战(4):防御简单C++ vector容器的越界问题例子分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C++版本1

这是一个C++的简单vector容器的越界问题的小例子:

func1.h

//设立一个简单vector容器的越界问题
#ifndef STRING_VECTOR_H
#define STRING_VECTOR_H
#include <vector>
#include <string>
using namespace std;
class StringVector {
public:
    StringVector();
    void addElement(const string& element);
    bool popElement();
    //bool modifyElement(const string& oldElement, const string& newElement);
    vector<string> getValues();
    string getElement(size_t index);
private:
    size_t cur_len;
    size_t max_len;
    vector<string> values;

};
#endif

func1.cpp

#include "func1.h"

StringVector::StringVector() {
    max_len = 10;
    cur_len = 0;
    // 设置容器长度为max_len
    values.resize(max_len);
}

void StringVector::addElement(const string& element){
    // if(cur_len>=max)
    //     return;
    // 往尾巴插入元素,第一次会在第11的位置插入元素
    values.emplace_back(element);
}

bool StringVector::popElement(){
    // if(cur_len<=0)
    //     return false;
    values.pop_back();
    cur_len--;
    return true;
}

string StringVector::getElement(size_t index){
    return values[index];
}

上面的代码是刻意制造出来的,用一个测试代码说明问题:

#include "func1.h"
#include <random>
#include <string>
#include <iostream>

int main(){
    // 正常运行的代码
    StringVector sv;
    sv.addElement("This is question1");
    std::string ret1 = sv.getElement(10);
    std::cout<<"ret1:"<<ret1<<std::endl;
    //sv.popElement();
    

    // 有BUG的代码
    sv.popElement();
    std::string ret2 = sv.getElement(11);

    std::cout<<"ret2:"<<ret2<<std::endl;
    return 0;
}

程序输出:

ret1:This is question1
ret2:

这个代码在Mac OS 系统上 make 后运行没有崩溃,但是测试代码里std::string ret2 = sv.getElement(11); 实际上发生了数组越界。

Rust版本1

在rust环境下创建一个比对小项目:

首选,创建一个测试目录:

mkdir rust

其次,添加一个Cargol.toml工程配置文件,添加空配置:

[workspace]

members = [

]

接着,创建一个类库项目some:

cargo new --lib some

接着,创建一个主程序main:

cargo new main

最后,配置两个子项目到根配置Cargo.toml里

[workspace]

members = [
    "some",
    "main"
]

此时目录结构这样:

├── Cargo.toml
├── main
│   ├── Cargo.toml
│   └── src
│       └── main.rs
├── some
│   ├── Cargo.toml
│   └── src
│       ├── lib.rs

现在,在some/src 目录下新增str_vec.rs,代码如下:

use core::fmt;

pub struct StringVector {
    cur_len: usize,
    max_len: usize,
    values: Vec<String>,
}

impl fmt::Display for StringVector {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "cur_len: {}, max_len:{}, values:{:?}",
            self.cur_len, self.max_len, self.values
        )
    }
}

impl StringVector {
    pub fn new(cur_len: usize, max_len: usize) -> Self {
        let mut vec_string: Vec<String> = Vec::with_capacity(max_len);
        for _i in 0..max_len {
            vec_string.push(String::from(""));
        }

        StringVector {
            cur_len: cur_len,
            max_len: max_len,
            values: vec_string,
        }
    }

    pub fn add_element(&mut self, element: String) {
        if self.cur_len >= self.max_len {
            return;
        }

        self.values.push(element);
        self.cur_len += 1;
    }

    pub fn pop_element(&mut self) -> bool {
        if self.cur_len == 0 {
            return false;
        }
        self.values.pop();
        return true;
    }

    pub fn get_values(&self) -> Vec<String> {
        self.values.clone()
    }

    pub fn get_element(&self, index: usize) -> String {
        self.values[index].clone()
    }
}

在 some/src/lib.rs 里添加测试代码

mod str_vec;

pub use str_vec::*;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let max_len = 10;
        let mut vec = str_vec::StringVector::new(0, max_len);

        // 正常代码
        vec.add_element(String::from("你好"));
        let ret1 = vec.get_element(10);
        println!("ret1:{:}", ret1);
        vec.pop_element();

        // 有BUG代码
        let ret2 = vec.get_element(11);

        println!("ret2:{:}", ret2);
    }
}

执行cargo的测试命令:cargo test -- --nocapture,输出:

warning: some crates are on edition 2021 which defaults to `resolver = "2"`, but virtual workspaces default to `resolver = "1"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
   Compiling some v0.1.0 (/Users/feilong/Desktop/test/mtest_cases/rust/some)
    Finished test [unoptimized + debuginfo] target(s) in 0.21s
     Running unittests src/main.rs (target/debug/deps/main-90bdc1be522c03e6)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/some-911f1fc75a0fadd8)

running 1 test
ret1:你好
thread 'tests::it_works' panicked at 'index out of bounds: the len is 10 but the index is 10', some/src/str_vec.rs:55:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
test tests::it_works ... FAILED

failures:

failures:
    tests::it_works

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `-p some --lib`

可以看到Rust的测试输出了详细的数组越界信息:thread 'tests::it_works' panicked at 'index out of bounds: the len is 10 but the index is 10', some/src/str_vec.rs:55:9

在数组越界上,如果采用和C++一样的数组下标直接索引,Rust代码在运行时会明确的Panic。

Rust版本2

但是Rust对类型的处理可以更好,稍微改造下get_element的实现:

use core::fmt;

pub struct StringVector {
    cur_len: usize,
    max_len: usize,
    values: Vec<String>,
}

impl StringVector {
    pub fn new(cur_len: usize, max_len: usize) -> Self {
        let mut vec_string: Vec<String> = Vec::with_capacity(max_len);
        for _i in 0..max_len {
            vec_string.push(String::from(""));
        }

        StringVector {
            cur_len: cur_len,
            max_len: max_len,
            values: vec_string,
        }
    }

    pub fn add_element(&mut self, element: String) {
        if self.cur_len >= self.max_len {
            return;
        }

        self.values.push(element);
        self.cur_len += 1;
    }

    pub fn pop_element(&mut self) -> bool {
        if self.cur_len == 0 {
            return false;
        }
        self.values.pop();
        return true;
    }

    pub fn get_values(&self) -> Vec<String> {
        self.values.clone()
    }

    pub fn get_element(&self, index: usize) -> Option<&String> {
        // 使用get返回Option<&String>
        self.values.get(index)
    }
}

改造下对应的测试代码:

mod str_vec;

pub use str_vec::*;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let max_len = 10;
        let mut vec = str_vec::StringVector::new(0, max_len);

        // 正常代码
        vec.add_element(String::from("你好"));
        let ret1 = vec.get_element(10);
        println!("ret1:{:?}", ret1.unwrap());
        vec.pop_element();

        // 有BUG代码
        let ret2 = vec.get_element(10);

        println!("ret2:{:?}", ret2);
    }
}

执行测试命令cargo test -- --nocapture

输出:

warning: some crates are on edition 2021 which defaults to `resolver = "2"`, but virtual workspaces default to `resolver = "1"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
   Compiling some v0.1.0 (/Users/feilong/Desktop/test/mtest_cases/rust/some)
warning: unused import: `core::fmt`
 --> some/src/str_vec.rs:1:5
  |
1 | use core::fmt;
  |     ^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: `some` (lib) generated 1 warning (run `cargo fix --lib -p some` to apply 1 suggestion)
warning: `some` (lib test) generated 1 warning (1 duplicate)
    Finished test [unoptimized + debuginfo] target(s) in 0.23s
     Running unittests src/main.rs (target/debug/deps/main-90bdc1be522c03e6)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/some-911f1fc75a0fadd8)

running 1 test
ret1:"你好"
ret2:None
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

可以看到ret2:None 此时数组超出范围,返回的是None,这个时候,可以使用Rust的 match 语法做自然的错误处理:

mod str_vec;

pub use str_vec::*;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let max_len = 10;
        let mut vec = str_vec::StringVector::new(0, max_len);

        // 正常代码
        vec.add_element(String::from("你好"));
        let ret1 = vec.get_element(10);
        println!("ret1:{:?}", ret1.unwrap());
        vec.pop_element();

        // 有BUG代码
        let ret2 = vec.get_element(10);

        // 使用match 语法
        match ret2 {
            // 放心使用
            Some(value) => println!("ret2:{:?}", String::from("world") + value),
            None => println!("do nothing"),
        }
    }
}

输出:

warning: some crates are on edition 2021 which defaults to `resolver = "2"`, but virtual workspaces default to `resolver = "1"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
warning: unused import: `core::fmt`
 --> some/src/str_vec.rs:1:5
  |
1 | use core::fmt;
  |     ^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: `some` (lib) generated 1 warning (run `cargo fix --lib -p some` to apply 1 suggestion)
warning: `some` (lib test) generated 1 warning (1 duplicate)
    Finished test [unoptimized + debuginfo] target(s) in 0.00s
     Running unittests src/main.rs (target/debug/deps/main-90bdc1be522c03e6)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/some-911f1fc75a0fadd8)

running 1 test
ret1:"你好"
do nothing
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests some

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

可以看到安全地有 do nothing 输出。实际上,如果此时不用match,直接写:

println!("ret2:{:?}", String::from("world") + ret2);

执行cargo build,编译器就提示类型不匹配:

error[E0308]: mismatched types
  --> some/src/lib.rs:22:55
   |
22 |         println!("ret2:{:?}", String::from("world") + ret2);
   |                                                       ^^^^ expected `&str`, found `Option<&String>`
   |
   = note: expected reference `&str`
                   found enum `Option<&String>`

所以你就强制要解决类型匹配问题,要么用unwrap,但是一旦你用了unwrap,就意味着如果是None会Panic,此处需要做match获取其他错误处理(以后展开):

println!("ret2:{:?}", String::from("world") + ret2.unwrap());

C/C++数组越界麻烦的根本原因,Rust首先做对的是什么?

Q1:c/c++和rust在数组越界上的处理差异是?
A1
C/C++ 和 Rust 在处理数组越界方面存在一些关键差异。

在 C/C++ 中,数组越界通常会导致不确定的行为。这意味着程序可能会崩溃、生成垃圾数据或以其他意想不到的方式工作。数组越界很难调试,因为它可能很难确定导致问题的索引。

Rust 在数组越界方面更加安全。如果您试图访问数组越界索引,Rust 将抛出一个错误。这使得在运行时更容易发现和修复数组越界问题。

以下是 C/C++ 和 Rust 在数组越界方面处理差异的一些示例:

// C++
int arr[10];
arr[11] = 1; // 数组越界,程序可能崩溃或生成垃圾数据

// Rust
let mut arr = [1, 2, 3, 4, 5];
arr[5] = 6; // 数组越界,Rust 抛出一个错误

Rust 在数组越界方面更加安全,因为它可以帮助您在编译时或运行时发现和修复数组越界问题。这可以帮助您编写更可靠的程序。

Q2:rust编译时检查出数组越界的例子
A2
好的,以下是 Rust 编译时检查出数组越界的例子:

fn main() {
    let mut arr = [1, 2, 3, 4, 5];
    arr[5] = 6; // 数组越界,Rust 编译时检查出
}

编译该代码会得到以下错误:

error[E0659]: index out of bounds: the len is 5 but the index is 5
 --> src/main.rs:4:5
  |
4 |     arr[5] = 6;
  |     ^^^^^^^ index out of bounds
  |

Rust 在编译时检查出数组越界,可以帮助您避免在运行时发生数组越界错误。

更多

Rust的在错误处理上的支持还有更多,可以慢慢展示,这些只是Rust现代语言设计带来的基本便利,Rust并不是小补丁式的解决问题。

关于Option的问题,也可以看这篇以前翻译的文章:翻译(3): NULL-计算机科学上最糟糕的失误: https://vector.blog.csdn.net/article/details/119341261?spm=1001.2014.3001.5502

解决问题要用系统性的方式解决。Rust是替代C++的代规模底层软件开发的首选。文章来源地址https://www.toymoban.com/news/detail-599620.html

到了这里,关于Rust实战(4):防御简单C++ vector容器的越界问题例子分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Day 23 C++ vector容器

    定义 C++的vector容器是一个动态数组,提供了存储和操作元素的功能。它是标准模板库(STL)的一部分,在头文件中定义。 vector数据结构和 数组非常相似 ,也称为 单端数组 ( 动态数组 )( 也被称为向量 ) 使用vector容器前,需要引入头文件: vector容器的迭代器——支持随

    2024年02月14日
    浏览(34)
  • 【C++第三阶段】vector容器

    以下内容仅为当前认识,可能有不足之处,欢迎讨论! vector函数是常用数据结构,见于刷题网站。 它是单端数组,与普通数组区别在于可以动态扩展。 动态扩展是编译器自动的,不是在原空间之后续接新空间,而是找更大的内存空间,将原数据拷贝新空间,释放原空间。

    2024年04月10日
    浏览(39)
  • 【C++】vector容器的模拟实现

    目录 一,框架设计 二,构造函数 三,析构函数 四,赋值运算符 五,容器接口的实现 1,迭代器实现 2,“ [] ”运算符的实现 3,swap交换和resize重设大小 4,insert插入和erase删除 介绍:         本文,我们重点实现vector容器的用法,这里要注意的是vector容器可以接纳任意类

    2024年02月02日
    浏览(55)
  • vector容器删除元素的几种简单方法(详细实用)

    (1)使用 vector库函数“erase”删除 , 使用erase函数后容器size自动-1 (2)使用 vector库函数“swap和pop_back()\\\" ,由于 pop_back删除的是最后一个元素 ,所以先移位再删除 如果从数组的角度理解,因为数组存储的内容 在地址上是连续的 ,要移除目标元素,就没有库函数可以使用,

    2023年04月16日
    浏览(49)
  • C++ —— STL容器【vector】模拟实现

    本章代码gitee仓库:vector模拟实现、vector源码 看源码发现 vector 是类模板定义的,成员是采用迭代器进行管理 当涉及到容器类时,通常有一些关键函数,如构造函数、析构函数和拷贝构造函数,它们负责初始化容器对象、销毁对象和进行对象的拷贝等 这里注意拷贝构造要实现

    2024年02月16日
    浏览(45)
  • 【C++】:C++中的STL序列式容器vector源码剖析

    vector定于与stl_vector.h头文件中 例如: vector的数据结构非常简单:一个线性连续空间 下面介绍vector的3个数据结构: start:表示目前使用空间的头 finish:表示目前使用空间的尾 end_of_storage:表示目前可用空间的尾 说明:为了降低空间配置时的速度成本,vector实际配置的大小可

    2024年01月22日
    浏览(51)
  • C++ stl容器vector的底层模拟实现

    目录 前言:   1.成员变量,容量与大小 2.构造函数 无参构造: 带参的使用值进行构造:  使用迭代器区间进行构造: 3.交换 4.拷贝构造 5.赋值重载 6.迭代器 7.扩容 reserve: resize: 8.插入与删除 insert: erase: insert迭代器失效问题: erase迭代器失效问题: 9.头插头删 10.[]重载

    2024年04月15日
    浏览(41)
  • C++容器(vector、deque、list、map)

    数组尾部添加或删除元素非常迅速。但在中部或头部就比较费时。 *代码演示:* 取: at在下标越界时会抛出异常,我们能捕获异常进行处理;而[]下标越界会让程序直接终止; 构造函数: cbegin, cend, crbegin, crend返回的是常量迭代器,不能通过迭代器修改vector元素的值。 插入(

    2024年02月02日
    浏览(53)
  • 【C++】容器篇(一)—— vector 的基本概述以及模拟实现

    前言: 在之前,我们已经对 string类进行了基本的概述,并且手动的实现了string类中常用的接口函数。本期,我将带领大家学习的是STL库中的一个容器 -- vector 的学习。相比于之前的string类,本期的 vector 相对来说实现起来略微难一点,难点就在于要考虑关于 “ 迭代器失效 ”

    2024年02月07日
    浏览(44)
  • 【C++入门】STL容器--vector底层数据结构剖析

    目录  前言  1. vector的使用       vector的构造  vector迭代器  vector空间相关的接口  vector 功能型接口  find  swap  insert  erase 2. vector内部数据结构剖析 reserve  push_back和pop_back size、capacity、empty、operator[ ];  insert和erase resize swap  拷贝构造和赋值重载 构造函数补充  迭代器

    2024年01月25日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包