第 1 章 C语言概述
如果有人说“我想要一种语言,只需对它说我要干什么就行”,给他一支棒棒糖好了。1
1每章章首的警句均选自Alan J. Perlis的文章“Epigrams on Programming”。该文发表在ACM SIGPLAN Notices(美国计算机协会编程特别兴趣小组会刊)1982年9月号第7~13页。
什么是C语言?它是20世纪70年代初期在贝尔实验室开发出来的一种广为使用的编程语言。这一简单回答显然没能传达出C语言的特别之处。不过别急,在深入学习这门语言之前,让我们先来回顾一下C语言的起源、设计目标和这么多年来的发展(1.1节)。我们还将讨论C语言的优缺点,以及如何高效地使用C语言(1.2节)。
1.1 C语言的历史
本节对C语言的历史做一个简单的回顾,从它的起源到它成为一种标准化语言,再到它对近代编程语言的影响。
1.1.1 起源
C语言是贝尔实验室的Ken Thompson、Dennis Ritchie等人开发的UNIX操作系统的“副产品”。Thompson独自编写出UNIX操作系统的最初版本,这套系统运行在DEC PDP-7计算机上。这款早期的小型计算机仅有8KB内存(毕竟那是在1969年)。
与同时代的其他操作系统一样,UNIX系统最初也是用汇编语言编写的。用汇编语言编写的程序往往难以调试和改善,UNIX系统也不例外。Thompson意识到需要用一种更加高级的编程语言来完成UNIX系统未来的开发,于是他设计了一种小型的B语言。Thompson的B语言是在BCPL语言(20世纪60年代中期产生的一种系统编程语言)的基础上开发的,而BCPL语言又可以追溯到最早(且影响最深远)的语言之一 ——Algol 60语言。
不久,Ritchie也加入到UNIX项目中,并且开始着手用B语言编写程序。1970年,贝尔实验室为UNIX项目争取到一台PDP-11计算机。当B语言经过改善并能够在PDP-11计算机上成功运行后,Thompson用B语言重新编写了部分UNIX代码。到了1971年,B语言已经明显不适合PDP-11计算机了,于是Ritchie着手开发B语言的升级版。最初,他将新开发的语言命名为NB语言(意为“New B”),但是后来新语言越来越偏离B语言,于是他将其改名为C语言。到了1973年,C语言已经足够稳定,可以用来重新编写UNIX系统了。改用C语言编写程序有一个超级重大的好处:可移植性。只要为贝尔实验室的其他计算机编写C语言编译器,他们的团队就能让UNIX系统也运行在那些机器上。
1.1.2 标准化
C语言在20世纪70年代(特别是1977年到1979年之间)持续发展。这一时期出现了第一本有关C语言的书。Brian Kernighan和Dennis Ritchie合作编写的《C程序设计语言》一书于1978年出版,并迅速成为C程序员必读的“圣经”。由于当时没有C语言的正式标准,所以这本书就成了实际上的标准,编程爱好者把它称为K&R或者“白皮书”。
在20世纪70年代,C程序员相对较少,而且他们中的大多数人是UNIX系统的用户。不过,到了20世纪80年代,C语言已不再局限于UNIX领域。运行在不同操作系统下的多种类型的计算机都开始使用C语言编译器,特别是迅速壮大的IBM PC平台也开始使用C语言。
随着C语言的迅速普及,一系列问题接踵而至。编写新的C语言编译器的程序员都用K&R作为参考。但遗憾的是,K&R对一些语言特性的描述超级模糊,以至于不同的编译器常常会对这些特性做出不同的处理。而且,K&R也没有对属于C语言的特性和属于UNIX系统的特性进行明确的区分。更糟糕的是,K&R出版后来C语言仍在不断变化,增加了新特性并且去除了一些旧的特性。很快,C语言需要一个全面、准确的最新描述开始成为共识。如果没有这样一种标准,就会出现各种“方言”,这势必威胁到C语言的主要优势——程序的可移植性。
1983年,在美国国家标准学会(ANSI)的推动下,美国开始制订本国的C语言标准。经过多次修订,C语言标准于1988年完成并在1989年12月正式通过,成为ANSI标准X3.159-1989。1990年,国际标准化组织(ISO)通过了此项标准,将其作为ISO/IEC 9899:1990国际标准
。我们把这一C语言版本称为C89或C90,以区别于原始的C语言版本(经典C)。附录D总结了C89和经典C之间的主要差异。
1995年,C语言发生了一些改变(相关描述参见Amendment 1文档)。1999年通过的ISO/IEC 9899:1999新标准中包含了一些更重大的改变,这一标准所描述的语言一般称为C99。由于存在两种标准,以前用于描述C89的ANSI C、ANSI/ISO C和ISO C等术语目前就有了二义性。
C语言的最近两次改变分别发生在2011年和2018年。国际标准化组织在2011年通过的C语言标准是ISO/IEC 9899:2011,这一标准所描述的C语言一般称为C11;在2018年通过的C语言标准是ISO/IEC 9899:2018,这一标准所描述的C语言一般称为C18。
从C99到C11再到C18的变化,没有从C89到C99那么显著。尤其是从C11到C18的变化,仅限于技术修正和澄清,总体上没有显著的改变,也没有引入新的语言特性。
在本书第1版发行的时候,C99还没有得到普遍使用,并且我们需要维护数百万(甚至数十亿)行的旧版本C代码,因此本书中我将用一个特殊图标
来标记对C99新增特性的讨论。不能识别这些新增特性的编译器就不是“C99兼容的”。附录C列出了C99和C89的主要区别。由于从C11到C18的变化不大,所以没有将它们分开单独讨论,并且用一个特殊的图标
来标记从C11开始引入的新特性。附录B列出了C1X和C99的主要区别。
1.1.3 基于C的语言
C语言对现代编程语言有着巨大的影响,许多现代编程语言都借鉴了大量C语言的特性。在众多基于C的语言中,以下几种超级具有代表性。
- C++:包括了所有C特性,但增加了类和其他特性以支持面向对象编程。
- Java:基于C++,因此也继承了C的许多特性。
- C#:由C++和Java发展起来的一种较新的语言。
- Perl:最初是一种超级简单的脚本语言,在发展过程中采用了C的许多特性。
思考到这些新语言的普及程度,人们自然会问:“C语言还值得学习吗?”我想答案是肯定的,缘由如下:第一,学习C有助于更好地理解C++、Java、C#、Perl以及其他基于C的语言的特性,而一开始就学习其他语言的程序员往往不能很好地掌握继承自C语言的基本特性;第二,目前仍有许多C程序,我们需要读懂并维护这些代码;第三,C语言依旧广泛用于新软件开发,特别是在内存或处理能力受限的情况下以及需要使用C语言简单特性的地方。
如果读者还没有学习上述任何一种基于C的语言,那么本书是一本超级好的预备教材。本书强调了数据抽象、信息隐藏和其他在面向对象编程中超级重大的原理。C++语言包含了C语言的全部特性,因此读者今后在使用C++语言时可以用到从本书中学到的所有知识。在其他基于C的语言中也能发现许多C语言的特性。
1.2 C语言的优缺点
与其他任何编程语言一样,C语言也有自己的优缺点。这些优缺点都源于该语言的最初用途(编写操作系统和其他系统软件)和它自身的基础理论体系。
- C语言是一种底层语言。为了适应系统编程的需要,C语言提供了对机器级概念(例如,字节和地址)的访问,而这些是其他编程语言尝试隐藏的内容。此外,C语言还提供了与计算机内置指令紧密协调的操作,使得程序可以快速执行。应用程序的输入/输出、存储管理以及其他众多服务都依赖于操作系统,因此操作系统必定不能运行得太慢。
- C语言是一种小型语言。与其他许多编程语言相比,C语言提供了一套更有限的特性集合。(在K&R第2版的参考手册中仅用49页就描述了整个C语言。)为了保持较少量的特性,C语言在很大程度上依赖一个标准函数的“库”(“函数”类似于其他编程语言中描述的“过程”“子例程”或“方法”)。
- C语言是一种包容性语言。C语言假设用户知道自己在做什么,因此它提供了比其他许多语言更高的自由度。此外,C语言不像其他语言那样强制进行详细的错误检查。
1.2.1 C语言的优点
C语言的众多优点有助于解释为什么这种语言如此流行。
- 高效。高效性是C语言与生俱来的优点之一。发明C语言就是为了编写那些以往由汇编语言编写的应用程序,所以对C语言来说,能够在有限的内存空间里快速运行就显得至关重大了。
- 可移植。虽然程序的可移植性并不是C语言的主要目标,但它还是成了C语言的优点之一。当程序必须在多种机型(从个人计算机到超级计算机)上运行时,常常会用C语言来编写。C程序具有可移植性的一个缘由是该语言没有分裂成不兼容的多种分支(这要归功于C语言早期与UNIX系统的结合以及后来的ANSI/ISO标准)。另一个缘由是C语言编译器规模小且容易编写,这使得它们得以广泛应用。最后,C语言自身的特性也支持可移植性(尽管它没有阻止程序员编写不可移植的程序)。
- 功能强劲。C语言拥有一个庞大的数据类型和运算符集合,这个集合使得C语言具有强劲的表达能力,往往寥寥几行代码就可以实现许多功能。
- 灵活。虽然C语言最初设计是为了系统编程,但是没有固有的约束将它限制在此范围内。C语言目前可以用于编写从嵌入式系统到商业数据处理的各种应用程序。此外,C语言在其特性使用上的限制超级少。在其他语言中认定为非法的操作在C语言中往往是允许的。例如,C语言允许一个字符与一个整数值相加(或者是与一个浮点数相加)。虽然灵活性可能会让某些错误溜掉,但是它使编程变得更加轻松。
- 标准库。C语言的一个突出优点就是它具有标准库,该标准库包含了数百个可以用于输入/输出、字符串处理、存储分配以及其他实用操作的函数。
- 与UNIX系统的集成。C语言在与UNIX系统(包括广为人知的Linux)结合方面特别强劲。实际上,一些UNIX工具甚至假定用户是了解C语言的。
1.2.2 C语言的缺点
C语言的缺点和它的许多优点是同源的,均来自C语言与机器的紧密结合。下面是众所周知的几个问题。
- C程序更容易隐藏错误。C语言的灵活性使得用它编程出错的概率较高。在用其他语言编程时可以发现的错误,C语言编译器却无法检查出来。从这方面来说,C语言与汇编语言极为类似,后者直到程序运行时才能检查到大多数错误。更糟的是,C语言还包含大量不易觉察的隐患。在后续的章节中我们将看到,一个额外的分号可能导致无限循环,遗漏一个&可能引发程序崩溃。
- C程序可能会难以理解。虽然大多数衡量标准认为C语言是一种小型语言,但是它有许多其他通用语言没有的特性(并且常常被误解)。这些特性可以用多种方式结合使用,其中的一些结合方式尽管编程者心知肚明,但是其他人恐怕难以理解。另一个问题就是C程序简洁的本质。C语言产生的时候正是人机交互最为单调乏味的时期,因此设计者特意使C语言简洁以便将输入和编辑程序的用时减到最少。C语言的灵活性也可能是一个负面因素,过于机智的程序员甚至可以编写出除了他们自己几乎没人可以读得懂的程序。
- C程序可能会难以修改。如果在设计中没有思考维护的问题,那么用C语言编写的大规模程序将很难修改。现代的编程语言一般都会提供“类”和“包”之类的语言特性,这样的特性可以把大的程序分解成许多更容易管理的模块。遗憾的是,C语言恰恰缺少这样的特性。
混乱的C语言
即使是那些最热爱C语言的人也不得不承认C代码难以阅读。每年一次的国际C语言混乱代码大赛(International Obfuscated C Code Contest, IOCCC)竟然鼓励参赛者编写最难以理解的C程序。获奖作品着实让人感觉莫名其妙,例如1991年的“最佳小程序”:
v,i,j,k,l,s,a[99]; main() { for(scanf(“%d”,&s);*a-s;v=a[j*=v]-a[i],k=i<s,j+=(v=j<s&& (!k&&!!printf(2+”
%c”-(!l<<!j),” #Q”[l^v?(l^j)&1:2])&& ++1||a[i]<s&&v&&v-i+j&&v+i-j))&&!(1%=s),v||(i==j?a[i+=k]=0: ++a[i])>=s*k&&++a[–i]) ; }
这个程序是由Doron Osovlanski和Baruch Nissenbaum共同编写的,其功能是打印出八皇后问题(此问题要求在一个棋盘上放置8个皇后,使得皇后之间不会出现相互“攻击”的局面)的全部解决方案。实际上,此程序可用于求解皇后数量在4~99范围内的全部问题。更多的获奖程序可以到IOCCC网站获取。
1.2.3 高效地使用C语言
高效地使用C语言要求在利用C语言优点的同时要避免它的缺点。下面是一些提议。
- 学习如何规避C语言的缺陷。规避缺陷的提示遍布全书,寻找
符号即可发现。如果想看到更详尽的缺陷列表,可以参考Andrew Koenig的《C陷阱与缺陷》
一书。现代编译器可以检查到常见的缺陷并且发出警告,但是没有一个编译器可以发现全部缺陷。 - 使用软件工具使程序更加可靠。C程序员是众多软件工具的制造者(和使用者)。
lint是最著名的C语言工具之一,一般由UNIX系统提供。与大多数C语言编译器相比,lint可以对程序进行更加广泛的错误分析。如果可以得到lint(或某个类似的程序),那么使用它应该是个好主意。另一个有益的工具是调试工具。由于C语言的本质,许多错误无法被C编译器查出。这些错误会以运行时错误或不正确输出的形式表现出来。因此在实践中,C程序员必须能够很好地使用调试工具。 - 利用现有的代码库。使用C语言的一个好处是,许多人也在使用C。把别人编写好的代码用于自己的程序是一个超级好的主意。C代码常常被打包成库(函数的集合)。获取适当的库既可以大大减少错误,也可以免去相当多的编程工作。用于常见任务(包括用户界面开发、图形学、通信、数据库管理以及网络等)的库很容易获得。有些库是公用的,有些是开源的,有些则是作为商品销售的。
- 采用一套切合实际的编码规范。编码规范是一套设计风格准则,即使语言本身没有强制要求,程序员也会遵守。精心选择的规范可以使程序更加统一,并且易于阅读和修改。使用任何一种编程语言时,规范都很重大,C语言尤其如此。正如前面所说的,C语言本身具有高度的灵活性,这使得程序员编写的代码可能会难以理解。本书的编程示例只遵循一套编码规范,但是,还有其他一些同样有效的规范可以使用。(本书将穿插讨论一些可供选择的方法。)选用哪套编码规范并不重大,重大的是必须采纳某些规范并且坚持使用它们。
- 避免“投机取巧”和极度复杂的代码。C语言鼓励使用编程技巧。用C语言完成某项指定任务时一般会有多种解决途径,程序员常常会尝试选择最简洁的方式。但是,千万不要没有节制,由于最简略的解决方式往往也是最难以理解的。本书将给出一种相当简洁但依旧易于理解的编码风格。
- 紧贴标准。大多数C编译器提供不属于C89、C99或者C1X标准的特性和库函数。为了程序的可移植性,若非确有必要,最好避免使用这些特性和库函数。
问与答
问:设置“问与答”的目的是什么?
答:很高兴有此一问。“问与答”将出目前每章的结尾。设置它主要有以下几个目的。
最主要的目的是解决学生学习C语言时常常遇到的问题。读者可以在此(从某种意义上说)与作者对话,这种形式超级像是读者置身于作者的C语言课堂一般。
另一个目的是为对应章中涉及的某些主题提供额外的信息。本书的读者可能会有不同的知识背景。有些读者可能具有其他编程语言的经验,而另外一些读者可能是第一次学习编程。有多种语言经验的读者也许会满足于简要的说明和几个示例,而那些缺少经验的读者则需要更多讲解。最基本的原则是,如果你觉得哪个主题讲得不够详细,可以查阅“问与答”部分以获取更多的信息。
必要时,“问与答”中会讨论多种C编译器的常见差异。例如,我们将介绍一些由特定编译器提供的频繁使用(但未标准化)的特性。
问:lint是做什么的?(p.5)
答:lint检查C程序中潜在的错误,包括(但不限于)可疑的类型组合、未使用的变量、不可达的代码以及不可移植的代码。lint会产生一系列程序员有必要从头到尾仔细阅读的诊断信息。使用lint的好处是,它可以检查出被编译器漏掉的错误。但我们需要记得使用lint,由于它太容易被忘记了。更糟的是,lint可以产生数百条信息,而这些信息中只有少部分指出了实际错误。
问:lint这个名字是如何得来的?
答:与其他许多UNIX工具不同,lint不是缩写。它的命名是由于它像在程序中“吹毛求疵”。
问:如何获得lint?
答:如果使用UNIX系统,那么会自动获得lint,由于它是一个标准的UNIX工具。如果采用其他操作系统,则可能没有lint。幸运的是,lint的各种版本都可以从第三方获得。在许多Linux发行版中都包含lint的增强版本splint(Secure Programming Lint),这一工具可以免费下载。
问:有没有办法在不使用lint的情况下强制编译器进行更彻底的错误检查?
答:有。大多数编译器能根据我们的要求进行更彻底的检查。除了检查错误(毫无疑问违背C语言规定的情况)外,大多数编译器还提供警告,指出可能存在问题的地方。有些编译器具有多个“警告级别”,选择较高的级别能发现更多问题。如果你的编译器支持多级警告,提议选择最高级别,以便编译器执行其能力范围内最彻底的检查。第2章的“问与答”部分讨论了GCC(➤2.1节)的错误检查选项,GCC是随Linux操作系统发布的。
* 问:我很关心能让程序尽可能可靠的方法。除了lint和调试工具以外,还有其他有效的工具吗?4
4星号标注的问题包含过于超前或者过于深奥的内容,而且常常涉及后续章节中的知识,普通读者可能不感兴趣。提议感兴趣且有必定编程经验的读者可以认真钻研一下,其他读者在初次阅读时可以先跳过这部分内容。
答:有的。其他常用的工具包括越界检查工具(bounds-checker)和内存泄漏监测工具(leak-finder)。C语言不要求检查数组下标,而越界检查工具增加了此项功能。内存泄漏监测工具协助定位“内存泄漏”,即那些动态分配却从未被释放的内存块。
本书由人民邮电出版社于2008年出版。——编者注
符号即可发现。如果想看到更详尽的缺陷列表,可以参考Andrew Koenig的《C陷阱与缺陷》
lint是最著名的C语言工具之一,一般由UNIX系统提供。与大多数C语言编译器相比,lint可以对程序进行更加广泛的错误分析。如果可以得到lint(或某个类似的程序),那么使用它应该是个好主意。另一个有益的工具是调试工具。由于C语言的本质,许多错误无法被C编译器查出。这些错误会以运行时错误或不正确输出的形式表现出来。因此在实践中,C程序员必须能够很好地使用调试工具。
容易出错不是C语言的锅!编程者的水平而已,数学考不好,难道是数学的锅??
C语言,是高手的语言,因为它的容错特性,只有高手才能避免错误发生。
关键计算机识别能力
C语言是怎么来的——
我知道c语言是怎么来的,是谭浩强写出来的,哈哈。如果有人说“我想要一种语言,只需对它说我要干什么就行”,给他一个chat GPT账号就行了
学习C,就不用折腾汇编语言了!
C语言就是:啥都听你的,你说了算
需要的发展重点类似现在,自然语言,chatgtp直接执行
Deepseek就够了
竞然是小说
【电脑技术】人们自然会问:“C语言还值得学习吗?”我想答案是肯定的,原因如下:第一,学习C有助于更好地理解C++、Java、C#、Perl以及其他基于C的语言的特性,而一开始就学习其他语言的程序员往往不能很好地掌握继承自C语言的基本特性……
相比汇编,C保留灵活性又方便使用,内存泄漏是因为编程水平太低,写汇编都是自己规划内存,切换到C简直不要太爽
收藏了,感谢分享