一个典型的 off-by-one bug

今天美国人民过节,所以我今天上班基本上没啥事做。闲得发慌时无意中看到有人在邮件列表中报告 hexdump 有一个 bug,重现方法很简单,你只要通过 -s 把 skip 设置成该文件的大小就可以了,你可以用你手头上的 hexdump 试试,你会发现你可以看到全部文件的内容。这一下子激起了我的兴趣。

我一开始的想法是,hexdump 是个老命令了,虽然不能说像 ls 那些命令经过“千锤百炼”吧,但说“基本上不可能有bug”还是可以的。于是我抱着这个心态去翻源代码,虽然我在此之前从来没看过 hexdump 的源代码,但我还是信心满怀地找到了下面这么一句:skip -= sbuf.st_size; ,恩,看来是知道这种情况了,只是把它“折回”处理了,所以我回答说可能是一个 feature 而不是 bug。

但那个人后来的回复一下子惊醒了我,把 skip 再多加上一就是另一种结果了,什么都没有。所以这确实是一个 bug!当时我绕着源代码转了半天也没头绪,而且被里面那么多的全局变量和 static 变量给绕晕了,思考方向走错了。后来等我回到家做完晚饭一想,我靠,多一个或少一就没有 bug,这不就是 off-by-one 的症状嘛!肯定是哪里少算或者多算了一个临界值!抱着这个想法去找就简单了,很快就可以定位到问题所在,于是补丁自然而然地出来了

以前我们学C语言时不经常被提醒一定要注意 off-by-one 嘛,可能都没遇到过多么好的例子,现在这个就是现实中活生生的,非常典型的一个例子。作为反面教材放这里了。

现在再来回头看,问题其实很简单了,但当时分析这个 bug 时怎么就没想到这个原因值得深思:首先是心态不对,要面对现实,哪怕现实再残酷再不符合你的逻辑也要勇于否定自己,不要尝试找理由去否定现实,而是要接受现实去分析原因;其次,没有对“现象”进行深入思考,没有掌握到“现象”中表现出来的隐含规律,而这才是解决这个问题的关键!所以啊,不管在哪里多思考都是有好处的!

我很早以前就有这么个想法啊:作为程序员做 debug 工作,其实在很大程度上和侦探做破案工作是一样的。都要根据“凶案现场”(bug 症状)留下的蛛丝马迹顺藤摸瓜地去找“杀人凶手”(bug 所在);同样都是靠严密的逻辑去推理,唯一不同的是,侦探的逻辑和线索是事情发展的过程和顺序,而程序员的逻辑和线索是源代码;侦探在破案毫无头绪时会想更多的办法去搜集证据和证词等等,而程序员在毫无头绪时可以通过调试器等获取更多 debug 信息,总之都是获取更多的信息量,因为信息更多意味着分析出来的东西会更多。

这让我想起多年前上高中时上课都偷看的《福尔摩斯探案集》来了,里面福尔摩斯老师的那几句话真是饱含哲理,牛逼轰轰,金光闪闪啊:

当你把决不可能的因素排除后,不管剩下的是什么——-不管多么难以置信——那就是真相。

没有掌握全部证据以前,先作出假设,这是决大的错误,那样会使判断产生偏差。

猜想是很不好的习惯,它有害于作逻辑推理。

内牛满面地把这几句话收藏起来作为 debug 时的座右铭…… T_T