Linux下C语言编程

时间:2022-01-05 01:12:43
第1章 Linux下C语言编程简介
本章将简要介绍一下什么是Linux,C语言的特点,程序开发的预备知识,Linux下C语言开发的环境,程序设计的特点和原则以及编码风格等。通过本章的学习,可以对在Linux下使用C语言编程有一个基本的了解。
1.1 Linux 简 介
Linux是能够*传播并继承了UNIX内核的操作系统,是对UNIX的简化和改进,它既保留了UNIX系统的高安全性,同时也使其操作更加简单方便,从而使单机用户也可以使用。UNIX内核指的是操作系统底层的核心程序代码。
因为 Linux本身脱胎于UNIX系统,所以Linux程序与UNIX程序是十分相似的。事实上,UNIX下编写的各种程序基本上都可以在 Linux下编译和运行。此外,许多在UNIX操作系统下创建的一些商业化应用软件,其二进制形式几乎可以在不作任何修改的情况下直接运行在 Linux系统上。
Linux是由芬兰的赫尔辛基大学 (Helsinki)学生Linus Torvalds把Minix 系统向x86移植的结果。当时 Linus 手边有个 Minix 系统(UNIX 的一个分支),他对这个操作系统相当有兴趣,由于当时他正好有一台个人计算机,他想把这个系统移植到该计算机(x86 架构)上来使用。由于受益于Stallman提倡的开放源代码(Open Source)思想,他得以接触到UNIX操作系统的一些源代码,并仔细研读了UNIX 的核心,然后去除较为繁复的核心程序,将它改写成能够适用于一般个人计算机的一种操作系统,即Linux系统的雏形。
1992年1月,大概只有100人开始使用Linux,但他们为Linux的发展壮大作出了巨大贡献。他们对一些不合理的代码进行了改进,修补了代码错误并上传补丁。Linux的腾飞最关键的因素是获得了*软件基金(FSF)的支持,他们制定了一个GNU计划,该计划的目标就是要编写一个完全免费的 UNIX版本——包括内核及所有相关的组件,可以让用户*共享并且改写软件,而Linux正好符合他们的意愿。他们将Linux与其现有的GNU应用软件很好地结合起来,使Linux拥有了图形用户界面。
提示:
Linux 实际上只是提供了操作系统的内核;它实现了多任务和多用户功能,管理硬件,分配内存,激活应用程序的运行。对初学者来说,最重要的是要明白奇数的内核版本(比如 2.3、2.5、2.7)是实验用的、正在开发的内核。 稳定的、正式发行的内核版本号则是偶数的(比如 2.2、2.4、2.6)。
1994年3月, Linux 1.0正式版发布,它的出现无异于网络的“*宣言”。从此Linux用户迅速增加,Linux的核心开发小组也日渐强大。在Linux所包含的数千个文件中,有一个名为Credits的文件,里面列出了100多名对Linux有过重要贡献的黑客,包括他们的名字、地址以及所做的工作。其中的软件都是经过“优胜劣汰”的达尔文式的选择方式保存下来的。Linux的发展方法看起来很简单:所有黑客都可为其添加额外功能并完善其性能。所谓的β测试也不仅是修补漏洞,而是进行集成并进行更多的改进、创新。Linux发展过程中的这种随意性,造成了发展过程中出现了各种各样的Linux版本。
提示:
β测试是由软件的多个用户在一个或多个用户的实际使用环境下进行的测试。这些用户是与公司签定了支持产品预发行合同的外部客户,他们要求使用该产品,并愿意返回有关错误信息给开发者。开发者通常不在测试现场,因而,β测试是在开发者无法控制的环境下进行的软件现场应用。在β测试中,由用户记下遇到的所有问题,包括真实的以及主观认定的,定期向开发者报告,开发者在综合用户的报告之后,作出修改,最终将软件产品交付给全体用户使用。由于它处在整个测试的最后阶段,因此不能指望这时发现主要问题。同时,产品的所有手册文本也应该在此阶段完全定稿。
Linux操作系统在短短的几年之内得到了非常迅猛的发展,这与Linux具有的良好特性是分不开的。Linux几乎包含了UNIX的全部功能和特性,同时又有自己的一些特点。概括地讲,Linux具有以下主要特性:
●       开放性
开放性是指系统遵循世界标准规范,特别是遵循开放系统互联(OSI)国际标准。凡遵循国际标准所开发的硬件和软件,都能彼此兼容,可方便地实现互联。
●       多用户
多用户是指系统资源可以被不同用户各自拥有和使用,即每个用户对自己的资源(例如:文件、设备)有特定的权限,互不影响。Linux继承了UNIX的多用户特性。
●       多任务
多任务是现代计算机的最主要的一个特点。它是指计算机同时执行多个程序,而且各个程序的运行互相独立。Linux系统调度每一个进程,平等地访问微处理器。由于CPU的处理速度非常快,其结果是,启动的应用程序看起来好像在并行运行。事实上,从处理器执行一个应用程序中的一组指令到Linux调度微处理器再次运行这个程序之间只有很短的时间延迟,用户是感觉不出来的。
●       良好的用户界面
Linux向用户提供了3种界面:传统操作界面、系统调用界面和图形用户界面。Linux的传统操作界面是基于文本的命令行界面,即Shell,它既可以联机使用,又可在文件上脱机使用。Shell有很强的程序设计能力,用户可方便地用它编制程序,从而为用户扩充系统功能提供了更高级的手段。可编程Shell是指将多条命令组合在一起,形成一个Shell程序,这个程序可以单独运行,也可以与其他程序同时运行。
系统调用界面是为用户提供编程时使用的界面。用户可以在编程时直接使用系统提供的系统调用命令。系统通过这个界面为用户程序提供低级、高效率的服务。
Linux还为用户提供了图形用户界面。它利用鼠标、菜单、窗口、滚动条等设施,给用户呈现一个直观、易操作、交互性强的友好的图形化界面。
●       设备独立性
Linux是具有设备独立性的操作系统,它的内核具有高度的适应能力。随着越来越多的程序员开发Linux系统,将会有更多的硬件设备加入到各种Linux内核和发行版本中。另外,由于用户可以免费得到Linux的内核源代码,因此,用户可以根据需要修改内核源代码,以便适应新增加的外部设备。
设备独立性是指操作系统把所有外部设备统一当作文件来看待,只要安装它们的驱动程序,任何用户都可以像使用文件一样,操纵、使用这些设备,而不必知道它们的具体存在形式。
具有设备独立性的操作系统,通过把每一个外围设备看作一个独立文件来简化增加新设备的工作。当需要增加新设备时,系统管理员就在内核中增加必要的连接。这种连接(也称作设备驱动程序)能保证每次调用设备提供的服务时,内核能以相同的方式来处理它们。当新的或更好的外设被开发并交付给用户时,系统允许在这些设备连接到内核后,能不受限制地立即访问它们。设备独立性的关键在于内核的适应能力。其他操作系统只允许一定数量或一定种类的外部设备连接。而设备独立性的操作系统却能够容纳任意种类及任意数量的设备,因为每一个设备都是通过其与内核的专用连接进行独立访问的。
●       提供了丰富的网络功能
完善的内置网络是Linux的一大特点。Linux在通信和网络功能方面优于其他操作系统。其他操作系统不包含如此紧密地和内核结合在一起的连接网络的能力,也没有内置这些联网特性的灵活性。而Linux为用户提供了完善的、强大的网络功能。
支持Internet是其网络功能之一。Linux免费提供了大量支持Internet的软件,通过Internet,用户能用Linux与世界上各个地区的人方便地通信。它内建了http、ftp、dns等功能,支持所有常见的网络服务,包括ftp、telnet、NFS、TCP、IP等,加上超强的稳定性,因此很多ISP(Internet Service Providers)都是采用Linux来架设邮件服务器、FTP服务器及Web 服务器等各种服务器的。Linux在最新发展的内核中还包含了一些通用的网络协议,比如IPv4、IPv6、AX.25、X.25、IPX、DDP(Appletalk)、NetBEUI、Netrom 等。用户能通过一些Linux命令完成内部信息或文件的传输。 Linux不仅允许进行文件和程序的传输,它还为系统管理员和技术人员提供了访问其他系统的接口。
另外,还可以进行远程访问。通过这种远程访问的功能,一位技术人员能够有效地为多个系统服务,即使那些系统位于相距很远的地方。稳定的核心中目前包含的网络协议有TCP、IPv4、IPX、DDP、AX等。另外还提供Netware的客户机和服务器,以及现在最热门的Samba(让用户共享Mircosoft Network资源)。
●       可靠的系统安全
Linux采取了许多安全技术措施,包括对读/写进行权限控制、带保护的子系统、审计跟踪、核心授权等,这为网络多用户环境中的用户提供了必要的安全保障。
●       良好的可移植性
可移植性是指将操作系统从一个平台转移到另一个平台上,并使它仍然能按其自身的方式运行的能力。
Linux是一种可移植的操作系统,能够在从微型计算机到大型计算机的任何环境中运行。可移植性为运行Linux的不同计算机平台与其他任何计算机进行准确而有效的通信提供了手段,不需要另外增加特殊的和昂贵的通信接口。
1.2 C语言的简介和特点
C语言是贝尔实验室的Dennis Ritchie在B语言的基础上开发出来的,1972年在一台DEC PDP-11计算机上实现了最初的C语言。C语言是与硬件无关的,用C语言编写的程序能移植到大多数计算机上。C语言在各种计算机上的快速推广导致了许多C语言版本。这些版本虽然是类似的,但通常是不兼容的。为了明确定义与机器无关的C语言,1989年美国国家标准协会制定了C语言的标准(ANSI C)。在ANSI标准化后,C语言的标准在相当长的一段时间内都基本保持不变,尽管C++进行了改进(实际上,Normative Amendment1在1995年已经开发了一个新的C语言版本,但是这个版本很少为人所知)。ANSI标准在20世纪90年代又经历了一次比较大的改进,这就是ISO9899:1999(1999年出版)。这个版本就是通常提及的C99。它被ANSI于2000年2月采用。
C 语言之所以发展迅速,而且成为最受欢迎的语言之一,主要是因为它具有强大的功能。许多著名的系统软件,如UNIX/Linux、Windows、DBASE Ⅲ PLUS、DBASE Ⅳ 都是由C 语言编写的。用C 语言加上一些汇编语言子程序,就更能显示C 语言的优势,像PC- DOS 、WORDSTAR等就是用这种方法编写的。
归纳起来,C 语言具有下列特点:
●       中级语言。它把高级语言的基本结构和语句与低级语言的实用性结合起来。C 语言可以像汇编语言一样对位、字节和地址进行操作,而这三者是计算机最基本的工作单元。
●       结构式语言 。结构式语言的显著特点是代码及数据的模块化,即程序的各个部分除了必要的信息交流外彼此独立。这种结构化方式可使程序层次清晰,便于使用、维护以及调试。C 语言是以函数形式提供给用户的, 这些函数可方便地调用, 并采用多种循环、条件语句控制程序流向,从而使程序完全结构化。
●       功能齐全。C 语言具有各种各样的数据类型,并引入了指针概念,可使程序效率更高。另外,C 语言也具有强大的图形功能,支持多种显示器和驱动器。而且计算功能、逻辑判断功能也比较强大,可以实现决策目的。
●       可与Linux无缝结合。Linux本身是使用C语言开发的,在Linux上用C语言作开发,效率很高。
1.3 Linux程序设计基础知识
对一个Linux开发人员来说,在使用一种编程语言编写程序以前,对操作系统中程序的保存位置有一个透彻的了解是很重要的。比如,应知道软件工具和开发资源保存在什么位置是很重要的。下面首先简单介绍Linux的几个重要的子目录和文件。
这部分内容虽然是针对 Linux的,但同样也适用于其他类UNIX系统。
1.3.1 程序安装目录
Linux下的程序通常都保存在专门的目录里。系统软件可以在/usr/bin子目录里找到。系统管理员为某个特定的主机系统或本地网络添加的程序可以在/usr/local/bin子目录里找到。
系统管理员一般都喜欢使用/usr/local子目录,因为它可以把供应商提供的文件和后来添加的程序以及系统本身提供的程序隔离开来。/usr子目录的这种布局方法在需要对操作系统进行升级的时候非常有用,因为只有/usr/local子目录里的东西需要保留。我们建议读者编译自己的程序时,按照/usr/local子目录的树状结构来安装和访问相应的文件。
某些随后安装的软件都有它们自己的子目录结构,其执行程序也保存在特定的子目录里,最明显的例子就是 X窗口系统,它通常安装在一个名为/usr/X11R6的子目录里,XFree论坛组织发行的用于英特尔处理器芯片的各种XFree 86窗口系统变体也安装在这里。
GNU的C语言编译器gcc(后面的程序设计示例中使用的就是它)通常安装在/usr/bin或者/usr/local/bin子目录里,但通过它运行的各种编译器支持程序一般都保存在另一个位置。这个位置是在用户使用自己的编译器时指定的,随主机类型的不同而不同。对 Linux系统来说,这个位置通常是/usr/lib/gcc-lib/目录下以其版本号确定的某个下级子目录。GN的C/C++编译器的各种编译程序以及GNU专用的头文件都保存在这里。
1.3.2 头文件
在使用C语言和其他语言进行程序设计的时候,我们需要头文件来提供对常数的定义和对系统及库函数调用的声明。对C语言来说,这些头文件几乎永远保存在/usr/include及其下级子目录里。那些赖于所运行的 UNIX或Linux操作系统特定版本的头文件一般可以在/usr/include/sys或/usr/include/linux子目录里找到。其他的程序设计软件也可以有一些预先定义好的声明文件,它们的保存位置可以被相应的编译器自动查找到。比如,X窗口系统的/usr/include/X1R6子目录和GNU C++编译器的/usr/include/g++ -2子目录等。
在调用C语言编译器的时候,可以通过给出“ -I”编译命令标志来引用保存在下级子目录或者非标准位置的头文件,类似命令如下:
[david@localhost linux]$ gcc -I /usr/openwin/include hello.c
该命令会使编译器在/usr/openwin/include子目录和标准安装目录两个位置查找fred.c程序里包含的头文件。具体情况可以参考第3章。
用grep命令来查找含有某些特定定义与函数声明的头文件是很方便的。假设想知道用来返回程序退出状态的文件的名字,可以使用如下方法:
先进入/usr/include子目录,然后在grep命令里给出该名字的几个字母,如下所示:
[david@localhost linux]$ grep KEYSPAN *.h
pci_ids.h:#define PCI_SUBVENDOR_ID_KEYSPAN      0x11a9
pci_ids.h:#define PCI_SUBDEVICE_ID_KEYSPAN_SX2 0x5334
grep命令会在该子目录里所有名字以.h结尾的文件里查找字符串“KEYSPAN”。在上面的例子里,(从其他文件中间)可以查找到文件pci_ids.h。
1.3.3 库文件
库文件是一些预先编译好的函数的集合,那些函数都是按照可再使用的原则编写的。它们通常由一组互相关联的用来完成某项常见工作的函数构成。比如用来处理屏幕显示情况的函数(curses库)等。我们将在后续章节讲述这些函数库文件。
标准的系统库文件一般保存在/lib或者/usr/lib子目录里。编译时要告诉 C语言编译器(更确切地说是链接程序)应去查找哪些库文件。默认情况下,它只会查找 C语言的标准库文件。这是从计算机速度还很慢、CPU价格还很昂贵的年代遗留下来的问题。在当时,把一个库文件放到标准化子目录里然后寄希望于编译器自己找到它是不实际的。库文件必须遵守一定的命名规则,还必须在命令行上明确地给出来。
库文件的名字永远以lib这几个字母打头,随后是说明函数库情况的部分(比如用c表示这是一个 C语言库;而m表示这是一个数学运算库等)。文件名的最后部分以一个句点(.)开始,然后给出这个库文件的类型,如下所示:
●       .a 传统的静态型函数库。
●       .so和. sa 共享型函数库(见下面的解释)。
函数库一般分为静态和共享两种格式,用ls /usr/lib命令查一下就能看到。在通知编译器查找某个库文件的时候,既可以给出其完整的路径名,也可以使用–l标志。详细内容可以参考第3章。
1. 静态库
函数库最简单的形式就是一组处于可以“拿来就用”状态下的二进制目标代码文件。当有程序需要用到函数库中的某个函数时,就会通过 include语句引用对此函数做出声明的头文件。编译器和链接程序负责把程序代码和库函数结合在一起成为一个独立的可执行程序。如果使用的不是标准的C语言运行库而是某个扩展库,就必须用–l选项指定它。
静态库也叫做档案(archive),它们的文件名按惯例都以. a结尾。比如 C语言标准库为/usr/lib/libc.a、X11库为/usr/X11R6/lib/libX11.a等。
自己建立和维护静态库的工作并不困难,用ar(“建立档案”的意思)程序就可以做到,另外要注意的是,应该用gcc -c命令对函数分别进行编译。应该尽量把函数分别保存到不同的源代码文件里去。如果函数需要存取普通数据,可以把它们放到同一个源代码文件里并使用在其中声明为static类型的变量。
2. 共享库
静态库的缺点是,如果我们在同一时间运行多个程序而它们又都使用着来自同一个函数库里的函数时,内存里就会有许多份同一函数的备份,在程序文件本身也有许多份同样的备份。这会消耗大量宝贵的内存和硬盘空间。
许多UNIX系统支持共享库,它同时克服了在这两方面的无谓消耗。对共享库和它们在不同系统上实现方法的详细讨论超出了本书的范围,所以我们把注意力集中在眼前 Linux环境下的实现方法上。
共享库的存放位置和静态库是一样的,但有着不同的文件后缀。在一个典型的 Linux系统上,C语言标准库的共享版本是 /usr/lib/libc.so N,其中的N是主版本号。
1.4 Linux下C语言编程环境概述
Linux下C语言编程常用的编辑器是vim或emacs,编译器一般用gcc,编译链接程序用make,跟踪调试一般使用gdb,项目管理用makefile。下面先通过一个小程序来熟悉这些工具的基本应用。各个工具的详细使用方法将在后面的各个章节逐步讲解。
(1) 要编辑C源程序,应首先打开vim或emacs编辑器,然后录入以下多段源代码。使用main函数调用mytool1_print、mytool2_print这两个函数。




#include "mytool1.h"
#include "mytool2.h"


int main(int argc,char **argv)
{
mytool1_print("hello");
mytool2_print("hello");
}
(2) 在mytool1.h中定义mytool1.c的头文件。




/* mytool1.h */
#ifndef_MYTOOL_1_H
#define_MYTOOL_1_H


void mytool1_print(char *print_str);


#endif
(3) 用mytool1.c实现一个简单的打印显示功能。




/* mytool1.c */
#include "mytool1.h"
void mytool1_print(char *print_str)
{
printf("This is mytool1 print %s\n",print_str);
}
(4) 在mytool2.h中定义mytool2.c头文件。




/* mytool2.h */
#ifndef _MYTOOL_2_H
#define _MYTOOL_2_H


void mytool2_print(char *print_str);


#endif
(5) mytool2.c实现的功能与mytool1.c相似。




/* mytool2.c */
#include "mytool2.h"
void mytool2_print(char *print_str)
{
printf("This is mytool2 print %s\n",print_str);
}
(6) 使用makefile文件进行项目管理。makefile文件内容如下。




main:main.o mytool1.o mytool2.o
         gcc -o main main.o mytool1.o mytool2.o
         main.o:main.c mytool1.h mytool2.h
         gcc -c main.c
         mytool1.o:mytool1.c mytool1.h
         gcc -c mytool1.c
         mytool2.o:mytool2.c mytool2.h
         gcc -c mytool2.c
(7) 将源程序文件和makefile文件保存在Linux下的同一个文件夹下,然后运行make编译链接程序如下:
[david@localhost 1c]$ make
[david@localhost 1c]$ ./main
This is mytool1 print hello
This is mytool2 print hello
至此,这个小程序算是完成了,如果想跟踪调试可以参考第4章。
1.5 Linux程序设计的特点
在进行程序设计时首先应养成良好的程序设计风格。Linux操作系统的设计师们鼓励人们采用一种独到的程序设计风格。下面是Linux程序和系统所共有的一些特点。
(1) 简单性。许多最有用的 Linux软件工具都是非常简单的,程序小而易于理解。
(2) 重点性。一个所谓功能齐全的程序可能既不容易使用,也不容易维护。如果程序只用于一个目的,那么当更好的算法或更好的操作界面被开发出来的时候,它就更容易得到改进。在 Linux世界里,通常会在需求出现的时候把小的工具程序组合到一起来完成一项更大的任务,而不是用一个巨大的程序预测一个用户的需求。
(3) 可反复性。使用的程序组件把应用程序的核心部分组建成一个库。带有简单而又灵活的程序设计接口并且文档齐备的函数库能够帮助其他人开发同类的项目,或者能够把这里的技巧用在新的应用领域。例如dbm数据库函数库就是一套由不同功能的函数组成的集合,而不是一个单一的数据库管理系统。
(4) 过滤性。许多Linux应用程序可以用作过滤器,即它们可以把自己的输入转换为另外一种形式的输出。在后面将会讲到,Linux提供的工具程序能够将其他Linux程序组合成相当复杂的应用软件,其组合方法既新颖又奇特。当然,这类程序组合正是由Linux独特的开发方法支撑着的。
(5) 开放性。文件格式比较成功和流行的 Linux程序所使用的配置文件和数据文件都是普通的 ASCII文本。如果在程序开发中遵循该原则,将是一种很好的做法。它使用户能够利用标准的软件工具对配置数据进行改动和搜索,从而开发出新的工具,并通过新的函数对数据文件进行处理。源代码交叉引用检查软件 ctags就是一个这样的好例子,它把程序中的符号位置信息以规则表达式的形式记录下来供检索程序使用。
(6) 灵活性。因为你根本无法预测一个不太聪明的用户会怎样使用你的程序,因此在进行程序设计时,要尽可能地增加灵活性,尽量避免给数据域长度或者记录条数加上限制。同时如果可能,应尽量编写能够响应网络访问的程序,使它既能够跨网络运行又能够在本地单机上运行。
1.6 Linux下C语言编码的风格
Linux作为GN家族的一员,其源代码数以万计,而在阅读这些源代码时我们会发现,不同的源代码的美观程度和编程风格都不尽相同,例如下面的glibc代码:
static voidrelease_libc_mem (void)
{
/*Only call the free function if we still are running in mtrace mode. */
if (mallstream != NULL)
__libc_freeres ();
}
或者Linux的核心代码:
static int do_linuxrc(void * shell)
{
static char *argv[] = { "linuxrc",NULL,};
close(0);close(1);close(2);
setsid();
(void) open("/dev/console",O_RDWR,0);
(void) dup(0);
(void) dup(0);
return execve(shell,argv,envp_init);
}
比较一下,上面的这些代码是否看起来让人赏心悦目?而有些程序员编写的程序由于没有很好的缩进及顺序,让人看起来直皱眉头。编写干净美观的代码,不仅仅使代码更容易阅读,还能使代码成为一件艺术品。与微软的匈牙利命名法一样,Linux上的编程主要有两种编程风格:GNU风格和Linux核心风格,下面将分别介绍。
1.6.1 GNU编程风格
下面是基于GNU的编程风格,编写代码时应遵循这些基本要求。
●       函数开头的左花括号放到最左边,避免把任何其他的左花括号、左括号或者左方括号放到最左边。
à       尽力避免让两个不同优先级的操作符出现在相同的对齐方式中。
à       每个程序都应该有一段简短地说明其功能的注释开头。例如:fmt - filter for simplefilling of text。
●       请为每个函数书写注释,以说明函数做了些什么,需要哪些种类的参数,参数可能值的含义以及用途。
à       不要在声明多个变量时跨行。在每一行中都以一个新的声明开头。
à       当在一个if语句中嵌套了另一个if-else语句时,应用花括号把if-else括起来。
●       要在同一个声明中同时说明结构标识和变量,或者结构标识和类型定义(typedef)。
à       尽力避免在if的条件中进行赋值。
à       请在名字中使用下划线以分隔单词,尽量使用小写; 把大写字母留给宏和枚举常量,以及根据统一的惯例使用的前缀。
à       命令一个命令行选项时,给出的变量应该在选项含义的说明之后,而不是选项字符之后。
1.6.2 Linux 内核编程风格
下面是 Linux 内核所要求的编程风格:
●       注意缩进格式。
●       将开始的大括号放在一行的最后,而将结束大括号放在一行的第一位。
●       命名系统。变量命名尽量使用简短的名字。
●       函数最好要短小精悍,一个函数最好只作一件事情。
●       注释。注释说明代码的功能,而不是说明其实现原理。
看了上面两种风格的介绍,读者是不是觉得有些太多了,难以记住?不要紧,Linux有很多工具来帮助我们。除了vim和emacs以外,还有一个非常有意思的小工具 indent可以帮我们美化C/C++源代码。
下面用这条命令将Linux 内核编程风格的程序quan.c转变为 GNU编程风格,代码如下:
[david@localhost ~]$ indent -gnu quan.c
利用indent这个工具,大家就可以方便地写出漂亮的代码来。












第2章 vi与emacs编辑器
从本章开始,我们将进入Linux充满挑战的C语言编程世界,首先介绍的是文本编辑器。
文本编辑器可以说是计算机最基本的应用,修改设置文件、编写程序或者建立文件都需要用到它。Linux提供了齐全的文本编辑器,可以让用户按照自己的喜好进行选择。本章主要介绍vim、emacs等编辑器,对Linux其他的编辑器也稍作介绍。通过本章的学习,可以对Linux下的编辑器有一个深入的了解,为今后编程打下良好基础。
2.1 vim概述及应用
vim(vi improve)可以说是Linux中功能最为强大的编辑器,它是由UNIX系统下的传统文本编辑器vi发展而来的。下面首先介绍一下vi。
vi是个可视化的编辑器(vi就意味着可视化——visual)。 那么,什么是可视化的编辑器呢?可视化的编辑器就是可以在编辑文本的时候看到它们。非可视化的编辑器的例子可以举出不少,如ed、sed和edlin(它是DOS自带的最后一个编辑器) 等。vi成为BSD UNIX的一部分,后来AT&T也开始用vi,于是标准UNIX也开始 用vi。Linux下的vim是vi的一个增强版本,有彩色和高亮等特性,对编程有很大的帮助。
1. 启动与退出vim
由于vim的功能很多,首先来看如何启动和退出vim。
(1) 在Linux提示符下键入vim(或使用vim myfile来编辑已经存在的文件)即可启动它。
(2) 要退出vim,先按下Esc键回到命令行模式,然后键入“:”,此时光标会停留在最下面一行,再键入“q”,最后按下Enter键即可,见图2-1。
技巧:
在X-Window下也可以通过在“开始”菜单里找到“编程”︱Vi I Mproved来运行X-Window下的vim。此时其界面如图2-2所示。
图2-1 退出vim
图2-2 X-Window下的vim界面
2. 命令行模式的操作
命令行模式提供了相当多的按键及组合按键来执行命令,帮助用户编辑文件。由于这些命令相当多,在此仅作简单介绍。
(1) 移动光标
在命令行模式和插入模式下,都可以使用上、下、左、右4个方向键来移动光标的位置。但是有些情况下,如使用telnet远程登陆时,方向键就不能用,必须用命令行模式下的光标移动命令。这些命令及作用见表2-1。


表2-1 常用的移动光标的命令
命    令 操 作 说 明
h 将光标向左移动一格
l 将光标向右移动一格
j 将光标向上移动一格
k 将光标向下移动一格
0 将光标移动到该行的最前面
$ 将光移动到该行的最后面
G 将光标移动到最后一行的开头
W或w 将光标移动到下一个字
e 将光标移动到本单词的最后一个字符。如果光标所在的位置为本单词的最后一个字符,则跳动到下一个单字的最后一个字符。标点符号如“.”、“,”或“/”等字符都会被当成一个字
b 将光标移动到单词的最后一个字符,如果光标所在位置为本单词的第一个字符,则跳到上一个单词的第一个字符
{ 将光标移动到前面的“{”处。在C语言编程时,如果按两次就会找到函数开头“{”处,如果再次连续按两次还可以找到上一个函数的开头处
} 同“{”的使用,将光标移动到后面的“}”
Ctrl+b 如果想要翻看文章的前后,可以使用Page Down和Page Up;但当这两个键不能使用时,可以使用Ctrl+b将光标向前卷一页,相当于Page Up
Ctrl+f 将光标向后卷一页,相当于Page Down
Ctrl+u 将光标向前移半页
Ctrl+d 将光标向后移半页
Ctrl+e 将光标向下卷一行
Ctrl+y 将光标向后卷一行
N+\ 将光标移至第n行(n为数字)
(2) 复制文本
复制文本可以节省重复输入的时间,vim也提供了以下的操作命令,见表2-2。
表2-2 常用的复制文本的命令
命    令 操 作 说 明
y+y 将光标目前所在的位置整行复制
y+w 复制光标所在的位置到整个单词所在的位置
n+y+w 若输入3yw,则会将光标所在位置到单词结束以及后面两个单词(共3个单词)一起复制
n+y+y 若按3yy,则将连同光标所在位置的一行与下面两行一起复制
p 将复制的内容粘贴光标所在的位置。若复制的是整行文本,则会将整行内容粘贴到光标所在的位置
(3) 删除文本
删除文本命令一次可删除一个字符,也可以一次删除好几个字符或是整行文本,见表2-3。
表2-3 常用的删除文本的命令
命    令 操 作 说 明
d+左方向键 连续按d和左方向键,将光标所在位置前一个字符删除
d+右方向键 将光标所在位置字符删除
d+上方向键 将光标所在位置行与其上一行同时删除
d+下方向键 将光标所在位置行与下一行同时删除
d+d 连按两次d,可将光标所在的行删除,若是连续删除,可以按住d不放
d+w 删除光标所在位置的单词,若是光标在两个字之间,则删除光标后面的一个字符
n+d+d 删除包括光标所在行及向下的n行(n为数字)
n+d+上方向键 删除包括光标所在行及向上的n行
n+d+下方向键 同n+d+d命令
D 将光标所在行后所有的单词删除
x 将光标所在位置的字符删除
X 将光标所在位置前一个字符删除
n+x 删除光标所在位置及其后的n个字符
n+X 删除光标所在位置及其前的n个字符


(4) 找出行数及其他按键
当我们编写程序时,常常需要跳到某一行去修改,因此每一行的行号就相当重要。vim为此提供的命令见表2-4。
表2-4 常用的找出行数的命令
命    令 操 作 说 明
Ctrl+g 在最后一行中显示光标所在位置的行数及文章的总行数
nG 将光标移至n行(n为数字)
r   修改光标所在字符
R 修改光标所在位置的字符,可以一直替换字符,直到按下ESC键
u 表示复原功能
U 取消对行所做的所有改变
. 重复执行上一命令
Z+Z 连续两次输入z,表示保存文件并退出vi
% 符号匹配功能,在编辑时,如果输入“%(”,系统将会自动匹配相应的“)”
3. 命令行模式切换到输入模式
进入vim时,默认的模式是命令行模式,而要进入输入模式输入数据时,可以用下列按键:
●       按“a”键 从目前光标所在位置的下一个字符开始输入。
●       按“i”键 从光标所在位置开始插入新输入的字符。
●       按“o”键 新增加一行,并将光标移到下一行的开头。
4. 最后行模式的操作
vim的最后行模式是指可以在界面最底部的一行显示的输入命令,一般用来执行查找特定的字符串、保存及退出等任务。在命令行模式下输入冒号“:”,就可以进入最后行模式了,还可以使用“?”和“/”键进入最后行模式。比起命令行模式的诸多操作命令,最后行模式的操作命令就少多了,见表2-5。
表2-5 最后行模式主要的操作命令
命    令 操 作 说 明
e 在vi中编辑时,可以使用e创建新的文件
n 加载新文件
w 写文件,也就是将编辑的内容保存到文件系统中。vim在编辑文件时,先将编辑内容保存在临时文件中,如果没有执行写操作直接退出的话,修改内容并没有保存到文件中
w! 如果想写只读文件,可以使用w!强制写入文件
q! 表示退出vim,但是文件内容有修改的话,系统会提示要先保存,如果不保存退出,需要使用命令q!强制退出
set nu set可以设置vim 的某些特性,这里是设置每行开头提示行数。想取消设置,使用命令set none
/ 查找匹配字符串功能。在编辑时,想查找包含某一个字符串,可以用“/字符串”自动查找,系统会突出显示所有找到的字符串,并转到找到的第一个字符串。如果想继续向下查找,可以按n键;向前继续查找则按N键
? 也可以使用“?字符串”查找特定字符串,它的使用与“/”相似,但它是向前查找字符串


5. vim的注意事项
由于Linux系统的vim编辑器是从UNIX下的vi发展而来的,而UNIX下的vi编辑器是从行编辑器ed发展而来的。因此,vim不如目前流行的微软推出的同类编辑器易用、直观,但是它的强大功能却是微软同类产品无法比拟的。因此一些人学习时可能会感到有一些不便和困惑。针对这类问题,这里列出了使用vim中应注意的一些事项。当然要熟练使用vim,还需要平时操作中不断地提高和积累。
●       插入编辑方式和命令方式切换时出现混乱
这种情况产生的原因通常是:还未输入插入命令便开始进行文本输入,从而无法在正确位置输入文本;另外,当插入信息后,还未按Esc键结束插入方式,就又输入其他的命令信息,从而使命令无法执行。
当出现这种情况时,首先要确定自己所处的操作方式,然后再确定下一步做什么工作。若不易搞清楚当前所处的状态,还可以使用Esc键退回到命令方式重新进行输入。
●       在进行文档编辑时,vim编辑器会产生混乱
这种状态的产生往往是由于屏幕刷新有误,此时可以使用Ctrl+l键对屏幕进行刷新,如果是在终端,可以用Ctrl+r进行屏幕刷新。
●       对屏幕中显示的信息进行操作时,系统没有反应。
 出现这种情况可能是由于屏幕的多个进程被挂起(如不慎用了Ctrl+s键等),此时可用Ctrl+q进行解脱,然后重新进行输入。
●       当编辑完成后,不能正确退出vim
出现这种情况的原因可能是系统出现了意外情况。如:文件属性为只读、用户对编辑的文件没有写的权限。如果强行执行退出命令“:w!”仍无法退出,可以用“:w newfile”命令将文件重新存盘后再退出,以减少工作中的损失,这个新文件newfile应是用户有写权限的文件。
如果暂时没有可以使用的文件,可以借用/tmp目录建一个新的文件。因为Linux系统中的/tmp是一个临时目录,系统启动时总要刷新该目录,因此操作系统一般情况下不对此目录下进行保护。但当处理完成后,切记应将新文件进行转储,否则依然会造成信息损失。
●       在使用vim时,万一发生了系统掉电或者突然当机的情况怎么办?
工作时发生了掉电和当机,对正做的工作无疑是一种损失,但是vim程序可使损失降到最小。因为,对vim的操作实际上是对编辑缓冲区的数据操作,而系统经常会将缓冲区的内容自动进行保存。因此,当机后用户可以在下次登陆系统后使用-r选项进入vi,将系统中最后保存的内容恢复出来。例如,在编辑cd文件的时候突然断电或者系统崩溃后的恢复命令为:
[david@DAVID david]$ vi cd -r
vim的学习应侧重于实际的应用,在了解vim的使用规则后应该多上机操作,不断积累经验,逐步地使自己成为vi编辑能手。
2.2 emacs简介及应用
emacs编辑器是一款*软件产品,在Linux系统中比较流行。emacs的涵义是宏编辑器(macro editor)。emacs最开始是由richard stallman编写的,他的初衷是将emacs设计成一个Linux的shell,同时还增加了一些现代操作系统应支持的用户环境(比如,mail的收发、web的查询、新闻阅读、日志功能等)。另外,在emacs中还包括了list语言的解释执行功能。
emacs的一个缺点是它占用的磁盘空间比较大,因此为了支持用户的使用,emacs提供多种模式以适用于不同的用户需求。进行安装时,可根据选项设置指定的模式,以减少磁盘的使用量。
1. emacs的启动和退出
emacs中包含的命令很多,对于初学者来说有一些困难,但是一旦适应了它的使用方法,就会感到它的方便和灵活。
在文本模式下要进入emacs,只要键入emacs即可:
[david@DAVID david]$ emacs
或者键入emacs [filename]来编辑文件:
[david@DAVID david]$ emacs [filename]
启动emacs后,看到的是emacs的基本情况描述信息。
File Edit Options Buffers Tools Help
Welcome to GNU Emacs, one component of a Linux-based GNU system.


Get help                       C-h (Hold down CTRL and press h)
Undo changes         C-x u       Exit Emacs                             C-x C-c
Get a tutorial             C-h t       Use Info to read docs    C-h i
Ordering manuals                   C-h RET
Activate menubar F10 or ESC ' or   M-'
('C-' means use the CTRL key. 'M-' means use the Meta (or Alt) key.
If you have no Meta key, you may instead type ESC followed by the
character.)


GNU Emacs 21.2.1 (i386-redhat-Linux-gnu, X toolkit, Xaw3d scroll bars)
 of 2003-02-20 on porky.devel.redhat.com
Copyright (C) 2001 Free Software Foundation, Inc.


GNU Emacs comes with ABSOLUTELY NO WARRANTY; type C-h C-w for full details.
Emacs is Free Software--Free as in Freedom--so you can redistribute
copies
of Emacs and modify it; type C-h C-c to see the conditions.
Type C-h C-d for information on getting the latest version.


-uuu:---F1 *scratch* (Lisp Interaction)--L1--All--------
[35dFor information about the GNU Project and its goals, type C-h C-p.


提示:
要退出 emacs,只要键入Ctrl+x或Ctrl+c即可。 即先按住键盘上的 Ctrl 键不放,再按下英文字母x或c 即可。当然启动或退出 emacs 的方法还有多种,将在以下各小节中陆续介绍。
技巧:
在X-Window下也可以通过在“开始”菜单里找到“编程”︱emacs来运行X-Window下的emacs,见图2-3。
图2-3 X-Window下的emacs
2. 文本编辑
在emacs中的文本编辑的方式与vim的编辑方式有很大的区别,现在只简单介绍一些常用操作。
(1) 删除文本
●       删除光标左侧的字符:按Delete键可删除光标左侧的字符。
●       删除光标所在的字符:按Ctrl+d键可删除光标所在的字符。
●       删除光标左侧单词:按Alt+Delete键可删除光标左侧的单词。
●       删除光标右侧单词:按Alt+d键可以删除光标右侧的单词。
●       删除至行尾:按Ctrl+k键可以从光标处开始删除至尾行。
●       删除多行:不要移动光标,连续在同一位置按Ctrl+k键。
●       删除一个句子:按Alt+k从光标处开始删到句子尾。
(2) 行的分割、合并与新增
●       分割一行:在要分割处按下Enter键。
●       合并两行:在行尾处按Ctrl+d或于次行首按Delete。
●       新增空白行:按Ctrl+e将光标移至尾行再按下Enter键。
(3) 命令的复原与取消
●       复原上一个命令:按下Ctrl+x u、Ctrl +/或Ctrl+_ (同时按下Ctrl+Shift+_3个键),可以恢复到上一个命令。
●       取消目前再执行的命令:按Ctrl+g可以取消目前正在执行的命令,按错命令时可用此按键取消。
(4) 剪切与粘贴
在了解剪切(cut)与粘贴文本的按键操作前,先了解一下删除与剪切命令的区别。
●       删除:凡是一次只删除一个字符的按键命令多属于删除命令,如上述的Delete、Ctrl+d、Alt+Delete与Alt+d等按键。使用这些按键所删除的字符无法被恢复。
●       剪切:剪切命令可以将选择的内容复制到粘贴板上,并将原文中的内容删除。上面提到的Ctrl+k、Alt+k等按键就是剪切命令。
●       粘贴:按Ctrl+y会将当前粘贴板上的内容复制到光标所在位置。
(5) 复制文本与区块
●       复制文本:先剪切,再粘贴。可以在选择完内容后按Ctrl+k剪切文本,再按Ctrl+y复制文本。
●       复制区块:在一个地方(A)按下Ctrl+Spase或Ctrl+@ (Ctrl+Shift+2)使它成为一个表示点,将光标移至另一处(B),再按下Alt+w,可将A与B之间的文本复制到系统的内存中,稍后可用粘贴命令将它们粘贴回来。
3. 查找与替换
(1) 一般查找
在emacs中可用Ctrl+s及Ctrl+r两组命令进行渐进式查找。其中Ctrl+s会从光标所在的位置向文件尾方向查找,而Ctrl+r则是从光标所在的位置向文件头的方向查找。
按下Ctrl+s或Ctrl+r后,响应区会出现:
-search:
或者出现
-search backward:
可以在响应区输入要查找的文本,并按Enter键,光标便会移至符合查找条件的字符串位置,此时可以继续按Ctrl+s键,将光标移至下一个符合查找条件的字符串,或按Ctrl+r键,将光标移至上一个符合条件的字符串。


如果查找失败,就会出现如下的信息:
Failing I-search: sdfsdfsdfsdfsdfsdfsd
(2) 替换全部字符串
使用此功能,可将光标后所有的匹配字符串一次性替换掉,系统并不会询问用户来进行确认,因此使用时要特别小心。操作过程如下:
按Alt+x键,并于响应区输入“replace-string”(实际输入时要使用替换文本),即可开始字符串的替换。在提示符后面输入原始的字符串,并按Enter键,再在提示符后输入替换后的新字符串,即可替换光标后所有匹配的字符串。
(3) 选择性替换
选择性替换就是在替换时询问一下用户的意见,然后根据指示来决定是否替换。操作过程如下:
按下Alt+x键,于响应区输入“query-replace”,即可进行选择性替换,并在提示符后输入原始字符串,按Enter键,再提示输入替换后的新字符串。此时如果系统发现可替换的字符串,可按Enter键进行替换、按n键跳至下一个匹配的字符串,或按q键中止替换操作。操作的更详细说明可按F1键获得。
2.3 Linux下的其他编辑器
前两节介绍的vim和emacs都是Linux下的最常用的编辑器,尽管功能强大,但是操作也比较复杂,本节介绍两款操作简单的编辑器,即ed和pico。
2.3.1 最简单的文本编辑器ed
ed可以说是Linux下功能最简单的编辑器。ed一次仅能编辑一行,而非以全屏的方式来操作。
要进入ed编辑环境,只需要在命令行输入ed即可:
[david@DAVID david]$ ed
ed有两种模式,分别是命令行模式与输入模式。当第一次执行ed时,进入的是ed的命令行模式,此模式下只能执行一些命令。由于进入ed后没有任何的说明文本,如果输入的命令不正确,则会出现问号“?”。如下代码所示,表示ed无法确认当前的操作,此时应重新输入正确命令。
[david@DAVID david]$ ed
david
?
Linux
?
1. 输入文本
由于命令行模式仅能输入命令,因此要开始编辑文件内容,必须转到输入模式。进入编辑模式有3种方式,见表2-6。
表2-6 输入模式下3种输入方式
命    令 操 作 说 明
A 将新输入的内容接在最后一行后面
i 将新输入的内容加到最后输入的一行的前一行
c 将新输入的内容替换原来的最后一行


下面是三个命令的应用实例。
 a命令应用实例:
[david@DAVID david]$ ed
a
i am david
i'm a Linuxer
 i命令应用实例:
[david@DAVID david]$ ed
a
i am david
.
i
i am a Linuxer
c命令应用实例:
[david@DAVID david]$ ed
a
i am david
.
c
i am david
如果想编辑一个已经存在的文件(比如david.txt),则可用下面的方式来执行ed:
[david@DAVID david]$ ed david.txt
11
提示:
ed无法让用户一次看到全部的内容,但是可以在命令行模式下看到最后输入的一行,例如:
[david@DAVID david]$ ed david.txt
11
.
i am david
2. 插入一行
若输入内容后,想在前面插入一行,则可输入i:
[david@DAVID david]$ ed
a
i am david
i am a Linuxer
.
i
i love xueer    这一行将插入到“i am a Linuxer”之前
3. 存盘和退出
当建立文件时或完成编辑后,可以随时在命令行模式输入“w”保存文件,而要退出则输入“q”即可:
[david@DAVID david]$ ed
a
i am david
i am a Linuxer
.
i
i love xueer
.
w xueer.txt   将文件保存为xueer.txt。如果是编辑已有的文件,则输入“w”即可
47
q             退出ed
[david@DAVID david]$   回到Linux提示符下
以上对Linux下的ed作了简单的介绍,虽然ed的功能不是太强,但当我们只需建立一个简单的文件时,也不失为一个相当方便的工具。
2.3.2 最容易上手的编辑器pico
如果觉得vim和emacs太难学,而ed功能又太简单,那么不妨试试pico。pico的使用界面有点像DOS下的PE2,即使是第一次使用的人也能够很快熟悉这种操作方式。这是Linux下最容易使用的入门级文本编辑器。
1. pico的编辑环境
可以在Linux提示符下执行pico(或者执行pico filename 加载一个文件)来启动它:
UW PICO(tm) 4.2                New Buffer


^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text^T To Spell
pico不像其他编辑器那样有命令行模式与输入模式之分,用户可以直接在编辑区输入文本。按Enter键可换行,按空格键可将光标向右移动。当要删除字符时,将光标移动到该字符的右边,然后按Backspace键即可删除(按Delete键无效)。
2. pico的操作按键
在pico编辑环境的下方,有两排共12组操作按键,这些只是最常用的部分,其他比较少用的操作按键没有列出来。下面分别详述其功能。
(1) 显示辅助功能—— Ctrl+G
按Ctrl+G出现pico的帮助文档,再按Ctrl+V显示下一页,里面会列出所有的操作按键(除了这里介绍的12个之外,还有约12个操作按键,试试看)。
UW PICO(tm) 4.2                New Buffer


Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the pine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.


Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.


The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).


^G (F1)   Display this help text.


^F        move Forward a character.
^B        move Backward a character.
^P        move to the Previous line.
^N        move to the Next line.
^A        move to the beginning of the current line.
^E        move to the End of the current line.
^V (F8)   move forward a page of text.
^Y (F7)   move backward a page of text.
                    [ Unknown Command. ]


^X Exit Help                           ^V Next Pg
(2) 保存文件——Ctrl+O
按Ctrl+O后,操作会变成下面这个样子:
File Name to write : LINUX.TXT 输入文件名后按Enter键即可
^G Get Help ^T To Files
^C Cancel    TAB Complete
注意:
此处出现的几个操作按键,其中Ctrl+C显示当前内容对应的帮助文档,与Ctrl+G不同,Ctrl+C表示不保存内容而返回到原来的编辑环境。Ctrl+T会显示目录,由用户选择要保存为哪一个文件。TAB则会帮用户添上完整的文件名称。
(3) 插入文件——Ctrl+R
按Ctrl+R可以在文件中插入一个文本文件的内容。
File to insert from home directory: /home/david/david.txt 此处输入要插
入的文件的名称
^G Get Help ^T To Files
^C Cancel
(4) 滚动页面Ctrl+Y、Ctrl+C
按Ctrl+Y可切换到前一页,如同按下PageUp; 按Ctrl+Y 可以切换到下一页,如同按下PageDown。
(5) 剪切和粘贴整行文本——Ctrl+K、Ctrl+U
当要剪切整行文本时,可将光标移动到要剪切的那一行,然后按Ctrl+K。当剪切之后在其他位置粘贴的时候,则将光标移动到粘贴的位置的下一行,再按Ctrl+U。用户也可以连续按3次 Ctrl+K 剪切3行(中间不可以有其他按键),再将光标移动到要粘贴的位置,然后按Ctrl+U。如单独使用Ctrl+K,就如同删除整行的操作按键。
(6) 自动调整文本的对齐——Ctrl+J、Ctrl+U
在输入文本的时候,可能没有注意到文本排列的美观,而造成每一行有长有短参差不齐的情况:
UW PICO(tm) 4.2   New Buffer                       Modified


hello david
i love Linux
i am a liuxer how are you i am fine and you
此时若将光标放到调整的段落中,然后按下Ctrl+J,则整段文本会重新对齐,如下所示:
UW PICO(tm) 4.2     New Buffer                         Modified


hello david i love Linux i am a liuxer how are you i am fine and you
每一次Ctrl+J只对一个段落起作用,如果觉得pico对齐的样子还比不上原来的好看,此时不要移动光标(只要一移动就不能恢复了),接着按Ctrl+Uj即可恢复。
(7) 查找字符串——Ctrl+W
若要在文章中查找某一个字符串,按Ctrl+W。
Search : Linux
^G Get Help ^Y First Line ^T LineNumber^O End of Par
^C Cancel    ^V Last Line ^W Start of P
(8) 显示目前光标的位置——Ctrl+C
在pico中不能显示行号,因此我们可能会不知道目前光标所在的位置,但是只要按Ctrl+C,就会显示光标在全部行数中的第几行了。
(9) pico还可以按Ctrl+T检查拼写错误
UW PICO(tm) 4.2     New Buffer                       Modified


hello david
i love Linux
i am a liuxer how are you i am fine and you




Edit a replacement: david
^G Get Help
^C Cancel


















































第3章 gcc 编译器
Linux的各发行版中包含了很多软件开发工具,它们中的很多是用于C和C++应用程序开发的。本章将介绍如何使用Linux下的C 编译器和其他C编程工具。
3.1 gcc 简 介
在为Linux开发应用程序时,绝大多数情况下使用的都是C语言,因此几乎每一位Linux程序员面临的首要问题都是如何灵活运用C编译器。目前Linux下最常用的C语言编译器是gcc(GNU Compiler Collection),它是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序。gcc不仅功能十分强大,结构也异常灵活。最值得称道的一点就是它可以通过不同的前端模块来支持各种语言,如Java、Fortran、Pascal、Modula-3和Ada等。gcc是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比,平均效率要高20%~30%。gcc支持编译的一些源文件的后缀及其解释见表3-1。
表3-1 gcc所支持的语言
后 缀 名 所支持的语言
.c C原始程序
.C C++原始程序
.cc C++原始程序
.cxx C++原始程序
.m Objective-C原始程序
.i 已经过预处理的C原始程序
.ii 已经过预处理的C++原始程序
.s 组合语言原始程序
.S 组合语言原始程序
.h 预处理文件(标头文件)
.o 目标文件
.a 存档文件


开放、*和灵活是Linux的魅力所在,而这一点在gcc上的体现就是程序员通过它能够更好地控制整个编译过程。
在使用gcc编译程序时,编译过程可以细分为4个阶段:
●       预处理(Pre-Processing)
●       编译(Compiling)
●       汇编(Assembling)
●       链接(Linking)
Linux程序员可以根据自己的需要让gcc在编译的任何阶段结束,检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。与其他常用的编译器一样,gcc也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码。
gcc提供了30多条警告信息和3个警告级别,使用它们有助于增强程序的稳定性和可移植性。此外,gcc还对标准的C和C++语言进行了大量的扩展,提高了程序的执行效率,有助于编译器进行代码优化,能够减轻编程的工作量。
3.2 使 用 gcc
gcc的版本可以使用如下gcc –v命令查看:
[david@DAVID david]$ gcc -v
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/specs
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man
--infodir=/
sr/share/info --enable-shared --enable-threads=posix
--disable-checking --with-
ystem-zlib --enable-__cxa_atexit --host=i386-redhat-linux
Thread model: posix
gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5)
以上显示的就是Redhat linux 9.0里自带的gcc的版本3.2.2。
下面将以一个实例来说明如何使用gcc编译器。例3-1能够帮助大家迅速理解gcc的工作原理,并将其立即运用到实际的项目开发中去。
实例3-1 hello.c­­­­­­­­­­­­­­­­­­­­­­­­­­­­




#include
int main (int argc,char **argv) {
printf("Hello Linux\n");
}
要编译这个程序,只要在命令行下执行如下命令:
[david@DAVID david]$ gcc hello.c -o hello
[david@DAVID david]$ ./hello
Hello Linux
这样,gcc 编译器会生成一个名为hello的可执行文件,然后执行./hello就可以看到程序的输出结果了。
命令行中 gcc表示用gcc来编译源程序,-o 选项表示要求编译器输出的可执行文件名为hello ,而hello.c是源程序文件。从程序员的角度看,只需简单地执行一条gcc命令就可以了;但从编译器的角度来看,却需要完成一系列非常繁杂的工作。首先,gcc需要调用预处理程序cpp,由它负责展开在源文件中定义的宏,并向其中插入#include语句所包含的内容;接着,gcc会调用ccl和as将处理后的源代码编译成目标代码;最后,gcc会调用链接程序ld,把生成的目标代码链接成一个可执行程序。
为了更好地理解gcc的工作过程,可以把上述编译过程分成几个步骤单独进行,并观察每步的运行结果。
第一步要进行预编译,使用-E参数可以让gcc在预处理结束后停止编译过程:
[david@DAVID david]$ gcc -E hello.c -o hello.i
此时若查看hello.i文件中的内容,会发现stdio.h的内容确实都插到文件里去了,而且被预处理的宏定义也都作了相应的处理。
# 1 "hello.c"
# 1 ""
# 1 ""
# 1 "hello.c"
# 1 "/usr/include/stdio.h" 1 3
# 28 "/usr/include/stdio.h" 3
# 1 "/usr/include/features.h" 1 3
# 291 "/usr/include/features.h" 3
# 1 "/usr/include/sys/cdefs.h" 1 3
# 292 "/usr/include/features.h" 2 3
# 314 "/usr/include/features.h" 3
# 1 "/usr/include/gnu/stubs.h" 1 3
# 315 "/usr/include/features.h" 2 3
# 29 "/usr/include/stdio.h" 2 3
# 1 "/usr/lib/gcc-lib/i386-redhat-linux/3.2.2/include/stddef.h" 1 3
# 213 "/usr/lib/gcc-lib/i386-redhat-linux/3.2.2/include/stddef.h" 3
typedef unsigned int size_t;
# 35 "/usr/include/stdio.h" 2 3
# 1 "/usr/include/bits/types.h" 1 3
# 28 "/usr/include/bits/types.h" 3
# 1 "/usr/include/bits/wordsize.h" 1 3
# 29 "/usr/include/bits/types.h" 2 3
# 1 "/usr/lib/gcc-lib/i386-redhat-linux/3.2.2/include/stddef.h" 1 3
# 32 "/usr/include/bits/types.h" 2 3


"hello.i" 838L, 16453C                         1,1           Top
下一步是将hello.i编译为目标代码,这可以通过使用-c参数来完成:
[david@DAVID david]$ gcc -c hello.i -o hello.o
gcc默认将.i文件看成是预处理后的C语言源代码,因此上述命令将自动跳过预处理步骤而开始执行编译过程,也可以使用-x参数让gcc从指定的步骤开始编译。最后一步是将生成的目标文件链接成可执行文件:
[david@DAVID david]$ gcc hello.o -o hello
在采用模块化的设计思想进行软件开发时,通常整个程序是由多个源文件组成的,相应地就形成了多个编译单元,使用gcc能够很好地管理这些编译单元。假设有一个由david.c和xueer.c两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序davidxueer,可以使用下面这条命令:
[david@DAVID david]$ gcc david.c xueer.c -o davidxueer
如果同时处理的文件不止一个,gcc仍然会按照预处理、编译和链接的过程依次进行。如果深究起来,上面这条命令大致相当于依次执行如下3条命令:
[david@DAVID david]$ gcc david.c -o david.o
[david@DAVID david]$ gcc xueer.c -o xueer.o
[david@DAVID david]$ gcc david.o xueer.o -o davidxueer
在编译一个包含许多源文件的工程时,若只用一条gcc命令来完成编译是非常浪费时间的。假设项目中有100个源文件需要编译,并且每个源文件中都包含10 000行代码,如果像上面那样仅用一条gcc命令来完成编译工作,那么gcc需要将每个源文件都重新编译一遍,然后再全部链接起来。很显然,这样浪费的时间相当多,尤其是当用户只是修改了其中某一个文件的时候,完全没有必要将每个文件都重新编译一遍,因为很多已经生成的目标文件是不会改变的。要解决这个问题,关键是要灵活运用gcc,同时还要借助像make这样的工具。关于make,将在第5章作详细的介绍。
3.3 gcc警告提示功能
gcc包含完整的出错检查和警告提示功能,它们可以帮助Linux程序员尽快找到错误代码,从而写出更加专业和优美的代码。先来读读例3-2所示的程序,这段代码写得很糟糕,仔细检查一下不难挑出如下毛病:
●       main函数的返回值被声明为void,但实际上应该是int;
●       使用了GNU语法扩展,即使用long long来声明64位整数,仍不符合ANSI/ISO C语言标准;
●       main函数在终止前没有调用return语句。
实例3-2 bad.c­­­­­­­­­­­­­­­­­­­­­­­­­­­­




#include
void main(void)
{
 long long int var = 1;
 printf("It is not standard C code!\n");
}
下面看看gcc是如何帮助程序员来发现这些错误的。当gcc在编译不符合ANSI/ISO C语言标准的源代码时,如果加上了-pedantic选项,那么使用了扩展语法的地方将产生相应的警告信息:
[david@DAVID david]$ gcc -pedantic bad.c -o bad
bad.c: In function 'main':
bad.c:4: warning: ISO C89 does not support 'long long'
bad.c:3: warning: return type of 'main' is not 'int'
需要注意的是,-pedantic编译选项并不能保证被编译程序与ANSI/ISO C标准的完全兼容,它仅仅用来帮助Linux程序员离这个目标越来越近。换句话说,-pedantic选项能够帮助程序员发现一些不符合ANSI/ISO C标准的代码,但不是全部。事实上只有ANSI/ISO C语言标准中要求进行编译器诊断的那些问题才有可能被gcc发现并提出警告。
除了-pedantic之外,gcc还有一些其他编译选项也能够产生有用的警告信息。这些选项大多以-W开头,其中最有价值的当数-Wall了,使用它能够使gcc产生尽可能多的警告信息。例如:
[david@DAVID david]$ gcc -Wall bad.c -o bad
bad.c:3: warning: return type of 'main' is not 'int'
bad.c: In function 'main':
bad.c:4: warning: unused variable 'var'
bad.c:6:2: warning: no newline at end of file
gcc给出的警告信息虽然从严格意义上说不能算作是错误,但很可能成为错误的栖身之所。一个优秀的Linux程序员应该尽量避免产生警告信息,使自己的代码始终保持简洁、优美和健壮的特性。
    在处理警告方面,另一个常用的编译选项是-Werror,它要求gcc将所有的警告当成错误进行处理,这在使用自动编译工具(如make等)时非常有用。如果编译时带上-Werror选项,那么gcc会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改。只有当相应的警告信息消除时,才可能将编译过程继续朝前推进。执行情况如下:
[david@DAVID david]$ gcc -Werror bad.c -o bad
cc1: warnings being treated as errors
bad.c: In function 'main':
bad.c:3: warning: return type of 'main' is not 'int'
bad.c:6:2: no newline at end of file
对Linux程序员来讲,gcc给出的警告信息是很有价值的,它们不仅可以帮助程序员写出更加健壮的程序,而且还是跟踪和调试程序的有力工具。建议在用gcc编译源代码时始终带上-Wall选项,并把它逐渐培养成为一种习惯,这对找出常见的隐式编程错误很有帮助。
3.4 库 依 赖
在Linux下使用C语言开发应用程序时,完全不使用第三方函数库的情况是比较少见的,通常来讲都需要借助一个或多个函数库的支持才能够完成相应的功能。从程序员的角度看,函数库实际上就是一些头文件(.h)和库文件(.so或者.a)的集合。虽然Linux下大多数函数都默认将头文件放到/usr/include/目录下,而库文件则放到/usr/lib/目录下,但并不是所有的情况都是这样。正因如此,gcc在编译时必须让编译器知道如何来查找所需要的头文件和库文件。
gcc采用搜索目录的办法来查找所需要的文件,-I选项可以向gcc的头文件搜索路径中添加新的目录。例如,如果在/home/david/include/目录下有编译时所需要的头文件,为了让gcc能够顺利地找到它们,就可以使用-I选项:
[david@DAVID david]$ gcc david.c -I /home/david/include -o david
同样,如果使用了不在标准位置的库文件,那么可以通过-L选项向gcc的库文件搜索路径中添加新的目录。例如,如果在/home/david/lib/目录下有链接时所需要的库文件libdavid.so,为了让gcc能够顺利地找到它,可以使用下面的命令:
[david@DAVID david]$ gcc david.c -L /home/david/lib –ldavid -o david
值得详细解释一下的是-l选项,它指示gcc去连接库文件david.so。Linux下的库文件在命名时有一个约定,那就是应该以lib三个字母开头。由于所有的库文件都遵循了同样的规范,因此在用-l选项指定链接的库文件名时可以省去lib三个字母。也就是说gcc在对-l david进行处理时,会自动去链接名为libdavid.so的文件。
Linux下的库文件分为两大类,分别是动态链接库(通常以.so结尾)和静态链接库(通常以.a结尾),两者的差别仅在于程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。默认情况下,gcc在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库。如果需要的话可以在编译时加上-static选项,强制使用静态链接库。例如,如果在/home/david/lib/目录下有链接时所需要的库文件libfoo.so和libfoo.a,为了让gcc在链接时只用到静态链接库,可以使用下面的命令:
[david@DAVID david]$ gcc foo.c -L /home/david/lib -static –ldavid -o
david
3.5 gcc代码优化
代码优化指的是编译器通过分析源代码,找出其中尚未达到最优的部分,然后对其重新进行组合,目的是改善程序的执行性能。gcc提供的代码优化功能非常强大,它通过编译选项-On来控制优化代码的生成,其中n是一个代表优化级别的整数。对于不同版本的gcc来讲,n的取值范围及其对应的优化效果可能并不完全相同,比较典型的范围是从0变化到2或3。
编译时使用选项-O可以告诉gcc同时减小代码的长度和执行时间,其效果等价于-O1。在这一级别上能够进行的优化类型虽然取决于目标处理器,但一般都会包括线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化。
选项-O2告诉gcc除了完成所有-O1级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等。
选项-O3则除了完成所有-O2级别的优化之外,还包括循环展开和其他一些与处理器特性相关的优化工作。
通常来说,数字越大优化的等级越高,同时也就意味着程序的运行速度越快。许多Linux程序员都喜欢使用-O2选项,因为它在优化长度、编译时间和代码大小之间取得了一个比较理想的平衡点。
下面通过具体实例来感受一下gcc的代码优化功能,所用程序如例3-3所示。
实例3-3 count.c­­­­­­­­­­­­­­­­­­­­­­­­­­­­




#include
 int main(void)
{ double counter;
   double result;
   double temp;
   for (counter = 0; counter < 4000.0 * 4000.0 * 4000.0 / 20.0 + 2030;
counter += (5 - 3 +2 + 1 ) / 4)
     { temp = counter / 1239;
        result = counter;
       }
                printf("Result is %lf\n", result);
                return 0;
}
首先不加任何优化选项进行编译:
[david@DAVID david]$ gcc -Wall count.c -o count
借助Linux提供的time命令,可以大致统计出该程序在运行时所需要的时间:
[david@DAVID david]$ time ./count
Result is 3200002029.000000
real    1m59.357s
user    1m59.140s
sys     0m0.050s
接下来使用优化选项来对代码进行优化处理:
[david@DAVID david]$ gcc -Wall count.c -o count2
在同样的条件下再次测试一下运行时间:
[david@DAVID david]$ time ./count2
Result is 3200002029.000000
real    0m26.573s
user    0m26.540s
sys     0m0.010s
对比两次执行的输出结果不难看出,程序的性能的确得到了很大幅度的改善,由原来的1分59秒缩短到了26秒。这个例子是专门针对gcc的优化功能而设计的,因此优化前后程序的执行速度发生了很大的改变。尽管gcc的代码优化功能非常强大,但作为一名优秀的Linux程序员,首先还是要力求能够手工编写出高质量的代码。如果编写的代码简短,并且逻辑性强,编译器就不会做更多的工作,甚至根本用不着优化。
优化虽然能够给程序带来更好的执行性能,但在如下一些场合中应该避免优化代码。
●       程序开发的时候:优化等级越高,消耗在编译上的时间就越长,因此在开发的时候最好不要使用优化选项,只有到软件发行或开发结束的时候,才考虑对最终生成的代码进行优化。
●       资源受限的时候:一些优化选项会增加可执行代码的体积,如果程序在运行时能够申请到的内存资源非常紧张(如一些实时嵌入式设备),那就不要对代码进行优化,因为由这带来的负面影响可能会产生非常严重的后果。
●       跟踪调试的时候:在对代码进行优化的时候,某些代码可能会被删除或改写,或者为了取得更佳的性能而进行重组,从而使跟踪和调试变得异常困难。
3.6 加    速
在将源代码变成可执行文件的过程中,需要经过许多中间步骤,包含预处理、编译、汇编和连接。这些过程实际上是由不同的程序负责完成的。大多数情况下gcc可以为Linux程序员完成所有的后台工作,自动调用相应程序进行处理。
这样做有一个很明显的缺点,就是gcc在处理每一个源文件时,最终都需要生成好几个临时文件才能完成相应的工作,从而无形中导致处理速度变慢。例如,gcc在处理一个源文件时,可能需要一个临时文件来保存预处理的输出,一个临时文件来保存编译器的输出,一个临时文件来保存汇编器的输出,而读写这些临时文件显然需要耗费一定的时间。当软件项目变得非常庞大的时候,花费在这上面的代价可能会变得很大。
解决的办法是,使用Linux提供的一种更加高效的通信方式——管道。它可以用来同时连接两个程序,其中一个程序的输出将直接作为另一个程序的输入,这样就可以避免使用临时文件,但编译时却需要消耗更多的内存。
注意:
在编译过程中使用管道是由gcc的-pipe选项决定的。下面的这条命令就是借助gcc的管道功能来提高编译速度的:
[david@DAVID david]$ gcc -pipe david.c -o david
在编译小型工程时使用管道,编译时间上的差异可能还不是很明显,但在源代码非常多的大型工程中,差异将变得非常明显。
3.7 gcc常用选项
gcc作为Linux下C/C++重要的编译环境,功能强大,编译选项繁多。为了方便大家日后编译方便,在此将常用的选项及说明罗列出来,见表3-2。
表3-2 gcc的常用选项
选 项 名 作    用
-c 通知gcc取消连接步骤,即编译源码并在最后生成目标文件
-Dmacro 定义指定的宏,使它能够通过源码中的#ifdef进行检验
-E 不经过编译预处理程序的输出而输送至标准输出
-g3 获得有关调试程序的详细信息,它不能与-o选项联合使用
-Idirectory 在包含文件搜索路径的起点处添加指定目录
-llibrary 提示连接程序在创建最终可执行文件时包含指定的库
-O、-O2、-O3 将优化状态打开,该选项不能与-g选项联合使用
-S 要求编译程序生成来自源代码的汇编程序输出
-v 启动所有警报
.h 预处理文件(标头文件)
-Wall 在发生警报时取消编译操作,即将警报看作是错误
-w 禁止所有的报警


3.8 gcc的错误类型及对策
如果gcc编译器发现源程序中有错误,就无法继续进行,也无法生成最终的可执行文件。为了便于修改,gcc给出错误信息,必须对这些错误信息逐个进行分析、处理,并修改相应的源代码,才能保证源代码的正确编译连接。.gcc给出的错误信息一般可以分为四大类,下面我们分别讨论其产生的原因和对策。
●       第一类:C语法错误
错误信息:文件source.c中第n行有语法错误(syntex errror)。这种类型的错误,一般都是C语言的语法错误,应该仔细检查源代码文件中第n行及该行之前的程序,有时也需要对该文件所包含的头文件进行检查。有些情况下,一个很简单的语法错误,gcc会给出一大堆错误,我们最主要的是要保持清醒的头脑,不要被其吓倒,必要的时候再参考一下C语言的基本教材。在这里推荐一本由Andrew Koenig写的《C 陷阱与缺陷》(此书已由人民邮电出版社翻译出版),说得夸张一点就是此书可以帮助你减少C代码和初级C++代码中的90%的bug。
●       第二类:头文件错误
错误信息:找不到头文件head.h(Can not find include file head.h)。这类错误是源代码文件中包含的头文件有问题,可能的原因有头文件名错误、指定的头文件所在目录名错误等,也可能是错误地使用了双引号和尖括号。
●       第三类:档案库错误
错误信息:连接程序找不到所需的函数库,例如:
ld: -lm: No such file or directory
这类错误是与目标文件相连接的函数库有错误,可能的原因是函数库名错误、指定的函数库所在目录名称错误等。检查的方法是使用find命令在可能的目录中寻找相应的函数库名,确定档案库及目录的名称并修改程序中及编译选项中的名称。
●       第四类:未定义符号
错误信息:有未定义的符号(Undefined symbol)。这类错误是在连接过程中出现的,可能有两种原因:一是用户自己定义的函数或者全局变量所在源代码文件,没有被编译、连接,或者干脆还没有定义,这需要用户根据实际情况修改源程序,给出全局变量或者函数的定义体;二是未定义的符号是一个标准的库函数,在源程序中使用了该库函数,而连接过程中还没有给定相应的函数库的名称,或者是该档案库的目录名称有问题,这时需要使用档案库维护命令ar检查我们需要的库函数到底位于哪一个函数库中,确定之后,修改gcc连接选项中的-l和-L项。
排除编译、连接过程中的错误,应该说只是程序设计中最简单、最基本的一个步骤,可以说只是开了个头。这个过程中的错误,只是我们在使用C语言描述一个算法中所产生的错误,是比较容易排除的。我们写一个程序,到编译、连接通过为止,应该说刚刚开始,程序在运行过程中所出现的问题,是算法设计有问题,说得严重点儿是对问题的认识和理解不够,还需要更加深入地测试、调试和修改。一个程序,稍为复杂的程序,往往要经过多次的编译、连接、测试和修改。 gcc是在Linux下开发程序时必须掌握的工具之一。
以上对gcc作了一个简要的介绍,主要讲述了如何使用gcc编译程序、产生警告信息、和加快gcc的编译速度。对所有希望早日跨入Linux开发者行列的人来说,gcc就是成为一名优秀的Linux程序员的起跑线。关于调试 C 程序的更多信息请看第4章关于gdb的内容。