Linux程序设计(第4版)

Linux程序设计(第4版)最新章节目录
上QQ阅读APP,阅读体验更流畅

1.2 Linux程序设计

许多人认为Linux程序设计就是用C语言编程。的确,UNIX最初是用C语言编写的,并且UNIX的大多数应用程序也是用C语言编写的,但C语言并不是Linux程序员或UNIX程序员的唯一选择。在本书中,我们将介绍几种其他的选择。

事实上,UNIX的第一个版本是在1969年用PDP 7机器的汇编语言编写的。差不多也在那个时候,Dennis Ritchie发明了C语言,并于1973年与Ken Thompson一起用C语言重写了整个UNIX内核,这在那个连系统软件都是用汇编语言编写的时代的确是个了不起的壮举。

对Linux系统来说,有各种各样的编程语言可供选用,其中许多是免费的,它们可以通过CD-ROM光盘获得或在因特网上通过FTP站点下载。表1-1列出了Linux程序员可用的部分编程语言。

1-1

我们将在第2章向读者显示如何用Linux的shell(bash)来开发小规模到中等规模的应用程序。在本书的其他章节,我们主要集中在C语言上。我们将集中精力从C语言程序员的视角探究Linux编程接口。我们假设读者具备C语言编程的基础。

1.2.1 Linux程序

Linux应用程序表现为两种特殊类型的文件:可执行文件脚本文件。可执行文件是计算机可以直接运行的程序,它们相当于Windows中的.exe文件。脚本文件是一组指令的集合,这些指令将由另一个程序(即解释器)来执行,它们相当于Windows中的.bat文件、.cmd文件或解释执行的BASIC程序。

Linux并不要求可执行文件或脚本文件具有特殊的文件名或扩展名。文件系统属性(我们将在第2章中讨论)用来指明一个文件是否为可执行的程序。在Linux中,你可以用编译过的程序代替脚本(反之亦然)而不会影响其他程序或调用者。事实上,在用户级别,这两者本质上没有任何不同。

当登录进Linux系统时,你与一个shell程序(通常是bash)进行交互,它像Windows中的命令提示窗口一样运行程序。它在一组指定的目录路径下按照你给出的程序名搜索与之同名的文件。搜索的目录路径存储在shell变量PATH里,这一点与Windows也很类似。搜索路径(你也可以添加这个路径)由系统管理员配置,它通常包含如下一些存储系统程序的标准路径。

❑ /bin:二进制文件目录,用于存放启动系统时用到的程序。

❑ /usr/bin:用户二进制文件目录,用于存放用户使用的标准程序。

❑ /usr/local/bin:本地二进制文件目录,用于存放软件安装的程序。

系统管理员(例如root用户)登录后使用的PATH变量可能还包含存放系统管理程序的目录,如/sbin和/usr/sbin。

可选的操作系统组件和第三方应用程序可能被安装在/opt目录下,安装程序可以通过用户安装脚本将路径添加到PATH环境变量中。

从PATH中删除目录并不是一个好主意,除非确信你了解这么做的后果。

注意,Linux像UNIX一样,使用冒号(:)分隔PATH变量里的条目,而不是像MS-DOS和Windows使用分号(;)。(UNIX使用冒号:在先,所以应该问微软为什么Windows要采用不同的方式,而不是问UNIX为什么与之不同!)下面是一个PATH变量的例子:

上面的PATH变量包含的条目有:标准程序存放位置、当前目录(.)、一个用户的家目录和X视窗系统的目录。


记住,Linux用正斜线(/)分隔文件名里的目录名,而不是像Windows那样用反斜线(\)。而且这次还是UNIX的用法在先。

1.2.2 文本编辑器

编写和输入本书中的代码需要使用一个编辑器。在典型的Linux系统上有许多编辑器可用,较流行的编辑器是vi。

本书的两位作者都喜欢Emacs,所以我们建议你花一点时间来学习这个功能强大的编辑器。几乎所有的Linux发行版都将Emacs作为可选的安装包,你也可以从GNU网站http://www.gnu.org上得到它,或者在XEmacs网站http://www.xemacs.org上得到它的图形化环境版本。

要更深入地学习Emacs,你可以使用它的在线指南。首先运行emacs命令以启动编辑器,然后输入Ctrl+H,接着输入字母t就进入了在线指南。Emacs也有完整的用户手册。在Emacs中输入Ctrl+H,接着输入字母i即可以得到相关信息。有些版本的Emacs可能包含访问手册和指南的菜单。

1.2.3 C语言编译器

在POSIX兼容的系统中,C语言编译器被称为c89。历史上,C语言编译器被简称为cc。许多年来,不同厂商销售的类UNIX系统中所带的C语言编译器均包含不同的功能和选项,但它们一般都称为cc。

在准备起草POSIX标准时,事实上已经不可能制订出兼容所有厂商的标准cc命令了。于是,POSIX委员会决定为C语言编译器创建新的标准命令,这就是c89。只要使用这个命令,在任何机器上,它的编译选项都相同。

Linux系统尽量实现这些标准。在Linux系统中,你会发现c89、cc和gcc这些命令全部或部分地指向系统的C语言编译器,通常是GNU C编译器,或gcc。在UNIX系统中,C语言编译器几乎总被称为cc。

在本书中,我们将使用gcc,这是因为它随Linux的发行版一起提供,并且它支持C语言的ANSI标准语法。如果你发现你的UNIX系统中没有gcc,我们建议你设法获取并安装它。你可以在http://www.gnu.org上找到它。我们在本书中用到gcc之处,你都可以直接将其替换为你的系统中C语言编译器相应的命令。

实验 你的第一个Linux C语言程序

在本例中,通过编写、编译和运行你的第一个Linux程序来开始Linux的C语言程序开发之旅。还是从最有名的Hello World程序开始吧。

(1)下面是文件hello.c的源代码:

(2)编译、链接和运行程序。

实验解析

你调用GNU C语言编译器(在Linux中,大多数情况下用cc也可以)将C语言源代码转换为可执行文件hello。然后运行这个程序,它将打印出欢迎信息。虽然这只是最简单的一个例子,但如果在你的系统上能做到这一点,你就能编译、运行本书中以后所有的例子了。如果无法完成上述操作,请检查你的系统以确保已安装了C语言编译器。例如,许多Linux发行版有个名为Software Development(软件开发)的安装选项(或类似选项),你应该在Linux系统安装过程中选中该项,从而确保安装了所需的软件包。

因为这是你运行的第一个程序,所以有些问题最好现在就指出来。hello程序很可能在你的家目录中。如果PATH变量不包含指向你的家目录的条目,shell就找不到hello程序。更进一步,如果PATH变量中包含的其中一个目录包含另一个名为hello的程序,shell就会执行那个程序。如果PATH中这样的目录出现在你的家目录之前,这种情况也会发生。为了避免这种潜在的问题,你可以在程序名前加上一个./(例如./hello)。它特别指示shell去执行当前目录下给定名称的程序。(符号.代表当前目录。)

如果你忘记用-o name选项告诉编译器可执行程序的名字,编译器就会把程序放在一个名为a.out的文件里(a.out的含义是assembler output,即汇编输出)。如果你确信编译了一个程序但又找不到它,别忘了看看有没有a.out文件!在UNIX的早期历史中,想在系统上玩游戏的人通常把游戏作为a.out来运行,以避免被系统管理员捉到,因此一些UNIX系统每晚会定期地删除所有名为a.out的文件。

1.2.4 开发系统导引

对Linux开发人员来说,了解软件工具和开发资源在系统中存放的位置是很重要的。以下几节将简单介绍一些重要的目录和文件。

1.应用程序

应用程序通常存放在系统为之保留的特定目录中。系统为正常使用提供的程序,包括用于程序开发的工具,都可在目录/usr/bin中找到;系统管理员为某个特定的主机或本地网络添加的程序通常可在目录/usr/local/bin或/opt中找到。

系统管理员一般喜欢使用/opt和/usr/local目录,因为它们分离了厂商提供及后续添加的文件与系统本身提供的应用程序。一直保持以这种方式组织文件的好处在你需要升级操作系统时就可以看出来了,因为只有目录/opt和/usr/local里的内容需要保留。我们建议对于系统级的应用程序,你可以将它放在/usr/local目录中来运行和访问所需的文件。对于开发用和个人的应用程序,最好在你的家目录中使用一个文件夹来存放它。

其他一些功能特性和编程系统可能有其自己的目录结构和程序目录。其中最主要的一个就是X视窗系统,它通常安装在/usr/X11或/usr/bin/X11目录中。Linux发行版通常使用X视窗系统的X.Org基金会版本,它基于修订版7(X11R7)。其他类UNIX系统可能选择X视窗系统的其他版本,它们被安装到不同的位置,如Solaris提供的Sun Open Windows被安装到/usr/openwin目录中。

GNU编译系统的驱动程序gcc(你已在本章前面的编程示例中用过)一般位于/usr/bin或/usr/local/bin目录中,但它会从其他位置运行各种编译器支持的应用程序。这个位置是在编译编译器本身时指定的,并且它随主机类型的不同而不同。对Linux系统来说,这个位置可能是/usr/lib/gcc/目录下的一个版本特定的子目录。在撰写本书时,这个目录在本书其中一位作者的机器上是/usr/lib/gcc/i586-suse-linux/4.1.3。GNU C/C++编译器的各个工具和GNU专用的头文件都保存在这里。

2.头文件

用C语言及其他语言进行程序设计时,你需要用头文件来提供对常量的定义和对系统函数及库函数调用的声明。对C语言来说,这些头文件几乎总是位于/usr/include目录及其子目录中。那些依赖于特定Linux版本的头文件通常可在目录/usr/include/sys和/usr/include/linux中找到。

其他编程系统也有各自的头文件,这些头文件被存储在可被相应编译器自动搜索到的目录里。例如,X视窗系统的/usr/include/X11目录和GNU C++的/usr/include/c++目录。

在调用C语言编译器时,你可以使用-I标志来包含保存在子目录或非标准位置中的头文件。例如:

它指示编译器不仅在标准位置,也在/usr/openwin/include目录中查找程序fred.c中包含的头文件。请参看C语言编译器的使用手册(man gcc)以了解更多细节。

用grep命令来搜索包含某些特定定义和函数原型的头文件是很方便的。假设想知道用于从程序中返回退出状态的#define定义的名字,你只需切换到/usr/include目录下,然后用grep命令搜索可能的名字部分,如下所示:

上面的grep命令在当前目录下的所有以.h结尾的文件中搜索字符串EXIT_。在本例中,它在stdlib.h文件中找到了你需要的定义。

3.库文件

是一组预先编译好的函数的集合,这些函数都是按照可重用的原则编写的。它们通常由一组相互关联的函数组成以执行某项常见的任务,比如屏幕处理函数库(curses和ncurses库)和数据库访问例程(dbm库)。我们将在后续的章节中介绍一些函数库。

标准系统库文件一般存储在/lib和/usr/lib目录中。C语言编译器(或更确切地说是链接程序)需要知道要搜索哪些库文件,因为在默认情况下,它只搜索标准C语言库。这是从那个计算机速度还很慢而且CPU运行周期还很昂贵的时代遗留下来的问题。仅把库文件放在标准目录中,就希望编译器能够找到它是不够的,库文件必须遵循特定的命名规范并且需要在命令行中明确指定。

库文件的名字总是以lib开头,随后的部分指明这是什么库(例如,c代表C语言库,m代表数学库)。文件名的最后部分以.开始,然后给出库文件的类型:

❑ .a代表传统的静态函数库;

❑ .so代表共享函数库(见后面的解释)。

函数库通常同时以静态库和共享库两种格式存在,你可用ls /usr/lib命令查看。你可以通过给出完整的库文件路径名或用-l标志来告诉编译器要搜索的库文件。例如:

这条命令要求编译器编译文件fred.c,将编译产生的程序文件命名为fred,并且除了搜索标准的C语言函数库外,还搜索数学库以解决函数引用问题。下面的命令也能产生类似的结果:

-lm(在字母l和m之间没有空格)是简写方式(简写在UNIX环境里很有用),它代表的是标准库目录(本例中是/usr/lib)中名为libm.a的函数库。-lm标志的另一个好处是如果有共享库,编译器会自动选择共享库。

虽然库文件和头文件一样,通常都保存在标准位置,但你也可以通过使用-L(大写字母)标志为编译器增加库的搜索路径。例如:

这条命令用/usr/openwin/lib目录中的libX11库版本来编译和链接程序x11fred。

4.静态库

函数库最简单的形式是一组处于“准备好使用”状态的目标文件。当程序需要使用函数库中的某个函数时,它包含一个声明该函数的头文件。编译器和链接器负责将程序代码和函数库结合在一起以组成一个单独的可执行文件。你必须使用-l选项指明除标准C语言运行库外还需使用的库。

静态库,也称作归档文件(archive),按惯例它们的文件名都以.a结尾。比如,标准C语言函数库/usr/lib/libc.a和X11函数库/usr/lib/libX11.a。

你可以很容易地创建和维护自己的静态库,只要使用ar(代表archive,即建立归档文件)程序和使用gcc -c命令对函数分别进行编译。你应该尽可能把函数分别保存到不同的源文件中。如果函数需要访问公共数据,你可以把它们放在同一个源文件中,并使用在该文件中声明的静态变量。

实验 静态库

在本例中,你将创建一个小型函数库,它包含两个函数,然后你将在一个示例程序中调用其中一个函数。这两个函数分别是fred和bill,它们只打印欢迎信息。

(1)首先,为两个函数分别创建各自的源文件(将它们分别命名为fred.c和bill.c)。下面是第一个源文件:

下面是第二个源文件:

(2)你可以分别编译这些函数以产生要包含在库文件中的目标文件。这可以通过调用带有-c选项的C语言编译器来完成,-c选项的作用是阻止编译器创建一个完整的程序。如果此时试图创建一个完整的程序将不会成功,因为你还未定义main函数。

(3)现在编写一个调用bill函数的程序。首先,为你的库文件创建一个头文件。这个头文件将声明你的库文件中的函数,它应该被所有希望使用你的库文件的应用程序所包含。把这个头文件包含在源文件fred.c和bill.c中是一个好主意,它将帮助编译器发现所有错误。

(4)调用程序(program.c)非常简单。它包含库的头文件并且调用库中的一个函数。

(5)现在,你可以编译并测试这个程序了。你暂时为编译器显式指定目标文件,然后要求编译器编译你的文件并将其与先前编译好的目标模块bill.o链接。

(6)现在,你将创建并使用一个库文件。你使用ar程序创建一个归档文件并将你的目标文件添加进去。这个程序之所以称为ar,是因为它将若干单独的文件归并到一个大的文件中以创建归档文件或集合。注意,你也可以用ar程序来创建任何类型文件的归档文件(与许多UNIX工具一样,ar是一个通用工具)。

(7)库文件创建好了,两个目标文件也已添加进去。在某些系统,尤其是从Berkeley UNIX衍生的系统中,要想成功地使用函数库,你还需要为函数库生成一个内容表。你可以通过ranlib命令来完成这一工作。在Linux中,当你使用的是GNU的软件开发工具时,这一步骤并不是必需的(但做了也无妨)。

你的函数库现在可以使用了。你可以在编译器使用的文件列表中添加该库文件以创建你的程序,如下所示:

你也可以使用-l选项来访问函数库,但因其未保存在标准位置,所以你必须使用-L选项来告诉编译器在何处可以找到它,如下所示:

-L.选项告诉编译器在当前目录(.)中查找函数库。-lfoo选项告诉编译器使用名为libfoo.a的函数库(或者名为libfoo.so的共享库,如果它存在的话)。要查看哪些函数被包含在目标文件、函数库或可执行文件里,你可以使用nm命令。如果你查看program和libfoo.a,你就会看到函数库libfoo.a中包含fred和bill两个函数,而program里只包含函数bill。当程序被创建时,它只包含函数库中它实际需要的函数。虽然程序中的头文件包含函数库中所有函数的声明,但这并不会将整个函数库包含在最终的程序中。

如果你熟悉Windows软件开发,就会发现两者之间有许多相似之处,如表1-2所示。

表1-2

5.共享库

静态库的一个缺点是,当你同时运行许多应用程序并且它们都使用来自同一个函数库的函数时,内存中就会有同一函数的多份副本,而且在程序文件自身中也有多份同样的副本。这将消耗大量宝贵的内存和磁盘空间。

许多支持共享库的UNIX系统和Linux可以克服上述不足。对共享库及其在不同系统上实现方式的详细讨论超出了本书的范围,所以我们将仅讨论Linux下的实现。

共享库的保存位置与静态库是一样的,但共享库有不同的文件名后缀。在一个典型的Linux系统中,标准数学库的共享版本是/usr/lib/libm.so。

当一个程序使用共享库时,它的链接方式是这样的:程序本身不再包含函数代码,而是引用运行时可访问的共享代码。当编译好的程序被装载到内存中执行时,函数引用被解析并产生对共享库的调用,如果有必要,共享库才被加载到内存中。

通过这种方法,系统可以只保留共享库的一份副本供许多应用程序同时使用,并且在磁盘上也仅保存一份。另一个好处是共享库的更新可以独立于依赖它的应用程序。例如,文件/lib/libm.so就是对实际库文件修订版本(/lib/libm.so.N,其中N代表主版本号,在写作本书时它是6)的符号链接。当Linux启动应用程序时,它会考虑应用程序需要的函数库版本,以防止函数库的新版本致使旧的应用程序不能使用。

下面例子的输出取自SUSE 10.3发行版。如果你使用的不是这个发行版,输出可能略有不同。

对Linux系统来说,负责装载共享库并解析客户程序函数引用的程序(动态装载器)是ld.so,也可能是ld-linux.so.2、ld-lsb.so.2或ld-lsb.so.3。用于搜索共享库的额外位置可以在文件/etc/ld.so.conf中配置,如果修改了这个文件,你需要执行命令ldconfig来处理它(例如,安装了X视窗系统后需要添加X11共享库)。

你可以通过运行工具ldd来查看一个程序需要的共享库。例如,如果你在自己的示例程序上运行ldd,你将看到如下所示的输出结果:

在本例中,你看到标准C语言函数库(libc)是共享的(.so)。程序需要的主版本号是6。其他UNIX系统在访问共享库时也会有类似的安排,详情请参考你的系统文档。

共享库在许多方面类似于Windows中使用的动态链接库。.so库对应于.DLL文件,它们都是在程序运行时加载,而.a库类似于.LIB文件,它们都包含在可执行程序中。

(英)Neil Matthew Richard Stones
作家的话
去QQ阅读支持我
还可在评论区与我互动