在C ++中,程序员花了多少时间进行内存管理


39

习惯于垃圾回收语言的人通常会害怕C ++的内存管理。有工具,如auto_ptrshared_ptr将处理许多适合你的存储管理任务。许多C ++库早于这些工具,并拥有自己的方式来处理内存管理任务。

您花多少时间执行内存管理任务?

我怀疑它在很大程度上取决于您使用的库集,因此请说出您的答案适用于哪些库,以及它们的优劣之处。


1
确实不多,尤其是C ++ 0x,引用和STL。您甚至可以完全不使用内存管理来编写代码。
编码器

9
一般而言:如果您经验丰富,则不需要太多。如果您是C ++的新手,那么很多(->通常是在搜寻内存/资源泄漏)。
2011年

1
这些天,我发现真正的问题更多是关于追寻过时的参考文献。而且通常每次都很明显,只是很烦人,它之前没有被捕获:p
Matthieu M.

我知道这很老了,但是IMO内存管理是成为一名优秀程序员不可或缺的一部分。像STL容器这样的抽象很好,但是内存的无知与计算本身的想法背道而驰。人们还可能会问,如何从程序员的武器库中消除代数操纵,逻辑和循环。
imallett 2014年

大约“花了多少时间调试内存管理出错了”呢?就其本身而言,内存管理是可能的,并且在C ++中不是那么困难。事实是:设置它是一种精确的工艺,而且很容易发生乱搞。当您操蛋时,您甚至可能不会注意到,而随着时间流逝而积累的不稳定行为追溯到旧错误,这是您应该担心的实时陷阱。这就是为什么现代的非垃圾收集语言(我在考虑生锈)将很多责任移交给了编译器来检查典型错误。
ZJR

Answers:


54

现代C ++使您不必担心内存管理,除非您必须这样做,即直到您需要手动组织内存(主要是出于优化目的),或者如果上下文迫使您执行内存管理(认为是大型约束硬件)。我编写整个游戏时都没有操纵原始内存,只是担心使用容器是完成任务的正确工具,就像使用任何语言一样。

因此,它取决于项目,但是在大多数情况下,不是必须要处理的内存管理,而是对象生存期。这是使用智能指针解决的,它是RAII产生的惯用C ++工具之一。

一旦您了解了RAII,内存管理将不再是问题。

然后,当您需要访问原始内存时,将使用非常具体,本地化和可识别的代码(例如在池对象实现中)而不是“无处不在”来进行处理。

在这种代码之外,您不需要操纵内存,而只需对象的生命周期。

“难”的部分是要了解RAII。


10
绝对真实。在过去的5年中,我只在处理旧代码时才写了“删除”。
drxzcl 2011年

3
我在高度堆栈大小受限的嵌入式环境中工作。与RAII一样酷,如果堆栈空间不足,它就无法正常工作。因此,它又回到了指针微管理。
bastibe 2011年

1
@nikie我在操纵API的代码中使用了库智能指针,然后在特定于我的应用程序的代码中使用了标准或boost智能指针(如果我是对此做出决定的人)。如果您可以将库代码隔离在某些模块中,这些模块可以抽象化它们在应用程序中的使用方式,那么可以避免依赖项对API的污染。
克拉姆2011年

12
@Paperflyer:delete除非您有一个糟糕的实现,否则RAII不会比手动占用更多的堆栈空间。
DeadMG,2011年

2
@Paperflyer:堆上的智能指针占用相同的空间;区别在于,编译器在函数的所有出口上插入资源释放代码。而且因为这是如此广泛使用,这通常是良好优化(的方式如折叠多个出口一起,你不能-你不能把代码后return
MSalters

32

内存管理用于吓children孩子,但这只是程序员必须照顾的一种资源。考虑文件句柄,网络连接以及从OS获得的其他资源。

支持垃圾回收的语言通常不仅会忽略这些资源的存在,而且还会因不提供析构函数而使正确处理这些资源变得更加困难。

简而言之,我建议不要花太多的C ++开发人员时间来担心内存管理。正如klaim的回答所表明的,一旦您掌握了RAII,剩下的就只是反射。


3
我特别喜欢HttpWebRequest.GetResponse如何泄漏句柄并开始以GC语言崩溃。GC很酷,直到因为资源仍然泄漏而开始抽吸为止。msdn.microsoft.com/zh-cn/library/…请参阅“警告”。
编码器

6
+1用于将内存作为资源查看。是否有旧版代码,我们需要大声喊几遍:内存管理是一种技能,而不是诅咒
2011年

4
@Coder不知道我是否遵循。GC很烂,因为反正有可能滥用资源。我认为C#做得很好,提供确定性的资源使用IDisposable的释放...
马克斯

8
@Max:因为如果是垃圾回收,那么我希望不要担心通过使用和自定义IDisposables来获取愚蠢的资源。资源离开了作用域,仅此而已,应该对其进行清理。但实际上,我仍然必须思考和猜测哪些会泄漏,哪些不会泄漏。首先击败任何使用GC语言的原因。
编码器

5
@deadalnix他们确实有finalize构造。但是,您不知道何时调用它。是在套接字或WebResponse对象用尽之前?您会发现很多文章告诉您不应该依靠finalize-有充分的理由。
Dysaster

13

几乎没有。即使是像COM这样的旧技术,也可以为Standard指针编写自定义删除器,从而在很短的时间内将它们转换。例如,std::unique_ptr可以将其转换为使用五行自定义删除器来唯一保存COM引用。即使您必须手动编写自己的资源处理程序,诸如SRP和复制和交换之类的知识的普及也使得编写资源管理类以永久使用的相对容易。

事实是,共享,唯一和非所有权都随C ++ 11编译器一起提供,您只需要编写小型适配器即可使其即使在旧代码下也能正常工作。


1
您必须具备多少C ++技能?a)编写自定义删除器b)知道您需要自定义删除器吗?我问,因为在不了解全部情况的情况下,采用一种新的GC语言似乎很容易就可以接近正确了-在C ++中也很容易正确?
肖恩·麦克米伦

1
@SeanMcMillan:自定义删除器的编写和部署很简单,我提到的COM是针对所有COM类型的5行,任何接受过现代C ++基本训练的人都应该熟悉它们。您不能选择GCed语言,因为令人惊讶-GC无法收集COM对象。或文件句柄。或从其他系统获得的内存。或数据库连接。RAII将做所有这些事情。
DeadMG

2
通过“选择一种GC语言”,我的意思是我已经在Java / C#/ Ruby / Perl / Javascript / Python之间跳了起来,它们都具有相同的资源管理风格-内存大部分是自动的,而其他所有东西,您必须进行管理。在我看来,您在说C ++的管理工具可让您以与内存相同的方式管理文件句柄/数据库连接/等,并且一旦学习它就相对简单了。不做脑部手术。我理解正确吗?
肖恩·麦克米伦

3
@SeanMcMillan:是的,这是完全正确的,并且并不复杂。
DeadMG,2011年

11

当我是C ++程序员(很久以前)时,我花了很长时间担心尝试修复难以重现的错误时的内存管理错误

使用调制解调器C ++,内存管理已不再是一个大问题,但是您可以信任大型团队中的每个人都可以做到这一点。什么是成本/时间:

  • 培训(没有多少程序员精通这些问题)
  • 代码审查以查找内存管理问题
  • 调试内存管理问题
  • 始终要记住,应用程序某一部分中的错误可能是由于应用程序无关部分中的内存管理问题所致

因此,不仅是花费时间在“ ”上,这在大型项目上更是一个问题。


2
我认为一些C ++项目对由于写得不好的代码而导致的某些内存泄漏感到绝望。错误的代码将要发生,当它发生时,它也会占用很多其他人的时间。
杰里米,

@Jeremy,我发现当我从C ++迁移到C#时,仍然有很多写得不好的代码(如果不是更多的话),但是至少很容易找到程序中具有给定bug的部分。
伊恩

1
是的,这就是为什么大多数商店都迁移到Java或.NET的原因。垃圾收集可减轻不可避免的错误代码损坏。
杰里米(Jeremy)

1
奇怪的是,我们没有那些问题。
David Thornley

1
@DavidThornley,我认为很多问题都来自用C ++编写UI代码,这些天我看到的大多数C ++代码都不是UI
Ian

2

我经常使用boost和TR1库,从严格意义上讲,它们使内存管理(新/删除)不再是问题。另一方面,C ++中的内存分配并不便宜,并且必须注意创建这些奇特的共享指针的位置。您最终会大量使用工作区,或者使用基于堆栈的内存。通常,我会说这主要是设计问题,而不是实现问题。


2

客户需要多少时间?一旦掌握了,就很少了。当容器管理生命周期和引用时,这确实非常容易。imo,它比手动引用计数简单得多,并且如果您考虑用作文档的容器,它实际上是透明的,编译器可以方便地阻止您在设计良好的类型安全系统中执行无效的所有权转让。

我(作为客户)花费的大部分时间都花在包含来自其他api的类型上,因此它们在您的程序上下文中运行良好。例如:这是我的ThirdPartyFont容器,它支持这些功能,并实现破坏这种方式,并引用计数这种方式,并复制这种方式,和...。这些构造中的许多构造都需要放置到位,并且通常将它们放置在逻辑上。是否要将其包含在时间中取决于您的定义(无论如何,与这些API交互时,实现都必须存在)?

之后,您将需要考虑内存和所有权。在较低级别的系统中,这是很好且必要的,但是要实现如何移动内容可能会花费一些时间和脚手架。我不觉得这很痛苦,因为这是较低级别系统的要求。所有权,控制权和责任感是显而易见的。

因此我们可以将其转向使用不透明类型的基于C的api:我们的容器使我们能够抽象化管理寿命和复制这些不透明类型的所有小实现细节,这最终使资源管理非常简单,并节省了时间,缺陷,并减少了实现。

使用它们真的非常简单-问题(来自GC)是您现在必须考虑资源的生命周期。如果弄错了,可能要花很多时间才能解决。相比之下,学习和集成明确的生命周期管理是很复杂的(并非对所有人而言),这是真正的障碍。一旦您可以轻松地控制生命周期并使用好的解决方案,那么管理资源生命周期就非常容易。这不是我每天的重要工作(除非遇到了困难的错误)。

如果您不使用容器(自动/共享指针),那么您只是在痛苦。

我已经实现了自己的库。了一些时间来实现这些东西,但是大多数人都在重用(通常是个好主意)。


1

您的意思是像手动释放内存,关闭文件之类的事情?如果是这样,我想说的是最低限度的语言,通常少于我使用过的大多数其他语言,尤其是当我们将其概括为不仅是“内存管理”而是“资源管理”时。从这种意义上讲,我实际上认为C ++比Java或C#需要更少的手动资源管理。

这主要是由于析构函数自动破坏资源(内存或其他)。通常,只有在我要实现vlow级别的数据结构(大多数人不需要做的事情)或使用仅花费一点时间的C API时,才需要在C ++中手动释放/销毁资源。将需要手动释放/销毁/关闭的C资源包装到符合RAII的C ++包装器中。

当然,如果用户要求在图像编辑软件中关闭图像,则必须从收藏夹或其他内容中删除图像。但是希望不要将这种情况视为在这种情况下很重要的“内存”或“资源”管理,因为如果您想在那时释放与该图像关联的内存,那么几乎所有语言都需要这样做。但是同样,您要做的就是从集合中删除图像,然后由图像析构函数负责其余的工作。

同时,如果我将其与Java或C#进行比较,通常会发现人们不得不在那里手动关闭文件,手动断开套接字,将对象引用设置为null以允许对其进行垃圾回收等。还有更多的手动内存和如果您问我,用这些语言进行资源管理。在C ++中,您通常甚至不需要unlock手动使用互斥锁,因为互斥锁超出范围时,互斥锁会自动为您执行此操作。例如,您永远不必在C ++中执行以下操作:

System.IO.StreamReader file = new System.IO.StreamReader(path);
try
{
    file.ReadBlock(buffer, index, buffer.Length);
}
catch (System.IO.IOException e)
{
    ...
}
finally
{
    if (file != null)
        file.Close();
}

无需做任何事情,例如在C ++中手动关闭文件。无论结果是超出范围还是正常或异常执行路径,它们最终都会在超出范围时立即自动关闭自己。类似与内存相关的资源,例如std::vector。像file.Close()上面这样的代码通常会被皱着眉头,因为,特别是在一个finally块的上下文中,这表明当围绕C ++的整个思路要自动化时,本地资源需要手动释放。

在手动内存管理方面,我想说C要求最大,Java / C#要求中等,而C ++要求最小。有很多理由不愿使用C ++,因为它是一种很难掌握的语言,但是内存管理不应该是其中之一。相反,我实际上认为这是这方面最简单的语言之一。

当然,C ++确实允许您开始手动分配内存并调用operator delete/delete[]以手动释放内存。它还允许您使用C函数,例如mallocfree。但这是一种古老的编码方法,我认为这种方法早在人们赞扬之前就已经过时了,因为Stroustrup在他甚至从很早就创造出该术语之前就提倡RAII。因此,我什至不认为说“现代C ++”实现资源管理自动化是不公平的,因为这一直都是我们的目的。否则,您几乎无法获得异常安全性。只是在90年代初期,许多被误导的开发人员试图将C ++像使用对象的C一样使用,常常完全忽略了异常处理,并且从来不应该以这种方式使用它。如果您使用C ++实际上总是打算使用它的方式,那么内存管理是完全自动化的,通常不需要您手动处理(或应该处理)很多东西。


1
现代Java具有“尝试资源”的功能,该功能可以删除finally块中所有混乱的代码。很少需要有finally块。设计师似乎复制了RAII概念。
kiwiron

0

取决于团队中的高级技术主管。在某些公司(包括我的公司)中,没有所谓的智能Poiner概念。它被认为是花哨的。因此,人们只需将删除操作放到各处,就会有一个驱动器用于每2个月修复一次内存泄漏。新一轮的delete语句无处不在。因此,取决于公司和在那里工作的人的类型。


1
您的环境中有什么阻止您auto_ptr和朋友使用的东西吗?
肖恩·麦克米兰

2
喜欢你的公司不写C ++代码的声音,你写C.
gbjbaanb
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.