Linux 程序设计(应用篇)

申明:整理自《Linux程序设计(3)

 二、Linux程序设计 

    许多人认为Linux程序设计就是用C语言编程。的确,UNIX最初是用C语言编写的,UNIX的大多数应用程序也是用C语言编写的,但C语言并不是Linux程序员或UNIX程序员的唯一选择。 

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

   以下介绍几种其他的选择:

          Ada                C                C++      

          Eiffel             Forth            Fortran 

          Icon               Java             Javascript_

          Lisp               Modula 2         Modula 3 

          Oberon             Objective C      Pascal 

          Perl               Postscript_       Prolog 

          Python             Scheme           Smalltalk

          SQL                Tcl/Tk           Bourne Shell       

1. Linux程序 

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

   Linux并不要求可执行程序或脚本具有特殊的文件名或扩展名。在Linux中,我们可以用编译过的程序代替脚本(反之亦然)而不影响其他程序或调用者。事实上,在用户级别,这两者本质上没有任何不同。 

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

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

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

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

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

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

   注意,LinuxUNIX一样,使用冒号(:)分隔PATH变量里的条目,而不是像MS-DOSWindows使用分号(;)。下面是一个PATH变量的例子: 

   /usr/local/bin:/bin:/usr/bin:.:/home/neil/bin:/usr/X11R6/bin 

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

2. C语言编译器 

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

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

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

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

    实验:第一个Linux C语言程序

    最有名的HelloWorld程序:

    (1)代码略。

    需要用一个编辑器来输入这个程序。在典型的Linux系统上有许多编辑器可用,较流行的编辑器是vi。还有就是emacs,建议你花一点时间来学习这个功能强大的编辑器的一些特性。要更深入地学习emacs,则可以使用它的在线指南。首先运行emacs命令以启动编辑器,然后输入Ctrl+H,接着输入字母t就进入了在线指南。emacs也有完整的用户手册。在emacs中输入Ctrl+H,接着输入字母i即可以得到相关信息。有些版本的emacs可能包含菜单,那就可以通过它访问手册和指南。

   (2)编译、链接和运行我们的程序。
      #gcc -o hello hello.c
      #./hello
 
   实验解析
 
   调用GNU C语言编译器(在Linux中大多数情况下用cc也可以)将我们的C语言源代码转换为可执行文件hello。运行这个程序,打印出欢迎信息。虽然这只是最简单的一个例子,但如果在你的系统上能做到这一点,就能编译、运行本书中以后所有的例子了。如果无法完成上述操作,请检查你的系统以确保已安装了C语言编译器。例如,许多Linux发行版有个名为“Software Development(软件开发)”的安装选项,你应该在Linux系统安装过程中选中该项。
 
   因为这是我们运行的第一个程序,所以有些问题最好现在就指出来。hello程序很可能在你的主目录中。如果PATH变量不包含指向你的主目录的条目,shell就找不到hello程序。更进一步,如果PATH变量中的目录包含另一个名为hello的程序,就会执行那个程序。如果PATH中这样的目录出现在你的主目录之前,也会发生这种情况。为了避免这种潜在的问题,我们在程序名前加上一个./(例如./hello)。它特别指示shell去执行当前目录下给定名称的程序。
 
   如果你忘记用-o name选项告诉编译器将可执行程序放在何处,编译器则会把程序放在一个名为a.out的文件里(a.out的含义是assembler output,即汇编输出)。如果你确信编译了一个程序但又找不到它,别忘了看看有没有a.out文件!在UNIX的早期历史中,想在系统上玩游戏的人通常把游戏作为a.out来运行,以避免被系统管理员捉到,一些UNIX系统安装每晚会定期地删除所有名为a.out的文件。
 
3. 开发系统导引
 
   对Linux开发人员来说,了解软件工具和开发资源在系统中存放的位置是很重要的。以下将对一些重要的目录和文件做简单的介绍。
 
   (1). 应用程序
 
    应用程序通常存放在系统为之保留的特定目录中。系统为正常使用提供的程序,包括用于程序开发的工具,都可在目录/usr/bin中找到;系统管理员为某个特定的主机或本地网络添加的程序通常可在目录/usr/local/bin或/opt中找到。
 
   系统管理员一般喜欢使用/usr/local目录,因为它分离了厂商提供及后续添加的文件与系统本身提供的应用程序。保持/usr目录的这种组织方法的好处是:当你需要升级操作系统时,只需保留目录/usr/local里的内容。我们建议你在/usr/local目录结构下编译、运行自己的程序,并访问必需的文件。
 
   其他一些功能特性和编程系统都可能有其自己的目录结构和程序目录。其中最主要的就是X视窗系统,它通常安装在/usr/X11目录下,可选的安装位置还有用于修订版第6版的/usr/X11R6目录,XFree社团发布的用于Intel处理器芯片的各种XFree86变体也安装在这里,许多Linux发行版也用这个目录安装X视窗系统。其他类UNIX系统可能选择不同的位置,比如Solaris提供的Sun开放视窗系统就安装在/usr/openwin目录里。
 
   GNU编译系统的驱动程序gcc(在本章前面的编程示例中用过)一般位于/usr/bin或/usr/local/bin目录中,但它会从其他位置运行各种编译器支持的应用程序。这个位置是在编译编译器本身时指定的,并且随主机类型的不同而不同。对Linux系统来说,这个位置可能是/usr/lib/gcc-lib/目录下的一个版本特定的子目录。撰写本书时,在作者的一台机器上的这个目录是/usr/lib/gcc-lib/i486-suse-linux/3.3/.,GNU C/C++编译器的各个工具和GNU专用的头文件都保存在这里。
 
   (2). 头文件
 
   用C语言及其他语言进行程序设计时,我们要用头文件来提供对常量的定义和对系统及库函数调用的声明。对C语言来说,这些头文件几乎总是在/usr/include目录及其子目录下。那些依赖于特定Linux版本的头文件通常可在目录/usr/include/sys和/usr/include/linux中找到。
 
   其他编程系统也有各自的include文件,并将其存储在可被相应编译器自动搜索到的目录里。例如,X视窗系统的/usr/include/X11目录和GNU C++的/usr/include/g++目录。
 
   调用C语言编译器时,我们可以使用-I标志来包含保存在子目录或非标准位置中的include文件。例如:
   #gcc -I/usr/openwin/include fred.c
 
   它指示编译器不仅在标准位置,也在/usr/openwin/include目录中查找程序fred.c中包含的头文件。请参看C语言编译器的使用手册(man gcc)以了解更多细节。
 
   用grep命令来搜索包含某些特定定义和函数原型的头文件是很方便的。假设我们想知道用来返回程序退出状态的#define定义的名字,就简单地切换到/usr/include目录下,然后用grep命令搜索可能的名字部分,如下所示:
   #grep EXIT_ *.h
 
   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标志来指示编译器要搜索的库文件。例如:
   #gcc -o fred fred.c /usr/lib/libm.a
 
   这条命令指示编译器编译文件fred.c,将编译产生的程序文件命名为fred,并且除搜索标准的C语言函数库外,还搜索数学库以解决函数引用问题。下面的命令也能产生类似的结果:
   #gcc -o fred fred.c –lm
 
   -lm(在字母l和m之间没有空格)是简写方式(简写在UNIX环境里很有用),它代表的是标准库目录(本例中是/usr/lib)中名为libm.a的函数库。-lm标志的另一个好处是如果有共享库,编译器会自动选择共享库。
 
   虽然库文件和头文件一样,通常都保存在标准位置,但我们也可以通过-L(大写字母)标志为编译器增加库的搜索路径。例如:
   #gcc -o x11fred -L/usr/openwin/lib x11fred.c -lX11
 
   这条命令用/usr/openwin/lib目录中的libX11库版本来编译和链接程序x11fred。
 
   (4). 静态库
 
   函数库最简单的形式是一组处于“准备好使用”状态的目标文件。当程序需要使用函数库中的某个函数时,它包含一个声明该函数的头文件。编译器和链接器负责将程序代码和函数库结合在一起以组成一个单独的可执行文件。我们必须用-l选项指明除标准C语言运行库外还需使用的库。
 
   静态库,也称作归档文件(archive),按惯例它们的文件名都以.a结尾。比如,标准C语言函数库/usr/lib/libc.a和X11函数库/usr/X11/lib/libX11.a。
 
   我们可以容易地创建和维护自己的静态库,只要使用ar(代表archive,即建立归档文件)程序和gcc
–c命令对函数分别进行编译。我们应该尽可能把函数分别保存到不同的源文件中。如果函数需要访问公共数据,我们则把它们放在同一个源文件中并使用在该文件中声明的静态变量。
 
   实验:静态库
 
   下面来创建一个小型函数库,它包含两个函数,然后在一个示例程序中调用其中一个函数。这两个函数分别是fred和bill,它们只打印欢迎信息。
 
   (1)首先,为两个函数分别创建各自的源文件(将它们分别命名为fred.c和bill.c)。下面是第一个源文件:
   #include <stdio.h>
   
   void fred(int arg)
   {
      printf("fred: you passed %d/n",arg);
   }
  
   下面是第二个源文件:
   #include <stdio.h>
 
   void bill(char *arg)
   {
      printf("bill: you passed %s/n",arg);
   }
 
   (2)分别编译这些函数,产生要包含在库文件中的目标文件。这通过调用带有-c选项的C 语言编译器来实现,-c选项的作用是阻止编译器创建一个完整的程序。如果此时试图创建一个完整的程序将不会成功,因为我们还未定义main函数。
 
   #gcc -c bill.c fred.c
   #ls *.o
   bill.o fred.o
 
   (3)现在编写一个调用bill函数的程序。首先,为我们的库文件创建一个头文件。这个头文件将声明我们的库文件中的函数,它应该被所有希望使用我们的库文件的应用程序所包含。把头文件包含在源文件fred.c和bill.c中是有益的,它将帮助编译器发现所有错误。
   /* This is lib.h. It declare the functions fred and bill for users */
   void fred(int)
   void bill(char *)
 
   (4)调用程序(program.c)非常简单。它包含库的头文件并且调用库中的一个函数。
   #include "lib.h"
  
   int main()
   {
      bill("Hello World!");
      exit(0);
   }
 
   (5)现在,我们来编译并测试这个程序。我们暂时为编译器显式指定目标文件,然后要求编译器编译我们的文件并将其与预先编译好的目标模块bill.o链接。
   #cgcc -c program.c
   #cgcc -o program program.o bill.o
   #./program
   bill: We passed Hello World!
 
   (6)现在,创建并使用一个库文件。我们用ar程序创建一个归档文件并将目标文件添加进去。这个程序之所以称为ar,是因为它将若干单独的文件归并到一个大的文件中以创建归档文件或集合。注意,我们也可以用ar程序来创建任何类型文件的归档文件(与许多UNIX工具一样,ar是一个通用工具)。
   #ar crv libfoo.a bill.o fred.o
   a-bill.o
   a-fred.o
 
   (7)库文件创建好了,两个目标文件也已添加。在某些系统,尤其是从Berkeley UNIX衍生的系统中,要想成功地使用函数库,还需要为函数库生成一个内容表。我们通过ranlib命令来完成这一工作。在Linux中,如果我们使用的是GNU的软件开发工具,这一步就不是必需的(但做了也无妨)。
   #ranlib libfoo.a
 
   我们的函数库现在即可使用了。我们可以在编译器命令行的文件列表中添加该库文件以创建我们的程序,如下所示:
   #gcc -o program program.o libfoo.a
   #./program
   bill: We passed Hello World!
 
   也可以用-l选项来访问我们的函数库,但是因为其未保存在标准位置,所以我们必须用-L选项来指示编译器在何处可以找到它,如下所示:
   #gcc -o program program.o -L. -lfoo
 
   -L.选项指示编译器在当前目录(.)中查找函数库。-lfoo选项指示编译器使用名为libfoo.a的函数库(或者名为libfoo.so的共享库,如果它存在的话)。
 
   要查看目标文件、函数库或可执行文件里包含的函数,我们可使用nm命令。如果我们查看program和libfoo.a,就会看到函数库libfoo.a中包含fred和bill两个函数,而program里只包含函数bill。创建程序时,它只包含函数库中它实际需要的函数。虽然程序中的头文件包含函数库中所有函数的声明,但这并不将整个函数库包含在最终的程序中。
 
   (5). 共享库
 
   静态库的一个缺点是,当我们同时运行许多应用程序并且它们都使用来自同一个函数库的函数时,就会在内存中有同一函数的多份拷贝,在程序文件自身中也有多份同样的拷贝。这将消耗大量宝贵的内存和磁盘空间。
  
   许多UNIX系统和Linux支持共享库,它克服了上述不足。对共享库及其在不同系统上实现方式的详细讨论超出了本书的范围,所以我们将仅讨论Linux下的实现。
 
   共享库的保存位置与静态库是一样的,但共享库有不同的文件名后缀。在典型的Linux系统中,标准数学库的共享版本是/usr/lib/libm.so。
 
   程序使用共享库时,它的链接方式是这样的:它本身不再包含函数代码,而是引用运行时可访问的共享代码。当编译好的程序被装载到内存中执行时,函数引用被解析并产生对共享库的调用,如果有必要共享库才被加载到内存中。
 
   通过这种方法,系统可只保留共享库的一份拷贝并供许多应用程序同时使用,并且在磁盘上也仅保存一份。另一个好处是共享库的更新可以独立于依赖它的应用程序。文件/usr/lib/libm.so是对实际库文件修订版本(/usr/lib/libm.so.N,其中N代表主版本号,在写作本书时它是6)的符号链接。Linux启动应用程序时,它会考虑应用程序需要的函数库版本,以防止新的主版本函数库致使旧的应用程序不能使用。
 
   对Linux系统来说,负责装载共享库并解析客户程序函数引用的程序(动态装载器)是ld.so,也可能是ld-linux.so.2或ld-lsb.so.1。搜索共享库的其他位置可以在文件/etc/ld.so.conf中配置,如果修改了这个文件,就需要用命令ldconfig来处理(例如,安装了X视窗系统后需要添加X11共享库)。
 
   我们可通过运行工具ldd来查看程序需要的共享库。
  
   在例子中,我们看到标准C语言函数库(libc)是共享的(.so)。我们的程序要求使用的主版本号是6。其他UNIX系统在访问共享库时也会有类似的安排,详情请参考系统文档。
  
   共享库在许多方面类似于Windows中使用的动态链接库。.so库对应于.DLL文件,在程序运行时加载,而.a库类似于.LIB文件,包含在可执行程序中。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值