Rust会释放被覆盖变量的内存吗?

yuvru6vn  于 2024-01-08  发布在  其他
关注(0)|答案(4)|浏览(158)

我在Rust的书中看到,你可以定义两个同名的变量:

  1. let hello = "Hello";
  2. let hello = "Goodbye";
  3. println!("My variable hello contains: {}", hello);

字符串
这将输出:

  1. My variable hello contains: Goodbye


第一个hello会发生什么?它会被释放吗?我怎么才能访问它?
我知道给两个变量命名相同是不好的,但是如果因为我在100行以下声明它而意外发生这种情况,那将是一个真实的痛苦。

44u64gxh

44u64gxh1#

Rust没有垃圾收集器

Rust是否会释放被覆盖变量的内存?
是的,否则就是内存泄漏,这将是一个非常糟糕的设计决策。当变量被重新赋值时,内存被释放:

  1. struct Noisy;
  2. impl Drop for Noisy {
  3. fn drop(&mut self) {
  4. eprintln!("Dropped")
  5. }
  6. }
  7. fn main() {
  8. eprintln!("0");
  9. let mut thing = Noisy;
  10. eprintln!("1");
  11. thing = Noisy;
  12. eprintln!("2");
  13. }

个字符
第一个hello会发生什么
它是有阴影的。
变量引用的数据不会发生任何“特殊”的情况,只是您无法再访问它。当变量超出范围时,它仍然会被删除:

  1. struct Noisy;
  2. impl Drop for Noisy {
  3. fn drop(&mut self) {
  4. eprintln!("Dropped")
  5. }
  6. }
  7. fn main() {
  8. eprintln!("0");
  9. let thing = Noisy;
  10. eprintln!("1");
  11. let thing = Noisy;
  12. eprintln!("2");
  13. }
  1. 0
  2. 1
  3. 2
  4. Dropped
  5. Dropped

的字符串
另请参阅:

我知道如果两个变量的名字相同
这不是“坏”,这是一个设计决策。我想说,像这样使用阴影是一个坏主意:

  1. let x = "Anna";
  2. println!("User's name is {}", x);
  3. let x = 42;
  4. println!("The tax rate is {}", x);


对我来说,这样使用阴影是合理的:

  1. let name = String::from(" Vivian ");
  2. let name = name.trim();
  3. println!("User's name is {}", name);


另请参阅:

但如果这是偶然发生的,因为我声明它低于100行,这可能是一个真实的的痛苦。
不要有太大的函数,以至于你“不小心”做了一些事情。这适用于任何编程语言。
是否有手动清除内存的方法?
您可以调用drop

  1. eprintln!("0");
  2. let thing = Noisy;
  3. drop(thing);
  4. eprintln!("1");
  5. let thing = Noisy;
  6. eprintln!("2");
  1. 0
  2. Dropped
  3. 1
  4. 2
  5. Dropped

但是,正如oli_obk - ker所指出的,在函数退出之前,变量占用的堆栈内存不会被释放,只有变量占用的资源才会被释放。
所有关于drop的讨论都需要展示它的(非常复杂的)实现:

  1. fn drop<T>(_: T) {}


如果我在其他函数之外的全局作用域中声明变量呢?
全局变量永远不会被释放,即使你可以创建它们。

展开查看全部
vc6uscn9

vc6uscn92#

当涉及到删除顺序时,shadowingreassigning(重分配)变量之间有区别。
所有局部变量在超出作用域时通常会被删除,按照声明的相反顺序(参见 The Rust Programming Languagechapter on Drop)。这包括隐藏变量。通过将值 Package 在一个简单的 Package 器结构中,当它( Package 器)被删除时(就在 * 值本身被删除之前)打印一些东西,可以很容易地检查这一点:

  1. use std::fmt::Debug;
  2. struct NoisyDrop<T: Debug>(T);
  3. impl<T: Debug> Drop for NoisyDrop<T> {
  4. fn drop(&mut self) {
  5. println!("dropping {:?}", self.0);
  6. }
  7. }
  8. fn main() {
  9. let hello = NoisyDrop("Hello");
  10. let hello = NoisyDrop("Goodbye");
  11. println!("My variable hello contains: {}", hello.0);
  12. }

字符串
打印以下内容(playground):

  1. My variable hello contains: Goodbye
  2. dropping "Goodbye"
  3. dropping "Hello"


这是因为作用域中的新let绑定不会覆盖以前的绑定,所以就像您编写了

  1. let hello1 = NoisyDrop("Hello");
  2. let hello2 = NoisyDrop("Goodbye");
  3. println!("My variable hello contains: {}", hello2.0);


请注意,此行为与下面的代码(playground)不同,表面上非常相似:

  1. fn main() {
  2. let mut hello = NoisyDrop("Hello");
  3. hello = NoisyDrop("Goodbye");
  4. println!("My variable hello contains: {}", hello.0);
  5. }


这不仅会以相反的顺序删除它们,而且会在打印消息之前删除第一个值!这是因为当你给一个变量赋值时(而不是用一个新的变量来隐藏它),原始值会在新值被移入之前被 * 首先 * 删除。
我开始说局部变量在超出作用域时“通常”会被删除。因为你可以在变量中移入或移出值,所以有时候直到运行时才能分析出什么时候需要删除变量。在这种情况下,编译器实际上是inserts code to track "liveness" and drop those values when necessary,所以你不会意外地因为变量的值而导致泄漏。(但是,仍然可以通过调用mem::forget或创建具有内部可变性的Rc-cycle来安全地泄漏内存。

参见

展开查看全部
0pizxfdo

0pizxfdo3#

这里有几点需要注意:
在你给出的程序中,当编译它时,“Hello”字符串没有出现在二进制文件中。这可能是编译器优化,因为第一个值没有使用。

  1. fn main(){
  2. let hello = "Hello xxxxxxxxxxxxxxxx"; // Added for searching more easily.
  3. let hello = "Goodbye";
  4. println!("My variable hello contains: {}", hello);
  5. }

字符串
然后测试:

  1. $ rustc ./stackoverflow.rs
  2. $ cat stackoverflow | grep "xxx"
  3. # No results
  4. $ cat stackoverflow | grep "Goodbye"
  5. Binary file (standard input) matches
  6. $ cat stackoverflow | grep "My variable hello contains"
  7. Binary file (standard input) matches


请注意,如果打印第一个值,字符串会出现在二进制文件中,因此这证明这是编译器优化,不存储未使用的值。
另一件需要考虑的事情是,分配给hello的两个值(即“Hello”和“Goodbye”)都有一个&str类型。这是一个指向编译后静态存储在二进制文件中的字符串的指针。动态生成字符串的一个例子是当您从某些数据生成哈希值时,如MD5或SHA算法(结果字符串不静态存在于二进制文件中)。

  1. fn main(){
  2. // Added the type to make it more clear.
  3. let hello: &str = "Hello";
  4. let hello: &str = "Goodbye";
  5. // This is wrong (does not compile):
  6. // let hello: String = "Goodbye";
  7. println!("My variable hello contains: {}", hello);
  8. }


这意味着变量只是指向静态内存中的一个位置。在运行时不会分配内存,也不会释放内存。即使不存在上面提到的优化(即省略未使用的字符串),只有hello指向的内存地址位置会改变,但内存仍然被静态字符串使用。
对于String类型,情况会有所不同,请参考其他答案。

展开查看全部
kh212irz

kh212irz4#

我相信这个问题已经得到了彻底的回答,但我想给予更多的了解,因为我有一个similar question
1.问题中的示例代码只包含&str到程序二进制中的字符串常量,如这里和这里所解释的。没有什么需要清理的。
1.这些值被隐藏而不是被替换,但是...
1.如果考虑String::from(...) s并重新分配相同的变量,this Rustonomicon article解释了何时删除值。

相关问题