公告:本站提供编程开发方面的技术交流与分享,打造最佳教程网,希望能为您排忧解难!

C语言教程以C的精神

以C的精神

更新时间:2013-03-16 22:51:55 |

是否在某种意义上C, C++, 和Java可以共用同一精神来描述?从最表面的层次上,它们看起来是相似的。举个例子,下面的代码片断,如果放入一个适合的上下文中,在所有这3种语言中它们都将用Euclid算法计算最大公约数

int gcd(int m, int n)
{
    while( m > 0 ) {
        if( n > m ) {
            int t = m; m = n; n = t;
        }
        m -= n;
    }
    return n;
}

但是当我们谈共享同一精神时我们就要谈它们的一些本质上的共同点,超出纯粹的语法。

在研究那些本质时,让我们先来看看第一个ANSI C标准的理论基础:保持这个委员会的主要目标是保护C 的传统精神。C的精神有很多方面,但是其本质是一个委员会关于C语言基于的根本原则的观点。

关于C的精神的一些方面可以被概述成短句如下:
相信程序员。
不要阻止程序员做那些需要去做的。
保持语言短小精干。
一种方法做一个操作。
使得它运行的够快,尽管它并不能保证将是可移植的。

最后一句箴言需要一点解释。潜在的高效率的代码生成是C最为重要的力量。为帮助确保没有代码爆炸发生在它们看起来是一个非常简单的操作上,一些操作被解释成目标机器的硬件如何做它而不是以一个常规的抽象规则。

在很多方面B程序设计语言是这个精神的真切的化身,和其以后的发展可以被视为在上面这5个方面中的交替。更多的是后者。

“20年的学校教育并且他们让你日复一日地工作” (Bob Dylan)
首先说说我自己和C的一段恋爱史。回到1983年,作为一个刚刚获得学位心理学博士,我加入了我的导师Peter Ossorio的人工智能的研究。我被安排做语言分析,但是当到那里后我很快就明白了最需要做的工作是编程。公司有一个合同要交付一个运行于当时最新的VAX计算机的文本分析和检索系统,并且只有一个用FORTRAN编写的原型系统。并且不是那时能被VMS编译器接受的这个相当标准的FORTRAN,而是一个更老的,并大量扩展的方言不再被支持。我们的常驻数学家,正当我在对原型代码进行纠错时,他进来了,坚决地说这是用C重编这个软件的时候了。“为什么用C?”我问。“因为”,他仍然简洁地说,并给我一个Kernigan & Ritchie的拷贝。

我们当时太穷了而不能再租借一台VAX,因此我们不得不共享一台PC克隆机。它有128K的内存和两个320K的硬盘,一个装着Lattice C编译器和库,另一个装着所有我们的代码,它无论如何得运行在64K内存下,因为MSDOS要用掉另外64K。现在回想起来当时的8088的CPU时钟频率是令人捧腹的4.77MHz。现在回头看看就很清楚了除了一个小而简单的语言没有什么可以在那样的环境下工作。因此,用手里的K&R和硬盘中的C编译器,我们开始了工作。当我不费力气地实现我的矩阵代数代码(真正的程序员可以用任何语言写出FORTRAN)并且我们数学家将他的模式匹配理论变成递归集的密集指针算法时,C的重要能力很快变得明显。C的高效这时已变得非常明显,因为我们所有的代码与存储器的协调和运行速度之快超过了我们所有可能的期望。当我们对我们已有了正确的代码有信心时,我们计划借用附近大学的VAX用两周的时间来移植到VMS。它只化了两天时间,并只有两个bug,原因是我认为int只能有16位。我在那个大学计算机中心第二天快结束的时候,我想起来当时有个学生打量着我的肩膀说:“这是什么?”“C 代码,”我说。“什么是C?”他问。“可移植的结构汇编语言,”我说。“这正是这个世界所需要的,”他说,而我当时也不能肯定他是认真的还是讽刺。但是我已爱上了C,并且义无反顾。

需要是母(佚名)
那么称作C的这个不平凡的工具来自何处?在1969年Ken Thompson与Dennis Ritchie, Doug McIlroy和其他人开始为建立于他的PDP-7的Unix操作系统写一个FORTRAN编译器,正如Ritchie 所述:

正如我回想的,用Fortran 的念头持续了一个星期。取而代之的是他所制造的全新语言B的一个定义和一个编译器。B受到BCPL语言的很多影响,其他的影响是Thompson对斯巴达式语法的体验,并且编译器很适合非常小的空间运行。

像BCPL一样,B 是一个无类型语言有着丰富的各种操作于机器字,它可以有整数,位模式,字符,数据地址,和函数地址。我们可以很容易地将最大公约数的例子改写为B代码:
gcd(m, n)
{
    while( m > 0 ) {
        if( n > m ) {
            auto t = m; m = n; n = t;
        }
        m = m - n;
    }
    return n;
}

B是如何很好地体现这个精神的呢?几乎完美的,以我的观点。“相信程序员”和“不要阻止程序员做那些需要做的事”是一个无类型语言的明显特征。一个8千字节的空间限制和Thompson的斯巴达体验共同造就了“保持语言小而简单”—auto说明符正是仅有的语法cruft。用那么少的语法人们甚至幸运地找到“一种方法做一个操作”,并且只通过操作本地机器的字,对于一个快速执行这里没有障碍。

那么既然B(语言)是正好是来自天堂的,还有什么可以发展的呢?

“但是对于有着善良和邪恶之果的树,你不应吃…”(起源 2:17)
PDP-7是一个字寻址机器,但是PDP-11是字节寻址的。作为在B中笨拙处理字符的结果,封装和未封装的字节出入于机器字,这变成一个执行的障碍。而且,PDP-11被许诺很快会有一个浮点单元,但是16位机器字将没有足够的能力拥有一个浮点值。为了获得更好的性能,Dennis Ritchie决定尝试禁果而在B中增加字符和浮点数据类型。这明显的是C的精神箴言2和3第一次妥协,为了最大的性能而用无类型编程的简单作了交换。

在初尝禁果以后,Ritchie增加了用户定义struct和union类型并引入了在表达式使用中一个数组的名称被转化为它第一个数的地址的这个规则。仍然提供声明类型的语法,并且从B到C发展进行的很好。

该规则中int是缺省类型并且实际情况是在PDP-11指针值将可以赋给一个int类型并使得最早的C编译器接受大部分B代码成为可能,并且Unix的大部分,最初是用汇编和B,也用C的无类型方式重写了。因此我们对“程序员不能为那些他们没有用的东西付出代价”这个规则有了第一印象,并且我们也看到了一个后向兼容的方针,所有这两个都将对C和C++的未来的发展产生巨大的影响。

树上的果实依旧是需要付出代价的。C是保持非常简单的语言,但是越复杂的类型系统将更容易出错,并且相信程序员来避免错误变得更难了。随着时间的推移工具像绷带一样被提供用来检查可能的类型错误,并且编译器对它们将接受的代码变得更加严格。因此箴言1也被妥协为更多相信工具中的编译器而更少相信程序员。

你在为是否有价值赌一把而感到疑惑吗? (Joni Mitchell)
如果说Dennis Ritchie是偷尝禁果,那么Bjarne Stroustrup继续之而成为一个真正的Johnny Appleseed。C++语言有几近完美类型安全,这是个好事,同时它的类型系统可以证明在任何语言中是最复杂的。除了我们所引用C的基类型和派生类型而外,还有继承,多继承,虚继承,纯和虚成员函数,运行时类型消息,函数模板,类型模板,类型演绎,和更多其他的。箴言3已被丢弃在路边,同时语言以简单为代价获得了表现的力量。

尽管面向对象是最初将C扩展到C++的动机,最强有力的扩展被证明是由模板提供的类的编程便利。考虑到类型安全容器所以模板被引入,因此人们可以只需要定义一个类如list<T>一次然后就可以在任何一种表(list)元素中使用它了。但是1994年Erwin Unruh带了一个看起来一点错也没有的小程序到Santa Cruz,它不能通过编译,但导致了编译器在它的诊断输出中生成了一个序列的素数数字。我回想起来仍然迷惑,又觉得好玩,然后便是惊悸。通过引入模板我们无意中加入了完全图灵机的元语言(meta-language)到C++。在我们应限制模板工具以避免那种元编程(meta-programming),但是取代我们赌了一把并且作为模板碾过语言和库的影响它让我们付出了无尽的痛苦的代价。

是否类型安全就意味着程序员不需要再被相信?几乎肯定不是的。所有在C中未定义的的行为将可能继续在C++中使用,随着新的方法用就会出错。以我的经验这几乎对一个有十足创造力的傻瓜用C++做这种损害可能是没有限制的。但是这也让一个擅长于库的设计者可以将几乎毫无限制的复杂程度隐藏在一个简单、安全和优雅的C++界面下。类的元编程(meta-programming)特别之处是它被证明是用简单界面建立最有效的复杂工具,因此在我看来这个赌博是值得的。

简单的Oak(橡树)
因为混沌状态的循环在这个王国的仍然没有停止,英雄来到了巨人1那里并用力制服了他,命令说出让混沌恢复秩序的秘密。巨人回答说:给我你的左眼我就告诉你。因为英雄太爱他的那些受到威胁的人民了,他没有任何犹豫。他挖出了他自己的左眼并将它交给巨人,巨人这时就说,让混沌恢复秩序的秘密就是:用两只眼睛看。(John Gardner)

在浏览器大战中就在战斗最激烈时,Java被制造出来了。因为商业需要一种便携式语言它编译的代码可以在不稳定的浏览器上安全运行,并且它的语法和语义要尽量为现有的程序员所熟悉。迄今为止模糊的Oak项目,最初目标是那些如有线电视机顶盒的设备,在再命名为Java后接着有着充实资源的Sun Microsystems的应用将Java带进了市场。当这些已发生的时候,Java作为一个Web 浏览器插件几乎没有任何影响力,但是它在为基于Web的商业建设服务器方系统的工程师中和寻找便宜、简单的面向对象编程教学工具的教育家中找到了欢迎它的观众。

Java的设计集中于箴言的第3条和第4条,为安全和简单而牺牲了处理能力和速度。不但Java是完全类型安全的,根本就没有未定义行为。语言和虚处理机共同加强了它的安全性,以使没有任何怀有恶意的代码可以损坏主机。有鉴于此,Java能充分满足大多数编程任务而提供一个有丰富表现力语言方面做了值得注意的工作。但例外的是需要完全控制硬件或在必须用所有最有效的可能来使用硬件资源时,包括Java虚拟机本身。这样的代码则仍然必须用一个较低层次上的语言比如C++、C或汇编来写。

是否绝妙的安全度意味着程序员不需要再被相信 毕竟,Java的自动内存管理和缺乏指针运算意味着误入歧途并摇摆的指针是不可能出现的。一句话,不,但这里有三个例子。

首先,根据情况,自动内存管理实际上可以让程序需要的内存变得更加难于控制。可能对于Java执行时最频发的bug报告是垃圾收集泄漏。在这些报告中几乎每个案例都是错的-是Java代码本身占有了不需要的对象。但如果修改了不需要对象占有将可能导致执行问题,和代价昂贵的对象获得一样需要再造对象而不是在内存中保持。因此各种缓冲器策略被用来防止对有价值的对象过早破坏,导致这种系统的类WeakReference, SoftReference, PhantomReference, ReferenceQueue, 和WeakHashMap。

第二,缺乏栈分配对象可能使管理资源变得更难除了管理内存外。举个例子,我的第一个Java程序很快就因为文件描述符而无效:
void scanFile(String name, Filter filter)
{
    File file = new File(name);
    filter.scan(file);
}
当然,这令我明显感到难堪,但是难堪也是常有的事。我很快学会为缺乏垃圾回收器而使用目的块。

第三,尽管未定义状态在Java中是可能的,死锁和活锁不体面地变得容易。与其说为并行执行提供了自由死锁语言机制,还不如说Java只提供了低层同步原语,把它交给程序员去管理争用。尽管一个简单的监控工具比如Per Brinch Hansen的Concurrent Pascal 中提供的已经得到改进,及自从Hansen对于程序并行的安全方法取得相当可观的进展以来已有30年之久。但是无论是因为何种原因Java还是忽视了这个工作。
前面两个例子讨论到在Java中管理对象的生命周期一定比在C++中容易。

同时是否任何Java线程比Unix进程更难使用。尽管有这些告诫,Java仍是一个成功的工具。以其承诺的安全、轻便的程序Java已经在令人惊讶的范围上获得了成功。

要关注什么才是你所渴望的(佚名)
Java,作为一门完全新的语言,已能够尽可能地更加清楚地脱离了C和C++。即使这样,它以处理能力和速度为代价而获得的更加简单是C++所不能做到的。是否我们也在希望C++能否变得更为简单一些?

我相信我们在从Grace的B倒下中所损失最多为无类型编程的简单。这个损失是必需的,而对于C这个损失是可以管理的,但是对于C++复杂性已成为一个令人畏缩的障碍。大多数复杂性是因为支持类编程,因为先进的从业人员已将最初的模板语法扩展到超出它的本意。自相矛盾的是模板语义学可能正是指向语法的完全简化。
Generic编程的力量来源于编译器用上下文推断类型。在一定程度上,这个能力可以排除对于类型声明的需要。比如,我们来写一个GCD的版本,对于它自变量的类型是不可知的,我们现在写成这样
template
T gcd(T m,T n)
{
    while( m > 0 ) {
    if( n > m )
    swap(m,n);
    m = m - n;
    }
    return n;
}

这看起来并不坏,但是在类型的数量增加时会导致速度更差,并且如果结果的类型依赖于超过一个自变量类型这就几乎不可能了。因此我宁愿看到模板语法给我们带来选择,并同时让编译器做这个工作:
gcd(m,n)
{
    while( m > 0 ) {
    if( n > m )
    swap(m,n);
    m = m - n;
    }
    return n;
}
非常清楚和简单,就像B。并且也很安全,因为编译器可以确保所有的推断类型是一致的。【编者注:这是模板如何在D语言中工作的本质。】
令人感兴趣的是我们继续提出更多“愿望清单(wish list)”,但这将是我下一篇文章的主题。

感谢
感谢Angelika Langer对于我这篇文章草稿的评论。

关于作者
Dr. Greg Colvin从1972开始快乐的编程。他是ANSI/ISO C++标准委员会的资深成员(并且是auto_ptr的主要设计者)同时是Oracle 公司Java产品组的技术部的负责成员。业余时间他在他的美国科罗拉多的家庭工作室里玩夸张的绿色电吉他。
翻译:小刀人

译者注:
这是我看到关于《C++ Source》(http://www.artima.com/cppsource/)的介绍后,见到此文而译之。但愿我的译文没有让Greg Colvin先生的文章失色,如果真的这样,请看原文:In the Spirit of C,同时最好帮我指出误译之处,谢谢。
1、巨人,巨怪。斯堪的那维亚民间传说中的超自然的生物,时而被描述成友好的或顽皮的侏儒,时而被描述成巨人,居住在山洞里、小山上或桥下。

最佳教程网

最大的技术交流平台 www.goodxyx.com© CopyRight 2011-2013, All Rights Reserved

浙ICP备11033019号