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文章来源:https://www.toymoban.com/news/detail-599620.html
解决问题要用系统性的方式解决。Rust是替代C++的代规模底层软件开发的首选。文章来源地址https://www.toymoban.com/news/detail-599620.html
到了这里,关于Rust实战(4):防御简单C++ vector容器的越界问题例子分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!