C 古老而永恒的语言
by 长久
Preface
在众多的程序设计语言中, C语言具有顽强的生命力. 1973年, K.Thompson 和 D.M. Ritchie两人把UNIX的90%以上用C改写,形成UNIX第5版. 经过多次改进以及可移植编译程序的出现, C语言逐渐成为世界上应用最广泛的几种计算机语言之一. 之所以说它古老是因为在计算机这个飞速发展的领域, 作为一种编程语言, C的确是一个奇迹. 互联网的核心是UNIX, 可以说没有UNIX就没有今天的互联网, UNIX这个开源系统99%的代码是用C写的.
C is a general-purpose programming language. It has been closely associated with the UNIX system where it was developed, since both the system and most of the programs that run on it are written in C.
“The C Programming Language” --- K&R
我不倾向于把这篇文章写成C语言的教程, 那样做没有任何的实际意义. 能够让初学者了解一下C, 为高手提供一个茶余饭后的小品, 我很满足了.
文章现有结构
1. Basic Concepts
2. Method
3. Advance
1. Basic Concepts
C 的存在与发展, 证明它有不同于(或优于)其他语言的特点.
1. 语言本身较小(32个关键字).
2. 运算符丰富(34种).
3. 数据结构体系较为完善.
4. 生成的目标代码质量高, 程序执行效率高(10%~20% lower than assemble code).
5. 良好的可移植性.
根据ANSI的建议, C 包括
1. Types, Operators, and Expressions
2. Control Flow
3. Functions and Program Structure
4. Pointers and Arrays
5. Structures(不是通常说的结构体, 翻译为抽象数据类型更易理解)
6. Input and Output
Standard Library 虽然不是C 的一部分, 但实际上已经成为不可缺少的内容.
和众多的语言一样, C 有它自己的数据类型, 运算符, 表达式, 流程控制, 函数, 抽象数据类型, 当然还有语法. 值得一提的是它提供指针, 这是C 的核心, 是一个诱人而且危险的区域. 有人说编程语言大同小异, 对于要求不高的学习者来说是“一通百通”, 国外有位很有名的大师用两天时间学完了python, 并开始应用于项目开发中. 笔者98年开始学习C 语言(当时没有条件上网), 可能是由于没有人指点, 完全自学, 在无数错误提示中撞的头破血流, 收效甚微, 以至于直到现在也是一知半解. 深深体会到学习语言的种种困难. 诚然, 网上的学习资料不计其数, 也不乏经典之作, 但我个人看来总是有些太“像”教程, 当今计算机应用层面的更新速度已无法想象, 就拿编程语言来说, 平均一个月就有一门新的语言出现......
结论: 在这几年与计算机打交道的过程中, 笔者越来越感觉到要学习的是计算机的科学, 作为一个工具, 它的确是用来应用的, 为了在种类繁多的应用软件中立于不败之地, 要学习一个科学的思想, 在计算机科学层次的意识的指导下, 更快、更好地运用计算机解决实际问题. 小到学习C 语言, 笔者认为也应如此.
2. Method
Coding Style
--- 一个容易被初学者忽略的问题
“Windows 团队有5000个成员, 加上额外的5000个合作伙伴, 于是生产了超过5000万行的Windows Server 2003代码. 所有人员遵从统一的领导制造代码, 生成他们的工作结果, 编译并连接为可执行程序或其他组件, 最后组成一个Windows 的 CD. 这个过程持续12到13小时, 每一天都在进行, 这是曾经尝试过的最大的软件工程任务, 没有其他软件项目可与之相比”.
--- Mark Lucovsky (Windows Server 的设计师)
语言的学习是在读代码、写代码的循环中进行的.
如何学习语言, 简单地说就是读别人的代码, 然后自己动手写代码. 虽然所有能看到的代码不可能在格式上丝毫不差, 但真正高手写的代码可以说是孪生吧. 这就面临一个问题. 刚刚开始学习语言, 养成一个从自己内心能够接受的Coding Style, 不仅可以加快阅读代码的速度, 最重要的是可以加深对代码的理解. 紧接着, 我们要写代码了. 开始之前有一个例子:
2004年7月, 我在西北一所计算机学校替朋友担任了65天左右的二级考试辅导员, 有些学生基本上没有计算机基础, 要从指法学起, 出于习惯, 我特别留意他们指法的规范性, 其中一位用左手中指敲击“E”、“R”键, 直到毕业离校...... 2005年年初的一天, 偶尔在街上相遇, 得知他花了三个月时间才把那个错误纠正过来, 因为他在实际工作中认识到了问题的严重性.
写出规范的代码, 利于程序的调试、排错. 也为后人的阅读、维护、升级奠定了基础. 对于C 语言的学习者, 建议阅读Linux系统中名为CodingStyle的文件, 虽然看起来不是很详尽, 但我认为Linus前辈的目的不是告诉我们“所有事情应该怎么做”, 而是教会我们“碰到问题该怎么办”.
printf("Hello, World!\n"); 之后, 我们做什么?
使学习深入的方法(不是深入学习的方法)
(i) 发现实际问题, 尝试自己独立解决它
(ii) 阅读高手写的代码, 从中获得真知, 并虚心向任何人请教
(iii) 使用网络资源
这三者的关系可以用一句经典的话来表述: 相辅相成, 缺一不可.
学习语言要博览群书, 一本书不可能把C 的所有知识都告诉我们, 多数情况是某本书专注于几个部分, 比如指针, 流程控制, 文件I/O ..., 另一些功能就根本没有提到, 比如与汇编语言的接口, 图形, 色彩的显示等, 其次是有些术语只是一带而过, 于是我们只好去看另一本书, 谢天谢地, 那个术语在另一本书里有详细的解释与应用. 等等, 什么汇编, 色彩还是没影啊......? 这就是实际情况, 我曾在大学图书馆一连翻阅了21本C 语言的书, 只有一本谈到了关于图形的操作, 即graphics.h的有关内容. 既然博览群书都显得有些苍白无力, 是否我们的学习陷入了不可逆转的困境?
现在我们换一个角度来思考, 假设现在手边只有一本C 的书(其中没有任何令人振奋的内容). 听说C 语言特别强大, 它能不能在屏幕上显示一个类似于windows窗口的东西, 甚至进一步可以提供与用户的交互呢? 当然是先看书了, 咦? 书上没有, 问一下别人... 哦, 据说是要用一个叫g***的头文件, 马上开始在include 文件夹里寻找... 找到, 打开. 第一感觉是Coding Style, 开头是头文件名称, 简介, 版权... 咦? #ifndef 这个预编译命令没见过, 怎么书上又没有, 于是我们晕了... 苏醒之后又去问别人, 这次没有人知道(这种情况经常发生), 在google中搜索吧, 出来了, 哇, 条目好多..., 大部分都是英文的, 好不容易点了几个中文的, 不是“该页无法打开”, 就是找不到想要的东西(这两种情况更常见). 于是, 我们又晕了... 再次醒来, 极为愤怒地关掉电脑, 两眼冒火地扫射着身边的东西, 仿佛每个东西上都有一个“C”, 历尽“千辛万苦”后找到一本书(中文的), 详细介绍了那个 #ifndef, 看过之后似乎明白了点什么. 接着看g***文件, 一个个面目狰狞, 奇形怪状的函数声明出现了. 于是, 我们第三次晕了过去...
这是我们在发现并尝试解决实际问题, 虽然很晕, 但我们知道了g***文件, 知道了 #ifndef, 并即将知道头文件里声明的各个函数. 这是我们的收获. 如果仅仅是看书学习, 博览、博览、再博览, 不能保证会碰到g***文件, 而且我没有见过哪一本C 语言的书让读者去看头文件, 只是说使用printf()前, 要在程序的开头加上#include <"stdio.h">(对于printf(), 某些编译器不用加任何头文件, 比如TC 2.0), 仅此而已. 如果你发现一本, 麻烦你发封邮件告诉我(yl_changjiu@163.com)书名, 谢谢.
现在回顾一下, 看我们的学习是不是深入了.
3. Advance
既然踏上征途, 就不要畏惧艰难险阻.
经历了无数次晕厥与苏醒, 我们已经潜移默化地将学习深入了(但并未“深入学习”), 可能我们自己并未觉察. 但这的确是很关键的一步. 可以这样说, 对C 语言的学习, 我们已经入门了. 接着, 要深入学习了.
考过研的朋友都知道, 那恼人的英语试卷中大部分的关键点都是在考察某个单词的第二或第三个意思, 是命题人故意刁难吗? 不, 因为第一个意思已为大家所熟知, 对所有人都易如反掌. 面对C , 我们现在的处境如出一辙, 在熟知了基本数据类型, 流程控制, 输入输出, 文件的打开与关闭等 a piece of cake 类的内容后, 如何深入学习?
蛋糕吃完了, 上龙虾吧.
在吃龙虾之前, 还有几件事:
1. 通读你入门时使用的C 环境下所有的头文件.
2. 按照你所知道的标准头文件的格式, 创建自己的头文件, 并在程序中使用它们.
3. 如果你还在windows 下挣扎, 请安装Linux, 使用它, 并将其源代码作为编程时至高无上
的指导思想.
很快, 我发现自己身上已经有臭鸡蛋和西红柿了. 同时听到一群声音:“几年前发布的RedHat 7.2 源代码就有50多M, 当mp3听都要近一个小时, 什么年代才能看完.”, “Linux是什么? 没听说过.”... 读Linux 源代码的确是一项艰难的任务, 这就对了. 我的一位同学说过: 生活就是让你感到沮丧、难过的, 否则就不是生活了......
以下是关于Linux 的一些东西, 希望大家能在一开始就跟随经典, 高层次地进行学习, 不要像我一样走了太多的弯路. 顺便说一句Linux 下最常用的编译器是gcc, 在以后的文章里会有关于它的介绍.
一.核心源程序的文件组织:
1.Linux核心源程序通常都安装在/usr/src/linux下,而且它有一个非常简单的编号约定:任何偶数的核心(例如2.0.30)都是一个稳定的核心,而任何奇数的核心(例如2.1.42)都是一个开发中的核心. 以下内容基于稳定的2.2.5源代码.
2.核心源程序的文件按树形结构进行组织,在源程序树的最上层你会看到这样一些目录:
●Arch:arch子目录包括了所有和体系结构相关的核心代码. 它的每一个子目录都代表一种支持的体系结构,例如i386就是关于Intel cpu及与之相兼容体系结构的子目录. PC机一般都基于此目录.
●Include: include子目录包括编译核心所需要的大部分头文件. 与平台无关的头文件在 include/linux子目录下,与 Intel cpu相关的头文件在include/asm-i386子目录下, 而include/scsi目录则是有关scsi设备的头文件目录.
●Init:这个目录包含核心的初始化代码(注:不是系统的引导代码),包含两个文件main.c和Version.c,这是研究核心如何工作的一个非常好的起点.
●Mm: 这个目录包括所有独立于 cpu 体系结构的内存管理代码,如页式存储管理内存的分配和释放等;而和体系结构相关的内存管理代码则位于arch/*/mm/,例如arch/i386/mm/Fault.c
●Kernel: 主要的核心代码,此目录下的文件实现了大多数linux系统的内核函数,其中最重要的文件当属sched.c;同样,和体系结构相关的代码在arch/*/kernel中.
●Drivers: 放置系统所有的设备驱动程序;每种驱动程序又各占用一个子目录:如,/block 下为块设备驱动程序,比如IDE设备(ide.c). 如果你希望查看所有可能包含文件系统的设备是如何初始化的,你可以看drivers/block/genhd.c中的device_setup(). 它不仅初始化硬盘,也初始化网络,因为安装nfs文件系统的时候需要网络. 其他: 如, Lib放置核心的库代码; Net, 核心与网络相关的代码; Ipc, 这个目录包含进程间通讯的代码; Fs, 所有的文件系统代码和各种类型的文件操作代码,它的每一个子目录支持一个文件系统,例如fat和ext2; Scripts, 此目录包含用于配置核心的脚本文件等. 一般,在每个目录下,都有一个 .depend 文件和一个 Makefile 文件,这两个文件都是编译时使用的辅助文件,仔细阅读这两个文件对弄清各个文件这间的联系和依托关系很有帮助;而且,在有的目录下还有Readme 文件,它是对该目录下的文件的一些说明,同样有利于我们对内核源码的理解.