Evil of Memory Leak
本篇含技术内容,不喜者勿入。
这是一个关于内存泄漏的凄惨故事。内存泄漏,乃是C/C++ Programmer最恐惧的Bug之一;来不知其来,去往往也不知其去,潜伏在层层花括号深处,当你发现它的时候,往往已经是程序崩溃之时。我几天前还被一个诡异的memory bug困扰了一晚,此bug举止异于常bug,在调试时单步执行没有任何问题,但一运行就出一些稀奇古怪的结果。以至于我都无法定位bug的位置,最后不得不借助于石器时代的printf调试大法,在可能出错的位置加上一摞一摞的printf才找到bug的可能位置。就算我已经定位到某一行代码,在我盯着屏幕看了半天,看得眼前快要出现幻觉,还是想不通bug从何而来。果然,从哪里来,到哪里去,是个终极关怀级别的问题。这一块代码和内存分配有关,无奈之下只好重写一遍,用了个稍微不同的逻辑。然后bug消失,程序再次和谐。
我个人喜欢用C写东西,尽管已经有无数与memory leak搏斗的经验,每次仍然免不了犯这类错误,经验虽然不停增长,错误好像未见减少。所以有时候觉得Java, C#这样的带Garbage Collection的语言也不错,但昨天看了一篇文章,讲述了一个C#中的内存泄漏怎样导致200万美金旁落的悲惨故事。涉及到这么大的支票,我们的兴趣总会提高一些。
文章作者是Princeton大学电子工程系的学生,他(当然还有一堆牛人)参加了一项汽车自动驾驶比赛,奖金是2个million。他们控制汽车的程序是用C#写的,有1万行代码。在比赛的最后关头,汽车老是开一会就失去响应,然后要么一头撞到障碍物(例如灌木丛),或者就茫然地在大地上行走直到没油。分析起来,问题出在代码的障碍物探测模块,它负责寻找汽车前进道路上的障碍。当然,如果车已经开过去了,那么刚才路上的障碍信息就没用了,所以会被垃圾回收器处理掉。最后的Bug是,这些对象,理应被丢弃掉的废物,根本没有被回收。一开始只见内存占用像滚雪球一样越来越大,开了一会汽车就歇了。可想而知,2百万肯定是黄了,最郁闷的是都这样了还不知道为什么垃圾回收器就是不干活,正宗的死不瞑目。
比赛完了,牛人们坐下来开会总结经验教训。其中某牛从网上下了个ANTS Profier,分析了他们代码的内存使用,终于看到了问题所在。前面说过这是一个C#式的内存泄漏,并不是说垃圾回收器出了问题,没有处理该回收的内存。虽然微软很不招人喜欢,但要相信它还不至于犯这种错误。垃圾回收器判断一个对象是否该被回收,是根据它能否被当前代码所引用。如果代码里已经不能访问到这个对象,那么它自然是垃圾。出问题的程序里,虽然程序员已经清除了到这些废物对象的引用,但这些对象作为事件的订阅者(subscriber,基本上是C#程序员的黑话),还在被其他活跃对象默默地引用。最后的结果自然是内存耗尽。所以那篇回顾文章的作者在标题里就痛心疾首地喊:”If Only We’d Used ANTS Profiler Earlier…”,言下之意,还在为那几乎到手的200万美金心疼。
这个故事告诉我们一个道理:垃圾回收机制虽然很方便,但它实际上是把一个问题变成了另外一个问题(手动释放内存到去掉所有引用)。相对来说,发生错误的可能性减少了,“去掉所有引用”这句话是我在这里说说的,听起来有些别扭,实际上在代码中是一件比较自然的事,不需要特别操心。但是一旦出错,也更加难以察觉——因为我们几乎不可能跟踪垃圾收集器的动作。所谓一饮一啄,天下没有免费的午餐。
阅读(641 次)