为何C语言程序坚若磐石?

2008年11月11号(Single Day~~~)

C语言,在今天来讲是一种特殊的编程语言。只有极少数人真的能够用C进行编程,并且咱们中很大一部分人都对C有本身的见解。缓冲区溢出,栈溢出,整型数据溢出,C有不少广为人知缺陷,而这些缺陷被人们随意传播,甚至那些不熟悉C的人们。我本身已经有10念没有接触C了,因为这样或那样的缘由。开始的额时候,编译器是很昂贵的(在免费的UNIX被发布以前)并且很慢,那时的环境是很糟的。并且,全部关于C的恐怖故事让我以为我这么一个小小的普通程序员怎么能够写出可靠的C程序。程序员

撇过一些我直接从别的地方复制粘贴过来的不少小的C模块不说,我本身写的第一个C程序是Converge VM。其中有两件事情让我惊呆了:-o 。第一,写C程序原来不是那么难。过后我才知道我年轻的时候浪费时间写汇编代码这件事在心理上给我了很大的支持,毕竟C是高级一点的汇编语言。一旦一我的理解了像指针(能够说是低级语言中最微妙的概念,由于真实世界中没有相对应的比喻)这样的概念。第二件事情是,Converge VM没有像我期待那样尽是bug。编程

实际上,忽略可能在任何编程语言上都存在的逻辑错误,到目前为止在Converge VM中引起实际问题的只有两个只针对C才会有的错误(主意,我确定还有不少潜伏的bug,可是我情形尚未碰上太多)。第一个错误是,一个list没有以\0(C中经典的错误),这个问题花了很长时间去调试。另外一个错误则神奇的多了,花了我好几个月时间。Converge 垃圾回收器能够谨慎地根据指针回收随意分配的内存空间。在全部的如今结构中,指针都指的是字和字对齐的边界。然而,已经分配的内存块在长度上经常不是字和字对齐的。 (In all modern architectures, pointers have to live on word-aligned boundaries.However, malloc'd chunks of memory are often not word-aligned in length.) 因此有时候垃圾回收器会在一个内存块位置为4的地方尝试读取4bytes,即便那个内存块是5bytes长。换句话来讲,垃圾回收器尝试读入一块数据的1bytes和内存中理论上没有权限的3bytes随机数据。罕见和神奇的是,这致使的错误几乎无法解释。可是不夸张的说,在多少编程语言中一我的能够递归地加上垃圾回收器?数组

我和Converge VM的经历不怎么不符合我以前的偏见。我已经慢慢认可C程序会随机出现segfault,丢数据,并且经常会像Vikings(维京海盗)去Lindisfarne同样。对比来看,用高级语言编写的程序会按照正常逻辑和能够预料的模式报错。渐渐地,这些问题在我平常使用的我能够信任的这些用C写的程序中,我都碰到了。我不记得上次这些程序发生大问题的时候了。这些不会崩溃,也会优雅的处理次要的错误。就算,我对这些软件(我使用OpenBSD9年了,因此没有比这些质量更好的软件了)极度挑剔,还有一些明显的缘由以致于为何它为何如此可靠:它被不少人使用,而这些人帮助咱们找出bug。软件已经被开发出来很长时间了,因此以前的版本都存在bug。而且,坦诚一点,只有至关能干的程序员首选会倾向于C。可是,仍然存在一个根本的问题:为何用C写的程序坚如磐石?网络

过了写论文这段黑暗的时期以后,我最近作了一点C编程。对于长时间没有着手写C程序的人,想妥妥地发封邮件都没谱了。这些年,我都是经过ssh在远程机器用sendmail发送邮件的。这解决了不少问题(好比黑名单),在不少网络中它也有问题(尤为是无线网络),一个过多的网络链接会被抛掉。检查邮件是否发送是很烦人的过程。因此仔细检查它的设计后,我打算写一个简单的工具集来稳妥地经过ssh发送邮件。最终的程序 - extsmail - 比我以前所期待的有更多的功能,可是最基础的思想就是经过外部的命令好比ssh简单的重试发送邮件,直到成功发送。我还想让这个工具集尽量占用资源少还实用,还可移植。这必然决定应该用C写extsmail。而后我决定尝试尽量地写这个程序,就当是实验吧。用传统的UNIX方式,只依赖可靠的UNIX分发版所提供的功能,并且容错能力强。在作的过程当中,作了两份关于用C写程序的新手观察资料。ssh

第一个观察不是太明显。由于用C写的程序有无数多种错误方式,我比平时更加细心。特别的,任何内存块的的操做都会引起很是危险的缓冲溢出类型错误。然而,在一个高级语言中,我可能会比较懒,心想“嗯,我索引数组的时候是否是应该给这个值减一?先跑一遍看看”。在C中,我会想“OK,坐下来想一想缘由”。讽刺的是,跑程序和发现问题所花的时间和坐下来思考的时间是不同的,除了坐下来思考更加耗费脑力。编程语言

第二个观察,是我以前从没有碰到过的,在C中没有异常处理。若是,好比说extsmail,要提升容错能力,就得本身不得不处理全部可能的错误。从一方面来讲,这是很是痛苦的,extsmail有很大一部分比例(大概40%)都在检查和去除错误,虽然UNIX系统方法已经很仔细的处理出错的状况了。换句话说,当在C中调用一个方法,好比stat的时候,文档会列出全部失败的状况。用户能够很容易的选择应该在程序中修复哪一个状况,哪些致命错误应该进一步处理(在extsmail中,内存不足就是致命错误)。这就是在思惟模式上基于语言的异常处理方式巨大的不一样点,经典的哲学是正常地写代码,仅在少数状况下写try ... catch语句块来处理特定错误(不多碰到的错误)。Java,用受控制的异常,以不一样的方式告诉用户“当调用这个方法的时候,你须要try catch特定的异常”。工具

我明白了一件事情,当但愿软件足够的强壮(鲁棒性)的时候,基于异常的软件设计是不合适的。而须要明确的是知道一个方法返回的或者抛出的错误或者异常,而后根据状况去处理。在如今的IDE中,能够根据写的方法自动显示会抛出哪些异常,最多也就只能作到这一点了。理论上,面向对象中的子类和多态意味着预编译的库不能根据写的代码肯定会不会抛异常(由于子类会重写方法,会抛出不一样的错误)。从实用方面来讲,我怀疑这么多方法都会抛出不少不一样的异常,这会让用户懵了。对比UNIX中的方法,就很是注意,它们会尽可能减小返回给用户的错误的数量,和一些内部错误,或者收集归类错误。进一步,我也怀疑那些依赖异常处理的不少库须要大幅度的进行重写来减小抛出的异常数量直到一个合理的数值。再进一步,方法的调用者应该决定什么错误应该次要的,能够恢复的,哪些会致使重要的问题,甚至致使程序结束;受控制的异常,被调用者强制处理的异常,就忘了这一点。.net

Henry Spencer 说,“那些不懂UNIX的人注定要可怜地从新发明轮子”。这就是为何这么多用C写的程序比咱们所提出的偏见更加坚固,UNIX文化,在计算机主流里,最古老和最明智的文化,已经发现不少把C的局限和缺陷变成优点的方法。就像我经历的,慢慢地我才明白了这点。综上,若是没有通过大量的思考,我不建议使用C。若是使用C,那最终的软件坚如磐石,但开发会花费大量人力。设计

Source:http://tratt.net/laurie/blog/entries/how_can_c_programs_be_so_reliable指针

相关文章
相关标签/搜索