1.3 自动软件发布
我决定扛着Mike的大旗继续往下走。我采用uucp作为传输工具,通过编写一个名叫ASD(Automatic Software Distribution,自动软件发布)的软件包来为程序员提供一个安全的方法,使他们能够把自己的作品移植到其他机器上,我预料这些机器的数量会很快变得非常巨大。我决定采用两种方式来增强uucp:更新完成后通知发送者,允许同时在不同的位置安装一组文件。
这些功能理论上都不是很困难,但是由于可靠性和通用性这两个需求相互冲突,所以实现起来特别困难。我想让那些与系统管理无关的人用ASD。为了这个目的,我应该恰当地满足他们的需求,而且没有任何琐碎的限制。因此,我不想对文件名的长度、文件大小、一次运行所能传递的文件数目等问题作任何限制。而且一旦ASD里出现了bug,导致错误的软件版本被发布,那就是ASD的末日,我决不会再有第二次机会。
1.3.1 可靠性与通用性
C没有内建的可变长数组:编译时修改数组大小的唯一方法就是动态分配内存。因此,我想避免任何限制,就不得不导致大量的动态内存分配和由此带来的复杂性,复杂性又让我担心可靠性。例如,下面给出ASD中的一个典型的代码段:
/* 读取八进制文件 */
param = getfield(tf);
mode = cvlong(param, strlen(param), 8);
/* 读入用户号 */
uid = numuid(getfield(tf));
/* 读入小组号 */
gid = numgid(getfield(tf));
/* 读入文件名(路径) */
path = transname(getfield(tf));
/* 直到行尾 */
geteol(tf);
这段代码读入文件中用tf标识的一行的连续字段。为了实现这一点,它反复调用了几次getfield,把结果传递到不同的会话程序中。
代码看上去简单直观,但是外表具有欺骗性:这个例子忽略了一个重要的细节。想知道吗?那就想想getfield的返回类型是什么。由于getfield的值表示的是输入行的一部分,所以显然应该返回一个字符串。但是C没有字符串;最接近的做法是使用字符指针。指针必须指到某个地方;应该什么时候用什么方法回收内存?
C里有一些解决这类问题的方法,但是都比较困难。一种办法就是让getfield每次都返回一个指针,这个指针指向调用它的新分配的内存,调用者负责释放内存。由于我们的程序先后4次调用了getfield,所以也需要先后4次在适当场合调用free。我可不愿意使用这种解决方法,写这么多的调用真是很讨厌,我肯定会漏掉一两个。
所以,我再一次想,假如我能承受漏写一两个调用的后果,也就能承受漏写所有调用的后果。所以另一种解决方法应该完全无需回收内存,每次调用时,让getfield分配内存,然后永远不释放。我也不能接受这种方法,因为它会导致内存的过量消耗,而实际上,通过仔细地设计完全可以避免内存不足的问题。
我选择的方法是让getfield所返回内存块的有效期保持到下次调用getfield为止。这样,总体来说,我不用老是记着要回收getfield传回的内存。作为代价,我必须记住,如果打算把getfield传回的结果保留下来,那么每次调用后就必须将结果复制一份(并且记住要回收用于存放复制值的那块内存)。当然,对于上述的程序片断来说,付出这个代价是值得的,事实上,对于整个ASD系统来说,也是合适的。但是跟完全无需回收内存的情况相比,使用这种策略显然还是使得编写程序的难度增大。结果,我为了使程序没有这种局限性所付出的努力,大部分都花在进行簿记工作的程序上,而不是解决实际问题的程序上。而且由于在簿记工作方面进行了大量的手工编码,我经常担心这方面的错误会使ASD不够可靠。