当前位置: 首页 > 科技观察

Rust、Go、C,谁才是“内存管理大师”?

时间:2023-03-12 07:34:06 科技观察

翻译|陆新旺、赵云策划|Ethan每种编程语言都有自己的“威力”,但说到内存管理,Rust的呼声却不是一般的高。GC(垃圾收集)?手动分配?对于掌握了Rust奥秘的开发者来说,这些词汇简直是薄弱。我们都知道Rust编程语言的主要卖点之一就是它的内存安全性。Rust对待内存非常有自己的个性。与使用垃圾收集器的Haskell、Ruby和Python等编程语言不同,Rust为开发人员提供了以独特方式高效使用和管理内存的快速特性。Rust通过使用三个概念管理内存来实现内存管理:借用检查器、所有权和借用来管理和确保跨堆栈和堆的内存安全。本文讨论了Rust借用检查器,Rust的内存管理与Go和C等其他语言相比如何,以及Rust借用检查器的缺点。内存是如何工作的在讨论Rust如何管理内存之前,让我们回顾一下计算机内存是如何工作的。分配给运行程序的计算机内存分为栈和堆。堆栈是一种线性数据结构,它按顺序存储局部变量,而不用担心内存的分配和重新分配。每个线程都有自己的堆栈,当线程停止运行时,每个堆栈都会被释放。数据以后进先出(LIFO)模式存储——新数据堆积在旧数据之上。堆是用于随机存储全局变量的分层数据结构,内存分配和重新分配可能是一个问题。当一个文字被压入堆栈时,它有一个明确的内存位置;这使得分配和重新分配(推送和弹出)变得容易。然而,在堆上分配内存的随机过程会在使用内存时产生显着的开销,由于在堆上分配内存时涉及复杂的引用记录,这使得重新分配内存的速度变慢。局部变量、函数和方法驻留在栈上,所有其他变量驻留在堆上;因为堆栈具有固定的有限大小。Rust通过在堆栈上存储文字(整数、布尔值等)来有效地处理内存。结构和枚举等类型的变量在编译时存储在堆中,因为它们没有固定大小。所有权(Ownership):“值”的所有权是Rust中的一个概念,用于在没有垃圾收集器的情况下保持内存安全。Rust强制执行以下所有权规则:每个值都有一个称为所有者(owner)的变量每个值都有一个且只有一个所有者如果您将一个变量分配给一个新的所有者,则原始值将被删除,否则它现在将有两个所有者。当程序被编译时,Rust编译器会在编译程序之前检查程序是否遵守这些所有权规则。如果程序遵循所有权规则,则程序编译并执行,否则编译失败。Rust使用借用检查器来验证所有权规则。借用检查器验证所有权模型以及内存(堆栈或堆)中的值是否超出范围。如果该值超出范围,则释放内存。但这并不意味着访问该值的唯一方法是通过原始所有者。这时候就引入了“借”的概念。借用(borrowing):重用很有用为了让程序可以重用代码,Rust提供了借用的概念,类似于指针。所有权可以临时从所有者那里借用,并在借用的变量超出范围时归还。可以通过使用&(&)符号传递对所有者变量的引用来借用值。这在函数中非常有用。这是一个例子:1.fnlist_vectors(vec:&Vec){2.forelementinvec{3.println!("{}",element);4.}5.}该函数还可以通过使用对变量的可变引用来修改借用的变量。普通变量可以通过mut关键字设置为变量,那么变量引用只需要在&后面加上关键字mut即可。当然,在可以进行可变引用之前,变量本身必须是可变的。1.fnadd_element(vec:&mutVec)->&mutVec{2.vec.push(4);3.4.returnvec5.}左右滑动查看完整代码所有权和借用的概念是可能的似乎没有那么灵活,除非你理解复制、复制、移动的概念,以及它们如何协同工作.CopyOwnershipCopying通过复制位来复制值。复制仅适用于实现复制特征的类型。一些内置类型默认实现了Copy特性。在堆栈中,访问变量和更改所有权很容易,而在堆中复制则不容易,因为位操作涉及位移动和位操作,而堆栈更适合此类操作。下面是一个在堆中复制值的例子。1.fnmain(){2.letinitial=6;3.letlater=初始;4.println!("{}",初始);5.println!("{}",later);6.7。}变量initial和later在同一个作用域(scopescope)声明,然后通过赋值将initial的值复制到later。当变量在同一范围内时,initial将不再存在。这是为了防止必须重新分配变量。输出:尝试打印初始变量的值将引发编译错误,因为借用检查器注意到变量的所有权已转移。如果想保值怎么办?Rust提供了克隆变量的能力。复制变量您可以将值分配给新所有者,同时使用复制方法将值保留在旧所有者中。但是,您复制的类型必须提前实现复制特征。1.fnmain(){2.letinitial=String::from("显示所有权");3.让以后=初始。克隆();4.println!("{}=={}[显示克隆成功]",initial,later)5.}变量initial在后面的变量声明中被复制,这两个变量驻留在堆上。如果此时借用,这两个变量将引用同一个对象;然而,在这种情况下,这两个变量是堆上的新声明并占用单独的内存地址。移动所有权Rust提供了跨作用域改变变量所有权的能力。当函数按值获取参数时,函数中的变量成为该值的新所有者。如果您选择不移动所有权,则可以通过引用传递参数。这是一个如何将变量的所有权从一个变量转移到另一个变量的示例。1.fnchange_owner(val:String){2.3.println!("{}已从其所有者处移走,现在可以引用为val",val)4.}5.6.fnmain(){7.8.letvalue=String::from("ChangeOwnershipExample");9.change_owner(值);10.}change_owner函数获取先前声明的字符串的所有权,并且当接受值变量的值作为参数时,该字符串的所有权。此时尝试打印值变量将导致错误。Rust的借用检查器的缺点如果Rust的借用检查器一切都完美,其他系统编程语言可能会切换或提供具有借用检查器实现的版本。当谈到内存管理时,它是用户体验和便利性之间的权衡。各大编程语言内存管理解决方案一览使用垃圾收集器的语言让内存管理更简单,但同时降低了内存管理的灵活性,而像Rust和C这样的语言让开发者可以快速访问内存,只要遵循它的某些规则,就像Rust的所有权规则,以及在C中内存管理如何留给开发人员。借用检查器可能很复杂且有限制。随着项目规模的扩大,确定所有权规则可能会变得困难,并且进行更改可能会很昂贵。虽然Rust编译器会执行检查以防止诸如悬空引用之类的错误,但Rust还为开发人员提供了unsafe关键字以免除特定代码块的检查。如果在外部使用dependencyunsafe关键字,这可能不利于代码安全。许多开发人员,无论是初学者还是专家,都会遇到来自借用检查器的所有权错误,甚至更多的错误来自于在Rust中实现复杂的数据结构和算法。Rust和C的内存管理比较C编程语言是一种流行的系统编程语言,它不使用垃圾收集器或借用检查器来管理内存;相反,C允许开发人员根据需要手动和动态地管理内存。C开发者可以使用标准库中定义的malloc()、realloc、free、calloc等函数在堆中进行内存管理,一旦栈中的内存超出作用域就会自动释放。哪种方法更好通常取决于您要构建的内容。虽然开发人员可能会发现Rust借用检查器有些局限性,但它使开发人员能够更有效地管理内存,而无需成为内存管理专家。Rust开发人员还可以选择在没有标准库的情况下使用Rust,并获得类似C的体验,其中所有内存管理都是手动完成的。Rust及其标准库和借用检查器更适合构建需要处理资源密集型的应用程序。Rust和Go内存管理比较Rust和Go是相当新的、功能强大的语言,经常在很多方面进行比较,包括内存管理。Go使用非分代并发以不同的方式管理内存,这是一种三色标记和清除垃圾收集器,允许开发人员使用new和make函数手动分配内存,而垃圾收集器负责内存回收。Go的垃圾收集器由一个执行代码并将对象分配到堆的修改器和一个帮助释放内存的收集器组成。Go还允许开发人员通过使用关闭垃圾收集器的不安全或运行时包来手动访问和管理内存。运行时模块的调试包提供了调试程序的功能,通过使用SetGCPercent方法(有助于设置垃圾收集器目标百分比)等方法设置垃圾收集器参数来调试程序。Go的垃圾收集器一直受到Go开发者社区的批评,并且在过去几年中一直在改进。Go开发人员可能希望手动管理内存并从该语言中获得更多收益,默认情况下该语言具有垃圾收集器,不允许像C这样的语言在手动内存管理方面具有灵活性。Go和Rust在讨论内存管理时没有可比性,因为它们有不同且不相关的内存管理方式,在灵活性和内存安全性之间进行权衡,特别是当两种语言的开发人员都想使用其他语言的东西时。开发人员选择Go来构建需要简单性和灵活性的服务和应用程序,而选择Rust来构建需要低级交互但对性能和内存安全至关重要的应用程序。借用跳棋:Rust人绕不开的坎借用跳棋是Rust之旅中绕不开的难关。学习曲线在这里变得非常陡峭。随着borrowchecker的不断报错和警告,很多有Python、JavaScript等语言背景的Rust崇拜者难免怀疑人生:“硬着头皮用borrowchecker还有前途吗?放弃吧!”需要了解是的:任何试图绕过借用检查器的尝试都是徒劳的。这是一场你永远赢不了的决斗。唯一要做的就是将借用检查器视为一个纪律制定者,它教你如何编写内存高效的Rust代码,同时你必须通过学习更多关于如何编写更安全、内存高效的Rust代码来玩得好与借用检查员游戏。随着用Rust编写的代码量的增加,开发人员当然会像其他语言一样,找到防止借用检查器常见错误的最佳方法。学会与借用检查器斗智斗勇,开发者无法避免。结论毫无疑问,Rust是一种将在未来几年内存在并广泛使用的语言。我们已经看到像Discord和Microsoft这样的公司用Rust重写了他们的一些代码库,因为它能够通过外部函数接口(FFI)与C和C++等语言以及AWS、Mozilla等许多其他语言进行交互。)在产品的不同部分使用Rust。所有权和借用是Rust中的基本概念,当你编写更多的Rust程序时,很有可能你会从借用检查器中得到一个错误。使用正确的工具很重要;您可能会考虑在内存管理不是很重要并且性能很重要的程序中使用Go。原文链接:https://stackoverflow.blog/2022/07/14/how-rust-manages-memory-using-ownership-and-borrowing/译者介绍鲁信旺,社区编辑,编程语言爱好者,数据库,架构,云源兴趣浓厚,目前就职于某跨境电商海外营销公司,担任后端开发。