1.4 第一个程序
在开始写程序之前首先要搭建开发环境,安装编译器、头文件、库文件、开发文档等。在Linux系统下如何安装软件包和搭建开发环境不是本书的重点,这些问题需要读者自己解决,但我在这里简单列出需要安装的软件包供读者参考(假定你用的是Debian或Ubuntu发行版):
● gcc: The GNU C compiler
● libc6-dev: GNU C Library: Development Libraries and Header Files
● manpages-dev: Manual pages about using GNU/Linux for development
● manpages-posix-dev: Manual pages about using a POSIX system for development
● binutils:The GNU assembler,linker and binary utilities
● gdb: The GNU Debugger
● make: The GNU version of the "make" utility
本书所有代码都在Ubuntu 10.04 LTS(32位x86平台)发行版上编译测试通过。读者如果用其他Linux发行版,或者不使用发行版提供的软件包而是用自己从源代码编译出的软件包,则编译运行本书的代码得到的结果会有些不同,但不影响学习。
在Windows平台上使用微软的开发环境也可以编译运行本书的大部分代码。从http://www.microsoft.com/express/Downloads/可以下载免费版的Visual C++ 2010 Express,建议选择英文版下载安装,因为很多编译选项术语根本没有准确的中文翻译。安装之后打开开始菜单→所有程序→Microsoft Visual Studio 2010 Express→Visual Studio Command Prompt (2010),进入命令行编译和运行程序,不要使用IDE,我在前言中已经解释过理由了。
在Windows平台上编译运行C程序也可以使用MinGW(GNU开发工具的Windows版本)、Cygwin(在Windows系统中模拟的Linux环境)或者Intel的编译器,本书不做详细介绍。
通常一本教编程的书中第一个例子都是打印“Hello, World.”,这个传统源自参考文献[3],用C语言写这个程序可以这样写:
例1.1 Hello World
#include <stdio.h> /* main: generate some simple output */ int main(void) { printf("Hello, world.\n"); return 0; }
在Linux平台上,将这个程序保存成主目录下的main.c,然后编译运行:
$ gcc main.c $ ./a.out Hello, world.
gcc是Linux平台的C编译器,编译后在当前目录下生成可执行文件a.out,直接在命令行输入这个可执行文件的路径就可以执行它。如果不想把文件名叫a.out,可以用gcc的-o参数自己指定文件名:
$ gcc main.c -o main $ ./main Hello, world.
在Windows平台上,将这个程序保存成C:\目录下的main.c,打开Visual Studio命令行,用cl命令编译,然后运行:
Setting environment for using Microsoft Visual Studio 2010 x86 tools. C:\Program Files\Microsoft Visual Studio 10.0\VC>cd c:\ C:\>cl main.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. main.c Microsoft (R) Incremental Linker Version 10.00.30319.01 Copyright (C) Microsoft Corporation. All rights reserved. /out:main.exe main.obj C:\>main.exe Hello, world.
虽然这只是一个很小的程序,但我们目前暂时还不具备相关的知识来完全理解这个程序,比如程序的第一行,还有程序主体的int main(void){...return 0;}结构,这些部分我们暂时不详细解释,读者现在只需要把它们看成是每个程序按惯例必须要写的部分(Boilerplate)。但要注意main是一个特殊的名字,C程序总是从main里面的第一条语句开始执行的,在这个程序中是指printf这条语句。
第3行的/* ... */结构是一个注释(Comment),其中可以写一些描述性的话,解释这段程序在做什么。注释只是写给程序员看的,编译器会忽略从/*到*/的所有字符,所以写注释没有语法规则,爱怎么写就怎么写,并且不管写多少都不会被编译进可执行文件中。
printf语句的作用是把消息打印到屏幕。注意语句的末尾以;号(Semicolon)结束,下一条语句return 0;也是如此。
C语言用{}括号(Brace或Curly Brace)把语法结构分成组,在上面的程序中printf和return语句套在main的{}括号中,表示它们属于main的定义之中。我们看到这两句相比main那一行都缩进(Indent)了一些,在代码中可以用若干个空格(Blank)和Tab字符来缩进,缩进不是必需的,但这样使我们更容易看出这两行是属于main的定义之中的,要写出漂亮的程序必须有整齐的缩进,第9.1节将介绍推荐的缩进写法。
正如前面所说,编译器对于语法错误是毫不留情的,如果你的程序有一点拼写错误,例如第一行写成了stdoi.h,在编译时会得到错误提示:
$ gcc main.c main.c:1:19: error: stdoi.h: No such file or directory ...
这个错误提示非常紧凑,初学者往往不容易看明白出了什么错误,即使知道这个错误提示说的是第1行有错误,很多初学者对照着书看好几遍也看不出自己这一行哪里有错误,因为他们对符号和拼写不敏感(尤其是英文较差的初学者),他们还不知道这些符号是什么意思又如何能记住正确的拼写?对于初学者来说,最想看到的错误提示其实是这样的:“在main.c程序第1行的第19列,您试图包含一个叫做stdoi.h的文件,可惜我没有找到这个文件,但我却找到了一个叫做stdio.h的文件,我猜这个才是您想要的,对吗?”可惜没有任何编译器会友善到这个程度,大多数时候你所得到的错误提示并不能直接指出谁是犯人,而只是一个线索,你需要根据这个线索做一些侦探和推理。
有些时候编译器的提示信息不是error而是warning,例如把上例中的printf("Hello, world.\n");改成printf(1);然后编译运行:
$ gcc main.c main.c: In function ‘main’: main.c:7: warning: passing argument 1 of ‘printf’ makes pointer from integer without a cast ... $ ./a.out Segmentation fault
这个警告信息是说类型不匹配,但勉强还能配得上。警告信息不是致命错误,编译仍然可以继续,如果整个编译过程只有警告信息而没有错误信息,仍然可以生成可执行文件。但是,警告信息也是不容忽视的。出警告信息说明你的程序写得不够规范,可能有Bug,虽然能编译生成可执行文件,但程序的运行结果往往是不正确的,例如上面的程序运行时出了一个段错误,这属于运行时错误。各种警告信息的严重程度不同,像上面这种警告几乎一定表明程序中有Bug,而另外一些警告只表明程序写得不够规范,一般还是能正确运行的,有些不重要的警告信息gcc默认是不提示的,但这些警告信息也有可能表明程序中有Bug。一个好的习惯是打开gcc的-Wall选项,让gcc提示所有的警告信息,不管是严重的还是不严重的,然后把这些问题从代码中全部消灭。比如把上例中的printf("Hello, world.\n");改成printf(0);然后编译运行:
$ gcc main.c $ ./a.out
编译既不报错也不报警告,一切正常,但是运行程序什么也不打印。如果打开-Wall选项编译就会报警告了:
$ gcc -Wall main.c main.c: In function ‘main’: main.c:7: warning: null argument where non-null required (argument 1)
如果printf中的0是你不小心写上去的(例如错误地使用了编辑器的查找替换功能),这个警告就能帮助你发现错误。虽然本书的命令行为了突出重点通常省略-Wall选项,但是强烈建议你写每一个编译命令时都加上-Wall选项。
习题
1.尽管编译器的错误提示不够友好,但仍然是学习过程中一个很有用的工具。你可以像上面那样,从一个正确的程序开始每次改动一小点,然后编译看是什么结果,如果出错了,就尽量记住编译器给出的错误提示并把改动还原。因为错误是你改出来的,你已经知道错误原因是什么了,所以能很容易地把错误原因和错误提示信息对应起来记住,这样下次你在毫无防备的情况下撞到这个错误提示时就会很容易想到错误原因是什么了。这样反复练习,有了一定的经验积累之后面对编译器的错误提示就会从容得多了。