在IT技术中,不释放内存是否可接受?

15

我正在开发一个项目,该项目应该使用以下语法从命令行中调用:

program-name input-file

这个程序需要处理输入数据,计算一些东西并将结果输出到标准输出。

我选择使用C++语言,因为有很多原因我不想讨论。计算阶段将会高度符号化(类似于编译器),并且使用相当复杂的动态分配数据结构。特别是,它不适合RAII风格的编程。

我在想,如果我预计整个计算过程消耗的内存比可用内存少,并且操作系统在程序结束后可以一次性回收所有内存(假设程序在几秒钟内终止),那么是否可以不考虑释放内存?你们对此有什么感受?

作为备选方案,如果我的项目需要作为服务器或交互式运行,我想总是可以在源代码中加入垃圾收集器。有人使用过C++的垃圾收集器吗?它们工作得好吗?


我想对“它不适合RAII风格的编程”进行一些澄清。你是否担心循环引用?智能指针在其他情况下与常规指针无法区分。 - Mark Ransom
指针将在函数之间随机创建和传递。某个特定对象的所有者不清楚。这就是我的意思。 - user51568
4
智能指针根本不需要明确的所有者。当每个人使用完指针后,它会自动被删除。这比内存泄漏要好得多。 - Gorpik
19个回答

22

在问题描述的特定情况下,它不应该会有任何问题。

然而,这并不完全正常。静态分析工具会对此提出抱怨。最重要的是,这会养成不好的习惯。


1
我所见过的反对不释放内存最有力的论据是它会养成不良习惯。 - user51568
如果这是为了比赛,而且之后没有人会使用它,那当然可以... - Calyth
1
@stefan.ciobaca:对于现有的GC进行改进将是一场噩梦。要么你先做,要么就不做。 - Martin York
1
迎合静态分析工具可能是使用没有垃圾回收的语言的代价之一。这通常会导致无意义的页面错误和减慢速度,因为需要在进程结束时遍历所有内存以释放内存。 - Luke Quinane

15
有时不释放内存是正确的做法。
我曾经写过编译器。构建语法树并遍历它以编写中间代码后,我们只需要退出即可。释放树会:
  • 使编译器变慢,而我们当然希望尽可能快。
  • 占用代码空间。
  • 花费时间编写和测试内存释放代码。
  • 违反“没有代码比‘没有代码’执行得更好”的准则。
希望对你有所帮助!顺便说一句,“那时候”内存是非虚拟且最小的,计算机速度很慢,前两个问题都是非常重要的考虑因素。

12

我感觉像是“卧槽!!!”

这么说吧:

  • 你选择一种不包含垃圾回收器的编程语言,我们不能问为什么。

  • 你基本上是在说你懒得去清理内存。

卧槽!懒惰并不是任何事情的好理由,尤其是在不释放内存的情况下玩弄内存。

释放内存吧,这是一种不好的习惯,场景可能会改变,到那时你可能有一百万个理由需要释放内存,而不这样做的唯一理由就是懒惰。不要养成坏习惯,要习惯做正确的事情,这样将来你就会更容易做对它们!!


  1. 正确
  2. 不,我是说在正确的时间释放内存并且正确地执行这个操作非常困难。
“只要释放内存” 请参考上面的内容。
- user51568
当然很难,正因为如此你才试图避免它。你不想付出努力,这就是懒惰的定义(请不要个人化,只是这确实是一种非常糟糕的看待事物的方式)。 - Jorge Córdoba
1
虽然我同意这种情绪,但这相当激进,而且我认为它不如其他回答那样令人信服。 - deworde

8

不释放内存可能不会出现问题,但这是一种不好的做法。


2
如果它没有造成任何问题,为什么会被认为是不良实践呢? - Konrad Rudolph
程序将在退出时释放内存。但作为程序员,管理内存应该成为一种习惯。当我看到别人的代码时,如果发现内存已经分配(好吧,他检查了malloc!= null),但没有被管理,我会有一种不好的感觉。我怎么能用它来构建一个库呢? - Pierre
@Konrad-Rudolph,关于你提到的“如果它没有造成任何问题,那么为什么它是不好的”这一点,我理解。但是,如果以后您需要良好的内存使用或像Pierre建议的库,那么稍后再添加会更加困难。但是我同意“如果它没有做错任何事情,那么它就不是坏的”的说法。 - BobbyShaftoe

8

Joel Coehoorn是正确的:

这不应该引起任何问题。

然而,这并不正常。 静态分析工具会抱怨 关于它。最重要的是,它建立了 不良习惯。

我还想补充一点,考虑在编写代码时思考释放内存可能比尝试事后修改更容易。所以我可能会使其释放内存;你不知道你的程序将来可能如何使用。

如果您想要一个真正简单的方法来释放内存,请查看Apache使用的“池”概念


我同意提前思考会更容易一些。我的问题是,即使这样做很困难,而且在编码/设计阶段经常出现任何非平凡的修改(根据经验),都可能导致未知的内存泄漏。 - user51568
有趣的链接。如果我理解正确,这就是我目前正在做的事情,只不过我的池子实际上是有类型的。 - user51568

5

我认为这是不可接受的。您已经提到了潜在的未来问题。不要认为它们一定容易解决。

像“...鉴于我预计整个计算过程将消耗更少的时间...”这样的话,往往是致命的。同样,用某些功能重新调整代码是他们讨论但从未实现的事情之一。

短期内不释放内存可能听起来不错,但长期来看可能会带来巨大的问题负担。对我个人而言,我不认为这值得。

有两种策略。要么你从一开始就设计GC,这需要更多的工作,但会有回报。对于许多小对象,使用池分配器并跟踪内存池可能会更好。这样,您可以跟踪内存消耗,并避免许多类似的代码,但没有分配池可能会创建的问题。

要么您从一开始就在程序中使用智能指针。尽管它会使代码变得混乱,但我实际上更喜欢这种方法。一种解决方案是大量依赖模板,这可以减少引用类型时的冗余。

看看像WebKit这样的项目。他们的计算阶段类似于您的计算阶段,因为他们为HTML构建解析树。他们在整个程序中都使用智能指针。

最后:“这是一个风格问题...草率的工作往往会形成习惯。”- David Eddings的魔法城堡中的Silk。


我不明白从没有垃圾回收到有垃圾回收有什么大问题。你能提供更多细节吗? - user51568
模板如何在引用类型时消除冗余?有例子吗? - user51568
我会出于好奇心去看一下 WebKit。它的渲染阶段可能在某种模糊的方式上与我所做的相似。然而,我看到了很大的区别。由于性能原因,它们可能需要多次渲染相同的内容,并在运行之间缓存一些东西。 - user51568

3
如果程序的运行时间非常短,则不应该有问题。然而,懒得释放您分配的内存并且无法追踪您分配的内容是两个完全不同的问题。如果您只是失去了追踪,那么现在是时候问自己您是否真正知道您的代码对计算机做了什么。
如果您只是匆忙或懒惰,并且您的程序的生命周期与它实际分配的内存相比较小(例如,每秒分配10 MB并运行30秒就不小),那么您应该没问题。
关于释放已分配内存的唯一“高贵”的争论在于程序退出时...是应该释放所有内容以避免valgrind因泄漏而抱怨,还是让操作系统来做?这完全取决于操作系统以及您的代码是否可能成为一个库而不是一个运行时间短的可执行文件。
运行时的泄漏通常是不好的,除非您确实 知道 您的程序只会运行很短一段时间,而且不会导致其他重要程度远高于您的程序的进程出现脏页转储。

3
将使用相当复杂的动态分配数据结构。特别是,它不适合RAII样式编程。
我几乎可以确定这是懒惰编程的借口。为什么你不能使用RAII呢?是因为你不想跟踪你的分配,没有指向它们的指针吗?如果是这样,你如何使用分配的内存 - 总有一个包含某些数据的指针指向它。
是因为你不知道它应该何时被释放吗?将内存留在RAII对象中,每个对象都由某些东西引用,并且当包含对象被释放时,它们会自动释放 - 如果你希望将其作为服务器运行,这一点尤其重要,服务器的每个迭代实际上都运行着一个持有所有其他对象的“主”对象,因此您只需删除它,所有内存就会消失。这也有助于防止你过时的GC。
是因为你所有的内存都被分配并保持全程使用,只在最后才释放吗?如果是这样,请参见以上内容。
如果你真的想不出一个设计来避免内存泄漏,那么请至少使用私有堆。在退出之前销毁该堆,你就已经有了一个更好的设计,即使有点“hacky”。
有一些情况下,内存泄漏是可以接受的 - 静态变量,全局初始化数据等。但这些通常不会很大。

是的,我承认在编程时我很懒,而且我为此感到自豪。不详细说明,我所做的类似于一个编译器的数据结构。 - user51568
我实际上正在跟踪所有对象在一个“私有堆”中,正如你所说的(我称之为对象池)。 - user51568
“私有堆”是指私有堆 - 例如Windows中的HeapCreate。懒惰只会导致你比本来应该做好它时做更多的工作。 - gbjbaanb

3

像boost和TR1中的shared_ptr这样的引用计数智能指针也可以帮助您以简单的方式管理内存。

缺点是您必须包装使用这些对象的每个指针。


“MyClass * pointer = new MyClass” 和 “shared_ptr<MyClass> pointer(new MyClass)” 之间的区别对我来说并不令人不愉快。” - Mark Ransom
引用计数很不错,但对于复杂结构,您需要一些东西来打破循环引用。 - Constantin
@Constantin:这就是weak_ptr的作用所在。至于增加的打字工作,typedef怎么样呢?如果这是你自己的类,可以在每个头文件中添加类似“typedef shared_ptr<MyClass> spMyClass”的内容。如果你不能改变头文件,那么就在一个策略性的位置进行定义。 - foraidt

3
我以前做过这样的事情,只是后来发现需要程序能够处理多个输入而不需要单独的命令,或者程序的核心非常有用,需要将其转换为库例程,可以从另一个预计不会终止的程序中多次调用。回去重新设计程序比从一开始就使其无泄漏更困难。
因此,虽然按照您描述的要求从技术上讲是安全的,但我建议不要采用这种做法,因为您的要求可能在某些时候会改变。

我理解这种做法的原因。如果我的程序变成一个库时,与该库的接口基本上将由一个程序调用组成。这样能减轻痛苦吗? - user51568

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接