剧本杀发行 https://www.td010.com 博雯发自凹非寺量子位|公众号QbitAI 一句最简单的HelloWorld,居然也会出Bug? 倒不是这句代码还能写错,而是运行时找到了许多操作系统对异常处理的漏洞。 在向/dev/full输出结果,也就是设备空间不足、任何写入都应失败的情况下,C语言依然返回了0,成功退出: $gcchello.c-ohello$./hello>/dev/full$echo$?0 Bug的最初发现者表示:这可不是一个小错误,本质上是“打印到标准输出”的任务。 发生了错误但不抛出异常,意味着即使出现数据丢失,进程依然会继续运行。 于是他一不做二不休,又测试了C++、Python、Java等热门语言,发了篇博客,很快就在论坛盖起了高楼,讨论度直接爆了: 而评论区网友一通Debug,综合整理下来,踩中这一Bug的语言,竟足足有16种之多! HelloWorld的DeBug过程 最初的发现者是一名名叫sunfishcode的技术博主,他在博客里展示了C和Python两种语言的详细的deBug过程。 主要使用的是Linux系统下的一个经典的设备文件,/dev/full。 /dev/full总是在写入时返回设备无剩余空间(错误码为ENOSPC),常常用于测试程序能否正确处理I/O错误。 如果程序正常,那么就会返回错误报告: $echo"HelloWorld!">/dev/fullbash:echo:writeerror:Nospaceleftondevice$echo$?1 而正如我们开头所示的代码,在用C语言进行输出时,hello程序却报告成功,返回了0。 用strace命令跟踪这一进程产生的系统调用可以发现,程序确实出现了故障: $strace-etrace=write./hello>/dev/fullwrite(1,"HelloWorld!\n",13)=-1ENOSPC(Nospaceleftondevice)+++exitedwith0+++ 而以“错误不该被悄悄传递”为口号的Python也着了道。 程序向stderr打印了一条消息,丢失了信息,但最后也返回了0: $python2hello.py>/dev/fullclosefailedinfileobjectdestructor:sys.excepthookismissinglostsys.stderr$echo$?0 这个Bug严重吗?现实世界任何一个程序都不会拿HelloWorld当作关键性安全问题,但“打印到标准输出”却是现实中确实会有的程序任务。 而这也正是HelloWorld这个最简单的程序的本质。 博主sunfishcode这样说: 标准输出可能意味着一个具体文件,那么如果这个文件刚好耗尽了空间,程序又因为Bug没有检测到这一错误呢?父进程不会知道子进程失败了,只会继续运行。但期望生成的输出实际上已经丢失了数据。 当然,博主在最后也给出了没有踩雷的语言列表: 网友热议:这到底算不算Bug? 目前,博主已经针对这一Bug给出了一些解决方案,比如在C语言环境中可以采用这样的方法: #include#includeintmain(void){printf("Hello,World!\n");if(fflush(stdout)!=0||ferror(stdout)!=0){returnEXIT_FAILURE;}returnEXIT_SUCCESS;} 而评论区也贡献了Java环境中的解决方案,即添加一个方法来获得底层的、未包装的OutputStream: System.out.println("HelloWorld!");if(System.out.checkError())thrownewIOException(); 下方还有人补充到,Java已经引入的RuntimeIOException就可以用于I/O异常出现意外的情况: 因此我们可以引入一个新的类,比如ErrorCheckingPrintStream,并将“ErrorCheckingPrintStreamwithErrorChecks()”方法添加到PrintStream中。 而除此之外,评论区热议的一个话题就是: 这位博主所公布的问题到底算不算是一个Bug? 反对者直言作者是在标题党,还以为是发现了什么C语言标准库里的Bug,但实际上只是处理所有可能的系统调用的失败情况: HelloWorld只是简单地将API调用到文本界面,对一个简单的接口进行调用,我在那里没有发现过任何Bug。 有赞同的评论在下方做了进一步的补充,他认为C语言的编写方式里本来就写明:程序不关心任何形式的错误条件。 包括printf的返回值被忽略、输出不被刷新、刷新的返回不被检查、不关心errno值等等。 所以,用户本就不应该期望给定的系统调用返回额外的errno值,而是应该用特殊方法处理特殊情况。 甚至有人表示:程序的失败不是由程序控制结构定义,而是由需求定义,HelloWorld程序的需求难道包括主机系统的所有错误边界吗? 也有人更赞同作者,认为HelloWorld不只是接口调用,实际是在要求操作系统在某处写入数据,而这正是简单的程序与现实世界相关联的地方: 这是一个严重的问题,而似乎在大多数时候,这种看似简单的功能中存在的大量复杂性都被忽略了。 还有另辟蹊径,从教育的角度来看的评论: 毕竟C语言时很多程序员的入门语言,hello.c又是其中的第一个程序,要让初学者更好地理解控制结构,块,返回值,缓冲流的,printf格式化语言等概念,所以还是把它当成一个Bug吧。 那么你又怎么看? 参考链接:[1]https://blog.sunfishcode.online/Bugs-in-hello-world/[2]https://news.ycombinator.com/item?id=30611367[3]https://github.com/sunfishcode/hello-world-vs-io-errors ![]() |
![]() 鲜花 |
![]() 握手 |
![]() 雷人 |
![]() 路过 |
![]() 鸡蛋 |
分享
邀请