rust引用和指针
DST
起因是看到rust圣经的动态大小类型DST一章的这段话
原文说的是动态类型的定义,所的是在编译过程中无法确认类型所占有大小的类型。
vec和string这种之所以不是,是因为这些变量在某一时刻是可以确定大小的
比如vec在编译时可以明确知道目前它占用了多少,至于后面他的动态变大,那是后面的事,反正某一刻的大小是可以确定的。
而特征对象这种连类型都无法确定的,在任何时候都无法确定其占用了多少空间,才是DST。
这个概念对我来说还挺好理解的,我主要想说的是下面我意识到的东西。
动态大小类型 DST
读者大大们之前学过的几乎所有类型,都是固定大小的类型,包括集合 vec、string 和 Hashmap 等,而动态大小类型刚好与之相反:编译器无法在编译期得知该类型值的大小,只有到了程序运行时,才能动态获知。对于动态类型,我们使用 DsT(dynamically sized types)或者 unsized 类型来称呼它。上述的这些集合虽然底层数据可动态变化,感觉像是动态大小的类型。
但是实际上,这些底层数据只是保存在堆上,在栈中还存有一个引用类型,该引用包含了集合的内存地址、元素数目、分配空间信息,通过这些信息,编译器对于该集合的实际大小了若指掌,最最重要的是:栈上的引用类型是固定大小的,因此它们依然是固定大小的类型。
意识到引用实际上也是一个变量,而且一般是创建在栈上的,而他引用的数据存储在堆上
这么一看,引用和指针十分相似。
原来引用就是指针的一种,不过不同语言赋予了这种指针不同的含义以实现某种功能,于是便称之为引用。
把引用看成是某种指针,突然就感觉豁然开朗了。
关于引用和指针的理解
下面是查看变量地址所做的一些实验。
rustfn main() {
let a: &str = "testtes";
let b: i32 = 666;
let c: i32 = 777;
println!("The address of \"testtes\" is: {:p}", a);
println!("The address of a is: {:p}", &a);
println!("The size of \"testtes\" is: {} bytes", std::mem::size_of_val(a));
println!("The size of a is: {} bytes", std::mem::size_of_val(&a));
println!("The address of b is: {:p}", &b);
println!("The size of b is: {} bytes", std::mem::size_of_val(&b));
println!("The size of ref b is: {} bytes", std::mem::size_of_val(&&b));
println!("The address of c is: {:p}", &c);
}
结果如下
htmlThe address of "testtes" is: 0x55c4bc17c410
The address of a is: 0x7ffe1c1045c0
The size of "testtes" is: 7 bytes
The size of a is: 16 bytes
The address of b is: 0x7ffe1c1045d0
The size of b is: 4 bytes
The size of ref b is: 8 bytes
The address of c is: 0x7ffe1c1045d4
可以看到引用实际上就是一个指针
a是实际上的字符串数据"testtes"的引用,所以类型是&str
a的地址在栈上,字符串"testtes"明显不是在栈上
testtes占用了7个字节,说明rust的字符串是没有结束符的
哪如何表示字符串的长度呢?
我们看一下a这个引用占用了多少字节
发现是16个字节,
再看一下b这个整形的引用
发现才只有8个字节
分析一下这是为什么
我们知道,表示64位地址需要8个字节,所以在64位系统上一个 引用至少占用8个字节
&b的那8个字节看来就是单纯的地址
而a的16个字节,看来除了地址以外,还应该有8个字节用来存字符串长度什么的,我们来看一下是不是这样
rustfn main() {
let a: &str = "testtes";
let address = &a as *const _;
// 把a存储的值转换为bytes数组
let bytes = unsafe { std::slice::from_raw_parts(address as *const u8, 16) };
println!("The address of \"{}\" is {:p}", a, a);
// 打印bytes数组,16进制
for b in bytes {
print!("{:02x} ", b);
}
}
htmlThe address of "testtes" is 0x5640ff1b6705
05 67 1b ff 40 56 00 00 07 00 00 00 00 00 00 00
果然没错,发现a这个引用用到的16位,前八位就是其指向的地址,后八位存的是字符串的长度。
这下完全破案了,心里面舒坦了。
关于对象
不过还有一个问题,就是如果我们用的不是&str,而是使用结构体String呢
a这个String的对象到底是像C语言一样是String本身还是指向String的“引用”呢?
做个实验如下
rustfn main() {
let a: String = "testtes".to_string();
// 将a地址转换为*const String类型
let address: *const String = &a as *const _;
// 获取a占用的内存大小
let size_of_a: usize = std::mem::size_of_val(&a);
println!("Size of a: {}", size_of_a);
// 直接去a的地址看看a到底装的什么
let bytes: &[u8] = unsafe { std::slice::from_raw_parts(address as *const u8, size_of_a) };
// 打印bytes数组,16进制
for b in bytes {
print!("{:02x} ", b);
}
println!();
}
htmlSize of a: 24
07 00 00 00 00 00 00 00 10 ab 7f b4 ea 55 00 00 07 00 00 00 00 00 00 00
答案已经出来了,rust种的结构体对象并不是结构体数据本身,也只是一个“引用”
不过还有值得注意的是,String的对象a占用了24个字节
其中中间的比较熟悉是真正的String结构体数据在堆上的地址,前后的似乎都是指的装的字符串的长度。
如果真是这样,那么rust非要让“引用”前后装两个长度呢
所以其中一个应该不是指的长度
让我们看下String的源码
rustpub struct String {
vec: Vec<u8>,
}
是使用的动态数据实现的,那么答案已经呼之欲出了,其中一个指的并不是长度,而是动态数组的容量。
我们做个实验验证一下
rustfn main() {
let mut a: String = "testtes".to_string();
// 打印a的值
print_data_of_a(&a);
a += "hello";
// 再次打印a的值,看看地址是否发生变化
print_data_of_a(&a);
}
fn print_data_of_a(pa: &String) {
// 将a地址转换为*const String类型
let address_of_a: *const String = pa as *const _;
// 获取a占用的内存大小
let size_of_a: usize = std::mem::size_of_val(pa);
// 直接去a的地址看看a到底装的什么
let bytes: &[u8] = unsafe { std::slice::from_raw_parts(address_of_a as *const u8, size_of_a) };
// 打印bytes数组,16进制
for b in bytes {
print!("{:02x} ", b);
}
println!();
}
html07 00 00 00 00 00 00 00 10 6b c9 d7 a7 55 00 00 07 00 00 00 00 00 00 00
0e 00 00 00 00 00 00 00 10 6b c9 d7 a7 55 00 00 0c 00 00 00 00 00 00 00
啊哈,两次的结果差异已经给出了答案
在a加上hello之后,前面的7变成了e,也就是14,由7翻倍而来
而后面的7变成了c也就是12
可以确定,前8个字节存储的就是容量,最后8个字节存储的是字符串的长度
打印某个对象在内存中的数据
最后提供一个函数,用于打印某个对象在内存中的数据
rustfn print_data_of_t<T>(pt: &T) {
// 将a地址转换为*const String类型
let address_of_t: *const T = pt as *const _;
// 获取a占用的内存大小
let size_of_t: usize = std::mem::size_of_val(pt);
// 直接去a的地址看看a到底装的什么
let bytes: &[u8] = unsafe { std::slice::from_raw_parts(address_of_t as *const u8, size_of_t) };
// 打印bytes数组,16进制
for b in bytes {
print!("{:02x} ", b);
}
println!();
}
用法是提供要打印变量的类型T和这个对象的引用pt,类型是&T
💬 评论区
暂无评论,来发表第一条吧。