荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: jjk (prepare for the future), 信区: Linux
标  题: Advanced Linux Programming(中译版第一章) (zz)
发信站: 荔园晨风BBS站 (Mon Jan 14 19:49:19 2002), 转信

【 以下文字转载自 jjk 的信箱 】
【 原文由 jjk.bbs@apue.dhs.org 所发表 】
发信人: guru (王阿呆~~花房姑娘), 信区: APUE
标  题: Advanced Linux Programming(中译版第一章) (zz)
发信站: UNIX编程 (2002年01月14日18:14:19 星期一), 转信

发信人: zsll (四级), 信区: Linux
标  题: Advanced Linux Programming(中译版第一章) (zz)
发信站: 华南网木棉站 (Fri Jan  4 22:06:09 2002), 转信


                                Linux高级编程
                        ----Advanced Linux Programming

作者: Mark Mitchell, Jeffrey Oldham and Alex Samuel

译者:zsll

只是简单翻译了一下,而且省略了一点,打算假期才继续。如果谁想分担一下,请通知我
,还有,如果谁发现有中文版出现的,请通知我,以免重复劳动,另外,我也不用看的这
么辛苦嘛.

第一章 开始(Get start)
1.1(略)本节是有关vi,Emacs的使用,我想应该没必要译出来吧,因为有很多关于介绍他
们的资料了,而且,我们还可以用其他X界面下的编辑器,所以,我们还是快点进入主题
吧:
1.2 用GCC编译程序
编译器是把人们可以阅读的代码翻译为机器可读可执行的代码。Linux选用的编译器是G
NU Compiler Collection(GNU编译器集),人们经常称为GCC。GCC能编译的语言有:c,
c++,JAVA,Object-C,Fortran,还有Chill。本书主要集中讲述C和C++编程。
        假如你有一个工程(Project):Listing 1.2所示的C++代码reciprocal.cpp,C代码

ai
n.c如Listing1.1所示。要把这两个文件编译和连接在一起,组成一个可执行文件recip
rocal。这个可执行文件实现计算一个整型数(integer)的倒数(reciprocal).
______________________________________________
Listing 1.1 (main.c) C source file-main.c
#include <stdio.h>
#include "reciprocal.hpp"
int main (int argc, char **argv)
{
int i;
i = atoi (argv[1]);
printf ("The reciprocal of %d is %g\n", i, reciprocal (i));
return 0;
}
Listing 1.2 (reciprocal.cpp) C++ source file-reciprocal.cpp
#include <cassert>
#include "reciprocal.hpp"
double reciprocal (int i) {
// I should be non-zero.
assert (i != 0);
return 1.0/i;
}
这里还有一个叫reciprocal.hpp的头文件(看 Lising1.3)。
Listing 1.3 (reciprocal.hpp) Header file-reciprocal.hpp
#ifdef __cplusplus
extern "C" {
#endif
extern double reciprocal (int i);
#ifdef __cplusplus
}
#endif
编译的第一步是把源文件编译成目标代码(Object Code)。
1.2.1 编译单个源文件
        C编译器的名字就叫gcc。要编译C程序,你要用-c命令参数。例如,输入以下命令?

main.c编译:
                % gcc -c main.c
编译产生的结果是一个目标文件main.o。
        C++编译器的名字叫g++。他的操作非常类似gcc,例如,我们编译reciprocal.cpp?

用以下命令:
                % g++ -c reciprocal.cpp
我们来看看-c的作用,-c参数是告诉g++只需要把源程序编译成目标代码。没有-c,g++
会尝试连接代码而产生可执行文件(executable)。当你用这个-c参数,你将会得到一个
叫reciprocal.o的目标程序。
        你可能要用到另外一些参数去编译一些大程序。-I参数用来告诉GCC哪里可以找到?

件。在默认的情况下,GCC会在当前目录下查找,或者到已安装的标准头文件库里面查找
。如果你有在其它目录的头文件,那么你就需要这个-I参数了。例如,假设你的项目 (
Project)有一个叫src的目录,另外有一个叫include的目录存放头文件。而你编译reci
procal.cpp是要告诉g++你要用到../include目录里面的头文件reciprocal.hpp,你应该
这样做:
        % g++ -c -I ../include/ reciprocal.cpp
有时候,你可能会想在命令行定义一个宏。例如,在编译正式程序时,你不想reciproc
al.cpp开始的那个声明检查(assertion check)再出现,因为那只是在调试的时候才有用
的。你可以定义一个宏NDEBUG来关掉这个检查。你可以在reciprocal.cpp直接加上#def
ine。但是这样会改变了原程序。其实你可以非常容易的在命令行定义这个NDEBUG的:
        % g++ -c -D NDEBUG reciprocal.cpp
 如果你想定义NDEBUG为特定的值(如3),你可以这样做:
        % g++ -c -D NDEBUG=3 reciprocal.cpp
如果你真正编译一个产品代码(production cold),你可能想利用GCC优化自己的的代码,
使运行速度尽可能快。你可以用-O2参数做到。(GCC有不同的代码优化级别;第二级别
对大部分程序是合适的)例如,对reciprocal.cpp可优化编译如下:
% g++ -c -O2 reciprocal.cpp
你必须知道的是,优化过的代码会比没优化过的较难用调试器调试(详细请看1.4节的"
用GDB进行调试")。而且,优化编译能使一些Bug暴露出来,因为这些Bug在普通编译里
是没有的。
同过联机帮助,你将会知道更多关于gcc和g++的参数使你的代码更好的被编译。你可以
用下面的命令查看联机文档:
% info gcc
1.2.2 连接目标文件(Linking Object Files)
        在你编译了main.c和utilities.cpp后,你下一步应该连接(link)他们。你必须总?

g++连接含有C++的代码,即使它同时含有C代码。如果你的代码只含有C,那你可以用gc
c代替。因为上面的项目含有C和C++代码,所以你必须用g++连接:
% g++ -o reciprocal main.o reciprocal.o
这个-o选项用来给出连接后产生的文件名。现在你可以运行reciprocal了:
% ./reciprocal 7
7的倒数用小数形式表示为:0.142857
由上你可以看到,g++自动帮你连接了标准的C运行库(含有你上代码中用到的printf函
数)。如果你需要到另外的运行库(如图形接口工具包),你必须用-I参数手工指出其路径
。Linux中,库的名称总是用lib开头的。例如:Pluggable Authentication Module(PA
M)库被称作libpam.a(谁能告诉我这个库用来干嘛?)。你要在连接时用到这个库的话,
要这样:
% g++ -o reciprocal main.o reciprocal.o -lpam
这样,编译器会自动加上库前缀lib,和库后缀.a。
        当要用到头文件时,连接器会到一些标准的路径去找,如/lib和/usr/lib这样一些

?
由标准系统库的文件夹。如果你想要编译器搜索特定的地点,你就要用到-L参数,这个
参数要在-I参数之前。你可以用下面的命令去告诉连接器去/usr/local/lib/pam去寻找
它需要的库:
% g++ -o reciprocal main.o reciprocal.o -L /usr/local/lib/pam -lpam
你不一定要用-I去使预处理程序(preprocessor)搜索当前文件夹,但你必须要用-L参
数去使连接器搜索当前文件夹。特别地,你可以用到下面地命令去告诉编译器在当前文
件夹找test(libtest.a)这个库:
% gcc -o app app.o -L. -ltest
1.3 利用GNU Make去自动化编译过程(Automating the Process with GNU Make)
        如果你习惯于在windows操作系统下面编程,你一定对于集成开发环境(IDE,
Integ
at
ed Development Environment)非常熟悉。利用IDE,你在你的项目里加入源程序,IDE
就会自动帮你编译连接好一切。虽然在Linux下,IDE也是可用的,但本书中,我们不讨
论他。但是,本书会告诉你怎么用GNU Make去自动重新编译你的程序,实际上,大部分
linux下的编程人员都是这样做的。
        Make的基本原理(basic idea)非常简单。你要自动重复编译这个程序时,告诉make?

编译的目标文件和编译规则是什么(如何编译他),同时,你还要指出文件间的依赖关
系。
        在reciprocal这个简单的项目例子里,有三个对象(这是显而易见的):
reciproc
l.
o,main.o,和reciprocal自己。你可能已经想好用什么命令去编译他们了。但依赖关系还
需要想一想。很清楚,reciprocal依赖于reciprocal.o和main.o,因为如果你不预先编
译好这两个目标文件,你是无法完成这个程序的连接的。而目标文件在其源代码改变后
,必须重新编译。在本例中,如果reciprocal.hpp改变了的话,两个目标文件都要重新
编译!因为这两个源文件(main.c和reiprocal.c)都要包含(include)reciprocal.hpp
这个头文件。
        一个显见对象(obvious target),必须是一个干净的对象(clean target)。这个

韵?
删掉所有中间目标文件,使你可以干干净净的(重新)开始(你的程序)。可以用rm命
令删掉这些文件。
        你可以传送所有的这些信息到make,只要你把这一切放在一个叫Makefile的文件里

U?
是一个Makefile的例子:
reciprocal: main.o reciprocal.o
        g++ $(CFLAGS) -o reciprocal.main.o reciprocal.o
main.o: main.c reciprocal.hpp
        gcc $(CFLAGS) -c main.c
reciprocal.o: reciprocal.cpp reciprocal.hpp
clean:
        -rm -f *.o reciprocal
可以看到,目标文件写在最左边,然后是一个冒号,冒号右边是一些依赖的文件。编译
的规则在下一行。(现在请先不要管$(CFLAGS)是什么东东)这一行里,你必须用Tab键腾
出一个制表符间隔,否则,make会拒绝编译的。如果你用Emacs(就方便了),它会帮你
自动格式化的。
        如果你把已经编译好的目标文件(在说一次,就是.o文件)删除,只需要在命令行

?:

% make
你就可以看到:
%make
gcc -c main.c
g++ -c reciprocal.cpp
g++ -o reciprocal main.o reciprocal.o
你可以看到,make已经帮你自动编译了!(包括连接!)
如果你改变了main.c,然后再make,你可以看到:
% make
gcc -c main.c
g++ -o reciprocal main.o reciprocal.o
你可以看到,make可以自动找出那些文件是改过,然后重新编译它们,再连接。Recipr
ocal.cpp不需要重新编译,这是因为,reciprocal.o所需要的源文件全部没改动(包括头
文件)。
我们回头来看看$(CFLAGS),他是一个变量,你可以在Makefile或在命令行定义它。GNU
 make会在执行编译规则时替换它们。例如,当你想重新优化编译上面的project时(就
是用优化选项-O2啦),你可以这样做:
% make clean
rm -f *.o reciprocal
% make CFLAGS=-O2
gcc -02 -c main.c
g++ -o2 -c reciprocal.cpp
g++ -o2 -c reciprocal main.o reciprocal.o
你应该注意到,-o2标志被插入到$(CFLAGS)里了。
        在这里为止,你已经知道了这些关于make的最重要和最基本的东西了。如果你想得

礁?
多的信息,可以打:
% info make
本书中,你会看到更多的信息:如何更容易地运用Makefile,如何减少你需要写的规则
数目,如何自动地计算依赖。你还能得到关于以下信息:GNU,Autoconf,Automake,和Li
btool(分别by Gary V.Vaughan, Ben Elliston,Tom Tromey, and Ian Lance Taylor (
New Riders Publishing, 2000)).
1.4 用GNU Debugger(GDB)调试文件(Debugging with GNU Debugger)
        调试工具(debugger)是用来帮你找出你的程序哪里出了问题的工具。你将会经常

?
试程序的。GDB时大多数Linux程序员采用的调试工具。你可以用GDB使你的程序单步执行
,设置断点,和实时监视你的程序的变量。
1.4.1 编译调试信息(Compiling with Debugging Information)
        要用GDB调试你的程序,你必须使调试信息可用。你要在编译时加上-g选项。如果?

先写好了Makefile的话,你只需给CFLAGS赋上-g值即可:
% make CFLAGS=-g
gcc -g -c main.c
g++ -g -c reciprocal.cpp
g++ -g -o reciprocal main.o reciprocal.o
当你用-g参数编译好你地程序后,编译器会给你地程序加入额外地调试信息。GDB会凭这
些调试信息去找出当前执行的地址,如何打印出本地变量,等等。
1.4.2 运行GDB(Running GDB)
你可以用如下命令运行gdb:
%gdb reciprocal
当gdb运行后,你可以看到如下提示(prompt):
(gdb)
        运行gdb后,你要做地第一步是如何在gdb里运行你地程序。只需输入run命令和任?

件参数。下面地例子是运行一个程序,不带任何参数:
(gdb) run
Starting program:reciprocal
Program received signal SIGSEGV, Segmentation fault.
__strtol_internal (nptr=0x0, endptr=0x0, base=10, group=0)
at strtol.c:287
287 strtol.c: No such file or directory.
(gdb)
问题在于main里面没有错误检测的代码。程序应该有一个参数(argument),但是这个程
序运行时没有收到参数。SIGSEGV消息表示程序崩溃了。GDB找出崩溃处在一个叫__strt
ol_internal的函数里。这个函数在标准函数库里,而这个原文件没有安装,所以又有"
No such file or directory"这个消息。你可以用where命令察看堆栈:
(gdb) where
#0 __strtol_internal (nptr=0x0, endptr=0x0, base=10, group=0)
at strtol.c:287
#1 0x40096fb6 in atoi (nptr=0x0) at ../stdlib/stdlib.h:251
#2 0x804863e in main (argc=1, argv=0xbffff5e4) at main.c:8
从输出可以看出,main呼叫的atoi函数中有一个NULL指针,这是源程序的错误。
你还可以继续察看堆栈里的内容,如你想继续向上查看堆栈的2个level(应该是两个存
储单元吧),你可以用up命令:
(gdb) up 2
#2 0x804863e in main (argc=1, argv=0xbffff5e4) at main.c:8
8 i = atoi (argv[1]);
注意,gdb能够查找整个main.c和指出错误的函数调用出现在那行。你可以用print命令
察看变量的值:
(gdb) print argv[1]
$2=0x0
That confirms that the problem is indeed a NULL pointer passed into atoi.
You can set a breakpoint by using the break command:
从这里可以确定,问题是出在atoi的一个NULL指针.
你可以用break来设置一个断点:
(gdb) break main
Breakpoint 1 at 0x804862e: file main.c, line 8.
这个命令在main的首行设置了一个断点。现在你可以试着重新运行这个程序并给予参数

(gdb) run 7
Starting program: reciprocal 7
Breakpoint 1, main (argc=2, argv=0xbffff5e4) at main.c:8
8 i = atoi (argv[1]);
你可以看到,程序停在了断点上。
你现在可以用next命令单步执行这个程序:
(gdb) next
9 printf ("The reciprocal of %d is %g\n", i, reciprocal (i));
如果你想看看reciprocal在搞什么,你可以用step命令:
(gdb) step
reciprocal (i=7) at reciprocal.cpp:6
6 assert (i != 0);
你现在处在reciprocal函数的函数体里了。
        如果你在Emacs里运行gdb,你会发现用起来比gdb命令行方便多了。你可以在
emacs

镉?
M-x gdb来启动gdb。如果你的程序在断点停住,Emacs会自动输出相关的源代码。这样你
可以更容易地找出你想找的出错地点了,是不是比命令行下方便多了?
1.5 Finding More Information
        这里我不译出来,说的都是关于查man page,info的,我想没有必要再说吧!赫赫.大?

谅哦!


--
四级急死四级急死四级急死
急死四级急死四级急死四级

※ 修改:.zsll 于 Jan  4 22:12:24 修改本文.[FROM: 202.38.249.212]

--
Target Locked:Guru In Darkness.
我只是一只静静卧着的狮子。。。
※ 来源:·UNIX编程 apue.dhs.org·[FROM: 202.114.36.201] --
※ 转寄:·UNIX编程 apue.dhs.org·[FROM: 202.119.32.102]
--
※ 转载:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.0.146]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店