C语言中史上最愚蠢的Bug
本文来自“The most stupid C bug ever”,很有意思,分享给大家。我相信这样的bug,就算你是高手你也会犯的。你来看看作者犯的这个Bug吧。。
首先,作者想用一段程序来创建一个文件,如果有文件名的话,就创建真正的文件,如果没有的话,就调用?tmpfile()?创建临时文件。他这段程序就是HTTP下载的C程序。code==200就是HTTP的返回码。
else if (code == 200) { // Downloading whole file /* Write new file (plus allow reading once we finish) */ g = fname ? fopen(fname, "w+") : tmpfile(); }
但是这个程序,只能在Unix/Linux下工作,因为 Microsoft 的?tmpfile()的实现?居然选择了 C:\ 作为临时文件的存放目录,这对于那些没有管理员权限的人来说就出大问题了,在Windows 7下,就算你有管理员权限也会有问题。所以,上面的程序在Windows平台下需要用不同的方式来处理,不能直接使用Windows的tmpfile()函数。
于是作者就先把这个问题记下来,在注释中写下了FIXME:
else if (code == 200) { // Downloading whole file /* Write new file (plus allow reading once we finish) */ // FIXME Win32 native version fails here because // Microsoft's version of tmpfile() creates the file in C:\ g = fname ? fopen(fname, "w+") : tmpfile(); }
然后,作者觉得需要写一个跨平台的编译:
FILE * tmpfile ( void ) { #ifndef _WIN32 return tmpfile(); #else //code for Windows; #endif }
然后,作者觉得这样实现很不好,会发现名字冲突,因为这样一来这个函数太难看了。于是他重构了一下他的代码——写一个自己实现的tmpfile() – w32_tmpfile,然后,在Windows 下用宏定义来重命名这个函数为tmpfile()。(陈皓注:这种用法是比较标准的跨平台代码的写法)
#ifdef _WIN32 #define tmpfile w32_tmpfile #endif FILE * w32_tmpfile ( void ) { //code for Windows; }
搞定!编译程序,运行。靠!居然没有调用到我的w32_tmpfile(),什么问题?调试,单步跟踪,果然没有调用到!难道是问号表达式有问题?改成if – else 语句,好了!
if(NULL != fname) { g = fopen(fname, "w+"); } else { g = tmpfile(); }
问号表达式不应该有问题吧,难道我们的宏对问号表达式不起作用,这难道是编译器的预编译的一个bug?作者怀疑到。
现在我们把所有的代码连在一起看,并比较一下:
能正常工作的代码
#ifdef _WIN32 # define tmpfile w32_tmpfile #endif FILE * w32_tmpfile ( void ) { code for Windows; } else if (code == 200) { // Downloading whole file /* Write new file (plus allow reading once we finish) */ // FIXME Win32 native version fails here because // Microsoft's version of tmpfile() creates the file in C:\ //g = fname ? fopen(fname, "w+") : tmpfile(); if(NULL != fname) { g = fopen(fname, "w+"); } else { g = tmpfile(); } }
不能正常工作的代码
#ifdef _WIN32 # define tmpfile w32_tmpfile #endif FILE * w32_tmpfile ( void ) { code for Windows; } else if (code == 200) { // Downloading whole file /* Write new file (plus allow reading once we finish) */ // FIXME Win32 native version fails here because // Microsoft's version of tmpfile() creates the file in C:\ g = fname ? fopen(fname, "w+") : tmpfile(); }
也许你在一开始就看到了这个bug,但是作者没有。所有的问题都出在注释上:
/* Write new file (plus allow reading once we finish) */ // FIXME Win32 native version fails here because // Microsoft's version of tmpfile() creates the file in C:\
你看到了最后那个C:\吗?在C中,“\” 代表此行没有结束,于是,后面的代码也成了注释。这就是这个bug的真正原因!
而之所以改成if-else能工作的原因是因为作者注释了老的问号表达式的代码,所以,那段能工作的代码成了:
/* Write new file (plus allow reading once we finish) */ // FIXME Win32 native version fails here because Microsoft's version of tmpfile() creates the file in C: //g = fname ? fopen(fname, "w+") : tmpfile(); if(NULL != fname) { g = fopen(fname, "w+"); } else { g = tmpfile(); }
我相信,当作者找到这个问题的原因后,一定会骂一句“妈的”!我也相信,这个bug花费了作者很多时间!
最后,我也share一个我以前犯的一个错。
我有一个小函数,需要传入一个int* pInt的类型,然后我需要在我的代码里 把这个int* pInt作除数。于是我的代码成了下面的这个样子:
float result = num/*pInt;
…./* some comments */
-x<10 ? f(result):f(-result);
因为我在我当时用vi编写代码,所以没有语法高亮,而我的程序都编译通过了,但是却出现了很奇怪的事。我也不知道,用gdb调式的时候,发现有些语句直接就过了。这个问题让我花了很多时间,最后发现问题原来是没有空格导致的,TNND,下面我用代码高亮的插件来显示上面的代码,
float result = num/*pInt; .... /* some comments */ -x<10 ? f(result):f(-result);
Holly Shit! 我的代码成了:
float result = num-x<10 ? f(result):f(-result);
妈的!我的这个错误在愚蠢程度上和上面那个作者出的错误有一拼。
(全文完)
(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)
《C语言中史上最愚蠢的Bug》的相关评论
所以说,人都会犯错,重中之重永远是测试而不是严谨的风格(虽然也很重要)
Holly Shit! 应该是 Holy Shit!
这种bug一点也不愚蠢啊
我很喜欢这篇文章,让我想起了我写的一个小工具遇到的问题,以及那篇”21天教你学会C++”。抛开良好的编码规约不谈。除了钻研算法、专业知识、业务知识等等,程序员更应该认真的去好好学习你用到的编程语言,“工欲善其事,必先利其器”,语言作为工具,全面的了解你用到的语言是非常重要的,尤其是C和C++这种有很多历史遗留问题的语言。这些基本的东西了解之后,今后这些代码摆在你面前,你可以轻松看出来。
自己喜欢关注细节,C++的二联符和三联符早在初学时就在C++之父的TC++PL一书的附录上看到了。C/C++支持的续行符更是研究的比较多,许多这样的问题依赖于编辑器的语法高亮显然是不靠谱的,就我所知,VS2008的编辑器(VS2005没用过)开始才开始支持“单行注释接续行符”的语法高亮,而许多其他的编辑器或者集成开发工具更是从来就不支持。并且”//”或者”/*”的两个字符中间都是可以用”\”续行的,并且我没有发现任何一个编辑器可以对这种情况语法高亮,三联符和二联符更是如此。
邪恶的Windows目录分隔符啊 LOL
以前有个人甚至对我做出一个小工具说,对于C/C++语言的解析做到100%正确有什么意义呢,软件公司内部都有强制的编码规约,谁会写出那样的代码呢。我想说,严谨是有意义的,无论是统计分析还是语法高亮,对于C/C++语言做到100%正确是非常有意义的。如果所有支持C/C++语法高亮的编辑器都能做到100%正确,那么像这种错误,对于那些没有好好学习透C/C++语言的程序员,或者学习透了也会马虎的程序员 就不会发生了。
那个 bug 真正的愚蠢在于:任何一个靠谱的,支持语法着色的编辑器都应当能够显示出后面一段被当做注释。因此那个 bug 在靠谱的编辑器中不可能存在。
至于博主的问题,哪怕是用 vim 替代 vi ,都不会出现。
当然,在没有语法着色的史前时代,人们 也可以解决博主遇到的这个问题,方法是:用编码规范规定所有二元操作符前后必须加空格。因此除法的除号前后肯定有空格。
@bob
据我所知 emacs / vim 都可以实现对这种情况的语法着色,并且据我所知很少见到靠谱的编辑器不支持这种情况下的着色。
”//”或者”/*”的两个字符中间都是可以用”\”续行的,这个有编辑器可以支持。我又用了一下emacs,我这里怎么不成呢。
/\
/ comment
/\
* comment */
无论是vim还是emacs,能给我截个图吗,我对这个很感兴趣。
@poet
/??/
/ comment
还有这种情况,有编辑器支持语法高亮???
这个在VC编辑环境下,不会是bug,因为,VC代码加亮会提示这个出来
@poet
你用的是什么版本啊?我用的是:
GNU Emacs 23.1.1
VIM 7.2
都不支持。我用过的编辑器大大小小有三四十个左右吧,对 C、C++的续行符也是研究了一阵儿,真的是没见过一个能对我说的那种情况语法高亮的。除了这个之外,C++的关键字也是可以被续行符隔开的,比如:
in\
t a =0;
我也没见过有任何一个编辑器能语法高亮的。
能快点儿回复我不?我真的想赶紧知道,难道我的vim和emacs的设置不对?
读 k&r 的书,使用标准的代码格式确实不会犯这种错误啊。第一个错误就是只用/**/才有的优越感
@poet
如果你说的是普通的二联符和三联符的高亮,那么我也调查了一下:
#include
%:include
??=include
VS2008、VS2010的编辑器不支持,Emacs不支持,VIM仅支持二联符。
是有什么特殊设置,还是你看错我说的话了,咱能给个结论吗,你回复了一下就没下文了,弄得我这个感兴趣的人在这儿干着急,不太像话吧。
很有意思……我有阵子用Ruby的时候也遇到一个空格带来的bug —— http://qingbo.net/blog/post481-ruby-pitfall.html
Bob 大叔… 我替你顶一个, 我的 vim 也不能提示, vim 7.3
gvim 就能高亮出问题所在呀,
如图
http://img.ly/images/1893978/full
为啥乃们要用 vi 或记事本呢
最担心的是自己写错了,可是自己不知道,一直以为没错
哈哈 实在是太欢乐了!!
朋友可以试试在vimrc里面加上syntax on@walfud
如果有语法着色,很容易看出来了。为什么不用个好点的编辑器
很有意思,哈哈!
对后一个例子,打括号总是个好习惯。对于前一个例子,说明C编译器是先处理的‘\’,再处理的注释。这有点奇怪。
哈哈,不错
@zengxg
“\”属于预编译阶段,我觉得先处理这个很正常。
还没写过跨平台的东东
续行符的问题,gcc会有警告 warning: multi-line comment ,,,木有人看警告
晕 这都行。
以前还没注意过
有一次我发现有一个循环语句无论如何却只执行一次,找了老半天,后来发现,在for语句的括号后面有一个分号 ;
各种囧。。。。
看来写程序的时候适当加一些空格还是有好处,呵呵
我现在写程序太依赖 VAX 了,,不过它也非常的强大,基本程序写完是不可能出现语法的问题,只有逻辑的问题。。
@imfeng
这个在用-Wall选项时才会看到, 许多自己写Makefile的人是不把这个选项写进去的.
第二个bug好像是《C缺陷和陷阱》里面的错误例子
以前也遇到过这种问题
后来崩溃的直敲脑袋 囧
哈哈,真TM有意思!
作者自己提到的错误好像在《C专家编程》中有提到。
/\
/ comment
/\
* comment */
这部分在emacs会被认为是错的,会用红下划线标识,用的插件是emacs-23.3自带的cedet.
有一次我在一个宏后面加多了一个;号,后来编译出一些莫名其妙的错误。
g = fname ? fopen(fname, “w+”) : tmpfile();
这句在我的emacs中标识为不是注释。。。。。
但// Microsoft’s version of tmpfile() creates the file in C:\ 的\符号会被后移几个空格(对齐).
幸好VS上面会染色,之前还真没碰到这种问题~
fname是一个字符串指针,用fname?来判断而不是用 NULL != fname来进行分支判断是有风险的,因为C语言规范上并没有严格保证NULL就一定是0指针。
你的bug就不要秀了,谁让你不爱写空格的,活该了
最讨厌看那种没空格、没空行、所有代码都挤在一起的代码
c语言就没有NULL
C语言存在很多缺陷,这也是没办法的事。最可恨的是,这些缺陷却没有在C++中得到更正
@vardyh
ISO C有。
实是编辑工具没提示好。而且即使作者不是初学者,他遇到这个问题的重要原因之一就是他之前没有遇到这种问题。对于‘\ ’可能在宏定义的时候经常写。另一个重要的原因是他估计是在terminal或者记事本下编写的代码。这问题在一个支持C语法查检的文本编辑器中都被反应出来。