The Linux GCC HOWTO中译版

作者: Daniel Barlow 
译者: 陈建勋(Frank J.S. Chen) 

v1.17, 28 February 1996 
_________________________________________________________________ 

本文阐述安装GNU C编译器和程式馆的方法,同时概观地说明程式的编译、连结、 
执行、除错的过程以及可能面临的诸多问题。写作的参考资料泰半来自於Mitch 
DSouza先生所收集的GCC-FAQ;而另一个来源是ELF-HOWTO。此份HOWTO可以说已 
代替了GCC-FAQ,而且即将要永久替代ELF-HOWTO了。此乃GCC-HOWTO第一份公开发 
行的版本(不须理会版本序号;那是RCS的杰作),有任何指正与建议的,本人都 
很欢迎。 
_________________________________________________________________ 

1. 行远必自迩! 

* 1.1 译者的话 
* 1.2 动与静 
* 1.3 作者的私语 
* 1.4 印刷与排版 

2. 东东在哪儿? 

* 2.1 GCC-HOWTO在哪儿? 
* 2.2 GCC相关的资料又在哪儿? 
* 2.3 GCC 
* 2.4 C程式馆与标头档 
* 2.5 有关联的工具 (as, ld, ar, strings etc) 

3. GCC的安装与GCC的设定 

* 3.1 GCC的版本 
* 3.2 东东装好後都到哪儿去了? 
* 3.3 标头档ㄋㄟ?标头档ㄋㄟ? 
* 3.4 建立交叉编译器(Building cross compilers) 

4. 移植程式与编译程式 

* 4.1 gcc自行定义的符号 
* 4.2 线上求助说明 
* 4.3 移植能力 

5. 除错与监管 

* 5.1 预防重於治疗(lint) 
* 5.2 除错 
* 5.3 监管 

6. 连结 

* 6.1 共享程式库 vs静态程式库 
* 6.2 终极审判(‘sin() 在哪个程式库里?’) 
* 6.3 X档案? 
* 6.4 建立你自己的程式库 

7. 动态载入 

* 7.1 基本概念 
* 7.2 错误讯息 
* 7.3 控制动态载入器的运作 
* 7.4 以动态载入撰写程式 

8. 与发展人士联络 

* 8.1 Bug报表 
* 8.2 协助发展 

9. 结语 

* 9.1 名人榜 
* 9.2 翻译 
* 9.3 欢迎任何的回馈 
* 9.4 合法的行迳规定 

10. 索引 
_________________________________________________________________ 

1. 行远必自迩! 

1.1 译者的话 

* 这份译文为Linux document projects(LDP)中文翻译计画系列之一。目前之 
网址为 [1]http://www.linux.org.tw/CLDP/,欢迎各位网友踊跃投入此一计 
画。 
* 我并没有完全按照原文逐字翻译。为了力求译文通畅可读,我会稍稍的重组 
一部份的文字,加油添醋,或是精简原文;这样做的话,可以弥补中英文间 
语法结构的差异性,且语气可以贯通无碍。 
* 一些关键字与专业词汇等,会附加上原文单字。 
* 遇有转译困难,唯原文常见的字汇如bugs、shadow password、padding 
、image之类的,则保留原文不变。若阁下对这些字汇有适当译辞的,请不吝 
指教。 
* 内文中若遇有"[译者注:**]"之标记,则为本人额外之注解。 
* 对这篇译文有任何建议与疑问的,请email至frank63@ms5.hinet.net。 
* WWW Home Page: [2]http://linux.ntcic.edu.tw/~jsfrank/。 
* 此中译文件之翻译权已取得英籍之原作者Daniel Barlow 先生之同意;另, 
陈建勋先生保有此份中译版文件所有的权利,你可以任意的拷贝,以各种媒 
体散布这份中译文件,唯此节补充说明需原封不动附上,且不可任意更动译 
文。 
* v0.1版的译文相当粗糙,连文句的语法结构都嫌太过於松散,v0.2版针对中 
文的用字习惯来修正,并将上一版译的不妥当的地方修正过来,例如either 
这个字,英国人常把either当名词用,指两者中任意一个;这份HOWTO就充份 
反应出这个用字习性,跟美语有基本上的差异。 
* 文中有几句话没有译出来,一方面是看不懂,另方面是直译也译不出来,所 
以只好保留原文了,要是阁下有新的领悟的,无论如何请告诉我。 
* v0.1版翻译起始日期为:11/7/97;截止日期为:11/19/97。 
* v0.2版修正起始日期:5/13/98;截止日期为:6/3/98。 

1.2 动与静 

目前Linux的发展正波涛汹涌的进行著。简单一点讲,Linux有两种执行档的格式 
可用,取决於你的系统是怎麽整合起来的;你的Linux应该是其中一种吧!阅读这 
份文件,可以帮助你□清执行档的类别。 

要如何区别呢?执行公用程式‘file’(例如,file /bin/bash)就对了。 
就ELF的程式而言,萤幕上显示出来的讯息会含有ELF的字眼;如果说是a.out的, 
讯息内会箝有 Linux/i386的字样。 

ELF与a.out格式的差异之处会在後续的章节中讨论(很广泛喔!)。ELF是比较新 
的格式,一般而言,接受的程度较佳。 

1.3 作者的私语 

版权说明与合法的行迳规定,就摆在这份文件的尾端。除此之外,我还有一些不 
得不提醒你的话要讲。就算你□著没事干,也不要在Usenet上丢一些呆瓜问的问 
题;还有,不要老以为自己C语言的功力深厚,专门发表一些不是bugs的bugs出来 
丢人现眼,这不就等於告诉别人你不学无术,在关老爷面前耍大刀了吗?所以说 
自以为是的英雄主义是得不偿失的。 

1.4 印刷与排版 

如果你现在读的是Postscript、dvi或是html格式,那麽你所看到的字型变化就会 
比只读纯文字格式的人多一些。特别是档案名称、命令、命令的输出与摘录出来 
的原始码等,统统都是打字机的字型。这样做的话,对於某些需要强调的‘变数 
’还有那些没有固定结果的□例而言,就可以达到强调的效果了。 

读这份文件的同时,你也会得到一套蛮有用的索引。假若是dvi、 postscript之 
类的版本,索引的数字就是章节(section)的编号;如果是HTML的话,这些数字会 
按顺序排列,你可以用滑鼠左键去连结相对的索引内容;如果你看的是纯文字版 
本的话, 数字就只是数字,没别的含意;建议你赶快升级为妙哩! 

我用的shell是Bourne shell(不是C shell),举的例子自然是Bourne shell的 
语法。如果你用的是C shell的话,设定环境变数的语法会像下面这样: 

% setenv FOO bar 

要是用Bourne shell的话,我会这样子写: 

$ FOO=bar; export FOO 

如果提示符号显示的是井字符号#而不是钱字符号 $的话,很有可能这个命令是只 
适用於root而已。当然啦,要是你试了这些□例,结果弄得你的系统发生灾变, 
我可是一点责任也不会负的喔!祝好运! 

11/8/97译. 5/13/98修订 

2. 东东在哪儿? 

2.1 GCC-HOWTO在哪儿? 

这份文件是Linux HOWTO系列之一,换句话说,你可以在所有存放Linux HOWTO文 
件的网站上面找到她的芳踪,例如 
[3]http://sunsite.unc.edu/pub/linux/docs/HOWTO/。HTML的版本(可能会是较 
新的版本)可以从 
[4]http://ftp.linux.org.uk/~barlow/howto/gcc-howto.html上面抓下来。 

2.2 GCC相关的资料又在哪儿? 

标准的gcc说明文件是随附在发行的原始码(source distribution)内(往下看就 
有了!),里头有textinfo与.info两种档案。要是你的网路连接速率够快,或是 
有一片cdrom,不然,有高度的耐心也成,你可以自己把它untar,然後再把相对 
应的位元一一拷贝到/usr/info的目录底下。假如你的条件与上述的不符,不妨到 
[5]tsx-11站上去参观参观。不过,我想,没有必要老是惦记著最新的版本吧! 

libc的文件说明有两种来源。一种是GNU libc,以.info的格式储存,除了stdio 
之外,其馀Linux libc的说明都相当的详尽精确。另一种可以在Linux的archive 
[6]manpages上找到系统呼叫(system call)(第2节)与libc函数(function) 
(第3节)的文件说明。 

2.3 GCC 

解答有二: 

(a)你可以在 [7]ftp://tsx-11.mit.edu:/pub/linux/packages/GCC/的网站上找 
到 正式的Linux GCC发行系统(distribution),而且是已经编译好的可执行档。 
当我在写这份文件时,2.7.2(gcc-2.7.2.bin.tar.gz)是最新的版本。 

(b)自由软体基金会(Free Software Foundation)所发布的GCC最新原始码可以 
从网站 [8]GNU archives上取得。没有必要非得与上述的版本一致才行,不过这 
个版本的确是目前最新的。Linux GCC的维护网友(maintainers)让你可以很轻松 
的自行编译这个最新的版本。configure命令稿(script)会帮你自动设定好所有该 
做的事情。建议你有空不妨到 [9]tsx-11看看,说不定会有修正的版本是你会想 
要用的。 

如果想要编写出一些有用的软体(不是我罗唆,还是有不少没啥用途的软体在网 
路上四处流窜。),下面这一小节所谈的也是你需要的: 

2.4 C程式馆与标头档 

该选哪一套程式馆是取决於(i)你的系统是ELF的或是a.out的;(ii)你希望你的系 
统变成哪一种?如果你是从libc 4升级到libc 5,那麽给你一个良心的建议,先 
去看看ELF-HOWTO再说。你一定会问,在ELF文件的哪儿呢?嘿!嘿!不偏不倚, 
就差不多跟这份文件相同的位置。网站 [10]tsx-11上面可以找到你想要的。 

libc-5.2.18.bin.tar.gz 
--- ELF共享程式馆(ELF shared library images),静态程式馆 
(static libraries)与标头档(针对C语言与数学程式馆)。 

libc-5.2.18.tar.gz 
---libc-5.2.18.bin.tar.gz的原始码。这个档案你也需要,因为.bin.套 
件(package)含有必需的标头档。如果此时你正犹豫不决,不晓得是老身 
亲自下海,动手编译C程式库比较好;还是直接用人家编译好的二进位 
档(binaries)就可以了。有这种困扰的人,来,看我的嘴形:用人家编译 
好的二进位档不就解决了嘛!只有在你想要NYS或是shadow password的情 
况下,才需要自己的手来推动摇篮。 

libc-4.7.5.bin.tar.gz 
--- 这个档案是a.out的共享程式库(shared library images)与静态程式 
库,用途是为了与前述的libc 5套件共存共荣而设计的,不过除非你想要 
继续使用a.out的程式或是继续发展a.out的程式,不然的话,是不需要它 
的。 

2.5 有关联的工具 (as, ld, ar, strings etc) 

到目前为止,与之前所谈的都一样,从网站 [11]tsx-11上,就可以找到这些工具 
程式。目前的版本是binutils-2.6.0.2.bin.tar.gz。 

需要注意的是binutils只适用於ELF,而且目前libc的版本也都是属於ELF的;当 
然啦,习惯a.out的人如果有个ELF的libc与a.out的libc联合起来一起使用,这对 
他们来讲是再好不过的美事了。不可否认的,C程式馆的发展正以坚决的脚步迈 
向ELF的格式,除非你真的有很好的理由,需要a.out的东东,不然啊,大家都会 
鼓励你勇於突破,趁早加入锐不可挡的大潮流。 

11/9/97译 

3. GCC的安装与GCC的设定 

3.1 GCC的版本 

在shell的提示符号下键入gcc -v,萤幕上就会显示出你目前正在使用的GCC的版 
本。同时这也是一个相当可靠的方法,可以确定你现在所用的是ELF或是a.out。 
在我的系统上,执行gcc -v的结果是: 

$ gcc -v 
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs 
gcc version 2.7.2 

上面的讯息指出了几件重要的事情: 
* i486 这是指明你现在正在用的gcc是为了486的微处理器而写的---你的电脑 
可能是386或是586。这3种微处理器的晶片所编译而成的程式码,彼此间是可 
以相容使用的。差别之处是486的程式码在某些地方有加上padding的功能, 
所以可以在486上面跑得比较快。这对386的机器而言,执行程式的效能并不 
会有什麽不良的影响,只不过真的会让程式码变得稍稍的大了一些。 
* box 这可以说一点也不重要;不过也可能另有所指(像是slackware或者 
是debian),或者根本什麽也不是(所以罗!完整的目录名称是i486-linux 
)。假如你是实务派的佼佼者,亲自动手建立属於自己的gcc,那麽你可以在 
建立的过程中设定这一项,以装点门面。就像我做的一样。 
* linux 其实这是指linuxelf或是linuxaout。这一项会令人引起不必要的困惑 
,究竟是指哪一种会根据你所用的版本而异。 
+ linux 意指ELF若版本序号是2.7.0.(或是更新的版本);否则的话, 
就是a.out的了。 
+ linuxaout 意指a.out的格式。当linux的定义从a.out更换到ELF时 
,linuxaout就会顺水推舟,摇身一变,成了一个目标物。因此,你不 
会看到任何版本新於2.7.0.的gcc有linuxaout的。 
+ linuxelf 已经过时了。通常这是指2.6.3版的gcc,而且这个版本也可 
以用来产生ELF的可执行档。要注意的是,gcc 2.6.3版在产生ELF程式 
码时会有bugs,所以如果你目前用的恰好是这个版本,建议你赶快升级 
。 
* 2.7.2 版本的序号。 

所以,总结起来,我有2.7.2版的gcc,可以产生ELF格式的程式码。就这麽简单, 
惊讶吧!eh? 

3.2 东东装好後都到哪儿去了? 

如果安装gcc时没有仔细的看著萤幕,或者你是从一个完整的发行系统里把gcc单 
独抓出来安装的话,那麽也许你会想知道到底这些东东装好後是住在整个档案系 
统的那些地方。几个重点如下: 

* /usr/lib/gcc-lib/target/version/ (与子目录)大部份的编译器就是住在 
这个地方的。在这儿有可执行的程式,实际在做编译的工作;另外,还有一 
些特定版本的程式库与标头档等也会储存在此。 
* /usr/bin/gcc 指的是编译器的驱动程式---也就是你实际在命令列(command 
line)上执行的程式。这个目录可供各种版本的gcc使用,只要你用不同的编 
译器目录(如上所述)来安装就可以了。要知道内定的版本是那一个, 
在shell提示符号下打gcc -v。要是想强迫执行某个版本,就换打gcc -V 
version。例如: 

# gcc -v 
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs 
gcc version 2.7.2 
# gcc -V 2.6.3 -v 
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.6.3/specs 
gcc driver version 2.7.2 executing gcc version 2.6.3 

* /usr/target/(bin|lib|include)/ 如果你装了数种的目标物件,例如a.out 
与elf,或是某一种的交叉编译器(cross-compiler)等等,那些属於非主流目 
标物件(non-native target(s))的程式库,binutils(as、ld等等)工具 
与标头档等都可以在这儿找到。即使你只安装了一种gcc,还是可以在这儿找 
到这些原本就是替它们准备的东东。如果不是在这儿,那麽就应该是 
在/usr/(bin|lib|include)了。 
* /lib/,/usr/lib 与其它的目录等都是主流系统(native-system)的程式馆 
目录。许多的应用程式都会用到/lib/cpp,因此你也需要它---作法上,不是 
从/usr/lib/gcc-lib/target/version/ 目录里拷贝,就是弄个符号连结 
(symlink)指向那儿。 [译者注:所谓的native,是指目前你的系统是 
以a.out或elf的格式为主,或者内定的gcc是哪一种版本等等。native的意思 
是‘本土的’、‘本国的’与‘天生的’……等等;当你拿到一片CD-ROM重 
头至尾将Linux安装完成,让Linux出生,成为你个人特色浓厚的作业平台後 
,如果再加装一些不一样的目标物件,自然就有‘本土’与‘外省’( 无关 
政治),‘本国’与‘外国’、‘天生’与‘人为’等等的区别,同时也含 
有内定(default)的意思在。假若再附加上你个人的价值观判断和喜好,我 
想用主流(native)与非主流(non-native)来翻译应该还算恰当。] 

3.3 标头档ㄋㄟ?标头档ㄋㄟ? 

假如把你自行安装在/usr/local/include目录底下的标头档排除在外的话 
,Linux还有另外3种主要的标头档: 

* /usr/include/与其子目录底下的标头档,大部份都是由H.J.Lu发展的libc套 
件(libc binary package)所提供的。我会只说‘大部份’的原因,是因为你 
可能有其它来源的标头档(像是curses与dbm程式库等等)摆在这儿;尤其是 
,如果你现在用的是最新的libc发行系统的话(新版本不像旧版那样,已经 
不再支援curses或dbm了。),那东东之多是人人为之咋舌的! [译者 
注:libc binary package意指以二进位形式(machine code)储存之套件,并 
非原始码(text),若要以中文全称译出,则成‘libc二进位档套件’,似 
有聱牙之嫌,故略去binary,以libc套件通称。] 
* 在核心原始码的发行系统内(kernel source distribution) 
,/usr/include/linux 与 /usr/include/asm (里头有这些档案 
: 与 )应该有符号连结(symbolic links)可以连 
结至目录linux/include/linux 与 linux/include/asm。如果你有鸿鹄之志 
的话,安装这些东东後,就不应该只是拿来编译核心(kernel)而已。 把原 
始码解压缩(unpacking)後,可能你也会发现,需要在核心的目录 
(kernel directory)底下做make config的动作。很多的档案都会依 
赖的帮忙,可是这个档案却有可能因版本不同而不存在 
。若干核心版本里,asm就只是它自己的一个符号连结,仅仅是在make 
config时才建立出来而已。 [译者注:原文提及autoconf.h时是‘Many 
files depend on ,which otherwise may not exist,* 
’。此处之otherwise之词性应为形容词(adj),指‘另一情况’、‘另一种 
’、‘不同的’之意,将原文形容词子句拆开来应为:(i) Many files 
depend on . (ii) of other 
condition may not exist. 与下一句互相比对,此处应同指在不同版本之情 
况下。] 所以,当你在目录/usr/src/linux底下,解开核心的程式码时,就 
照著下面指示的做吧! 

$ cd /usr/src/linux 
$ su 
# make config 
(回答接下来的问题。通常回答得正不正确并不重要,除非你打算继续□造核心。) 
# cd /usr/include 
# ln -s ../src/linux/include/linux . 
# ln -s ../src/linux/include/asm . 

* 诸如、、、 与之类 
的档案,会随著不同的编译器版本而异,属於你‘个人的’档案,可以在 
/usr/lib/gcc-lib/i486-box-linux/2.7.2/include/与其它相类似(相同) 
的目录名称的地方找到。 11/11/97译 5/14/98修正 

3.4 建立交叉编译器(Building cross compilers) 

将Linux当作标的作业平台(target platform) 

假设你已经拿到gcc的原始码,通常你只要依循INSTALL档的指示便可完成一切的 
设定。 make後面再接configure --target=i486-linux --host=XXX on 
platform XXX,就能帮你变把戏了。要注意的是,你会需要Linux还有核心的标头 
档;同时也需要建立交叉组译器(cross assembler)与交叉连结器(cross 
linker),来源是 [12]ftp://tsx-11.mit.edu/pub/linux/packages/GCC/。 

Linux当成原始作业平台(source platform)而MSDOS作为标的作业平台 

Ugh。很明显的,这个大概需要用到套件“emx”或是延伸套件“go”。请自行去 
[13]ftp://sunsite.unc.edu/pub/Linux/devel/msdos看看。我并没有测试过这些 
个东西,所以没有办法保证什麽。 

4. 移植程式与编译程式 

4.1 gcc自行定义的符号 

只要执行gcc时,附加 -v这个参数,就能找出你所用的这版gcc,自动帮你定义了 
什麽符号。例如,我的机器看起来会像这样: 

$ echo main(){printf("hello world/n";} | gcc -E -v - 
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs 
gcc version 2.7.2 
/usr/lib/gcc-lib/i486-box-linux/2.7.2/cpp -lang-c -v -undef 
-D__GNUC__=2 -D__GNUC_MINOR__=7 -D__ELF__ -Dunix -Di386 -Dlinux 
-D__ELF__ -D__unix__ -D__i386__ -D__linux__ -D__unix -D__i386 
-D__linux -Asystem(unix) -Asystem(posix) -Acpu(i386) 
-Amachine(i386) -D__i486__ - 

假若你正在写的程式码会用到一些Linux独有的特性,那麽把那些无法移植的程式 
码,以条件式编译的前置命令封括起来,可是个不错的主意呢!如下所示∶ 

#ifdef __linux__ 
/* ... funky stuff ... */ 
#endif /* linux */ 

用__linux__就可以达成目的;看仔细一点,不是linux喔。尽管linux也有定义, 
毕竟,这个仍然不是POSIX的标准。 

4.2 线上求助说明 

gcc编译器参数的说明文件是gcc info page(在Emacs内,按下C-h i,然後选 
‘gcc’的选项)。要是弄不出来,不是卖你CD-ROM的人没把这个东东压给你,不 
然就是你现在用的是旧版的。遇到这种情况,最好的方法是移动尊臀到archive 
[14]ftp://prep.ai.mit.edu/pub/gnu或是它的mirrors站台,去把gcc的原始档案 
抓回家,重新烹饪一番。 

gcc manual page(gcc.1) 可以说是已经过时了,要是你吃饱了撑著没事干硬是 
想看,它就会告诉你说别无聊了。 

旗正飘飘 

在命令列上执行gcc时,只要在它的屁股後面加上-On的选项,就能让gcc乖乖的替 
你生出最佳编码的机器码。这里的n是一个可有可无的小整数,不同版本的gcc 
,n的意义与其正确的功效都不一样,不过,典型的□围是从0(不要鸡婆,我不 
要最佳编码。)变化到2(最佳编码要多一点。),再升级到3(最佳编码要再多 
一点,多一点)。 

gcc在其内部会将这些数字转译成一系列的-f与-m的选项。执行gcc时带上旗号-v 
与-Q,你就能很清楚的看出每一种等级的-O是对应到那些选项。好比说,就-O2来 
讲,我的gcc告诉会我说: 

enabled: -fdefer-pop -fcse-follow-jumps -fcse-skip-blocks 
-fexpensive-optimizations 
-fthread-jumps -fpeephole -fforce-mem -ffunction-cse -finline 
-fcaller-saves -fpcc-struct-return -frerun-cse-after-loop 
-fcommon -fgnu-linker -m80387 -mhard-float -mno-soft-float 
-mno-386 -m486 -mieee-fp -mfp-ret-in-387 

要是你用的最佳编码等级高於你的编译器所能支援的(e.g. -O6),那麽它的效 
果就跟你用你的编译器所能提供的最高等级的效果是一样的。说实在的,发行出 
去的gcc程式码,用在编译时竟是如此处理这等问题,真的不是什麽好的构想。日 
後若是有更进步的最佳编码方法具体整合到新的版本里,而你(或是你的users) 
还是试著这样做的话,可能就会发现gcc会中断你的程式了。 

从gcc 2.7.0升级到2.7.2的users应该注意一点,使用-O2时会有一个bug。更糟糕 
的是,强度折减参数(strength reduction)居然没有用!要是你喜欢重新编 
译gcc的话,是有那麽一个修正的版本可以更正这项错误;不然的话,一定要确定 
每次编译时都有加上-fno-strength-reduce喔! 

11/12/97译 

有个性的微处理器 

有一些-m的旗号十分有用处,但是却无法藉由各种等级的-O打开来使用。这之中 
最重要的有是-m386和-m486这两种,用来告诉gcc该把正在编译的程式码视作专 
为386或是486机器所写的。不论是用哪一种-m来编译程式码,都可以在彼此的机 
器上执行,-m486编译出来的码会比较大,不过拿来在386的机器上跑也不会比较 
慢就是了。 

目前尚无-mpentium或是-m586的旗号。Linus建议我们可以用-m486 
-malign-loops=2 -malign-jumps=2 -malign-functions=2来得到最佳编码的486 
程式码,这样做正好就可以避免alignment(Pentium并不需要)有过大的gaps发 
生。Michael Meissner说: 

我的第六感告诉我,-mno-strength-reduce(嘿!要晓得我可不是在谈强度折 
减参数的bug呀,那已经是另外一个争论的战场了。)一样也可以在x86的机器 
上产生较快的程式码,这是因为x86的机器对暂存器有著不可磨灭的□渴在, 
而且GCCs method of grouping registers into spill registers vs. 
other registers doesnt help either。传统上,强度折减的结果会使得编 
译器去利用加法暂存器以加法运算来取代乘法运算。事实上,我在怀 
疑-fcaller-saves可能也只是个漏洞也说不定。 

而我的第七感则再度的告诉我说,-fomit-frame-pointer可能会也可能不会有 
任何的赚头。从这点来看,就是意谓著有另一个暂存器可以用来处理记忆体分 
配的问题。另方面,若纯粹从x86的机器在转换它的指令集成为机器码的方法 
上来看,便意谓著堆叠所用到的记忆体空间要比frame所用到的还要来得多; 
换句话说,Icache对程式码而言并没有实质上的帮助,若是阁下用 
了-fomit-frame-pointer的话,同时也是告诉编译器在每次呼叫函数之後,就 
必须修正堆叠的指标;然而,就frame来讲,若呼叫的次数不多的话,则允许 
堆叠暂时堆积起来。 

有关这方面主题的最後一段话仍是来自於Linus: 

要注意的是,如果你想要得到最佳状况的执行效能,可千万别相信我的话。无 
论如何,一定要进行测试。gcc编译器还有许多的参数可用,其中可能就有一 
种最特别的组合,可以给你最佳编码的结果。 

11/14/97译 5/15/98修正 

Internal compiler error: cc1 got fatal signal 11 

Signal 11是指 SIGSEGV,或者 ‘segmentation violation’。通常这是指 
说gcc对自己所用的指标感到困惑,而且还尝试著把资料写入不属於它的记忆体里 
。所以,这可能是一个gcc的bug。 然而,大体而言,gcc是一支经过严密测试且 
可靠度良好的软体佳作。它也用了大量复杂的资料结构与惊人的指标数量。简言 
之,若是要评选本世纪最挑惕与最一丝不□的RAM测试程式,gcc绝对可以一摘后 
冠。假如你无法重新复制这只bug---当你重新开始编译时,错误的讯息并没有一 
直出现在同一个地方---那几乎可以确定,是你的硬体本身有问题(CPU,记忆体,主 
机板或是快取记忆体).千万不要因为你的电脑可以通过开机程序的测试、或 
是Windows可以跑得很顺、或者其它什麽的,就回过头来大肆宣传说这是gcc的一 
个bug;你所做的这些测试动作,通常没有什麽实际上的价值,这是很合理的结论 
。另外,也不要因为编译核心时,总是停留在‘make zImage’的阶段,就要大骂 
这是gcc的bug---当然它会停在那儿啊!做‘make zImage’时,需要编译的档案 
可能就超过200档案;我们正在研拟一个替代的方案。 

如果你可以重覆产生这个bug,而且(最好是这样啦!)可以写一个短小的程式来 
展示这只bug的话,你就可以把它做成bug报告,然後email给FSF,或者 
是linux-gcc通信论坛。你可以去参考gcc的说明文件,看看有什麽详细的资讯,是 
他们所需要的。 

4.3 移植能力 

据报,近日来许多正面的消息指出,若是有某件东东到现在都还没移植到Linux上 
去,那麽可以肯定的是,它一定一点价值也没有。 

嗯!正经一点。一般而言,原始码只需要做一些局部的修改,就可以克服Linux 
100%与POSIX相容的特质。如果你做了任何的修改,而将此部份传回给原作者,会 
是很有建设性的举动。这样日後就只需要用到‘make’,就能得到一个可执行的 
档案了。 

BSD教徒 (有 bsd_ioctl、daemon 与 ) 

编译程式时,可以配合-I/usr/include/bsd与连结-lbsd的程式库。(例如:在你 
的Makefile档内,把-I/usr/include/bsd加到CFLAGS那一行;把-lbsd加 
到LDFLAGS那一行)。如果你真的那麽想要BSD型态的信号行为,也不需要再加 
上-D__USE_BSD_SIGNAL了。那是因为当你用了-I/usr/include/bsd与含括了标头 
档之後,make时就会自动加入了。 

失落的封印(SIGBUS, SIGEMT, SIGIOT, SIGTRAP, SIGSYS etc) 

Linux与POSIX是完全相容的。不过,有些信号并不是POSIX定义的---ISO/IEC 
9945-1:1990 (IEEE Std 1003.1-1990), paragraph B.3.3.1.1 sez: 

“在POSIX.1中省略了SIGBUS、SIGEMT、SIGIOT、SIGTRAP与SIGSYS信号,那是 
因为它们的行为与实作的方式息息相关,而且也无法进行适当的分类。确认实 
作方式後,便可以发送这些信号,可是必须以文件说明它们是在什麽样的环境 
底下发送出来的,以及指出任何与它们的发展相关的限制。” 

想要修正这个问题,最简单也是最笨的方法就是用SIGUNUSED重新定义这些信号。 
正确的方法应该是以条件式的编译#ifdef来处理这些问题才对: 

#ifdef SIGSYS 
/* ... non-posix SIGSYS code here .... */ 
#endif 

11/15/97译 5/22/98修正 

K & R 

gcc是一个与ANSI相容的编译器;奇怪的是,目前大多数的程式码都不符合ANSI所 
定的标准。如果你热爱ANSI,喜欢用ANSI提供的标准来撰写C程式,似乎除了加 
上-traditional的旗号之外,就没有其它什麽可以多谈的了。There is a 
certain amount of finer-grained control over which varieties of brain 
damage to emulate;请自行查阅gcc info page。 

要注意的是,尽管你用了-traditional来改变语言的特性,它的效果也仅局限 
於gcc所能够接受的□围。例如, -traditional会打开-fwritable-strings,使得 
字串常数移至资料记忆体空间内(从程式码记忆体空间移出来,这个地方是不能任 
意写入的)。这样做会让程式码的记忆体空间无形中增加的。 

前置处理器的符号卯上函数原型宣告 

最常见的问题是,如众所皆知,Linux中有许多常用的函数都定义成巨集存放在标 
头档内,此时若有相似的函数原型宣告出现在程式码内,前置处理器会拒绝进行 
语法分析的前置作业。常见的有atoi()与atol()。 

sprintf() 

在大部份的Unix系统上,sprintf(string, fmt, ...)传回的是string的指标,然 
而,这方面Linux(遵循ANSI)传回的却是放入string内的字元数目.进行移植时 
,尤其是针对SunOS,需有警觉的心。 

fcntl 与相关的函数;FD_*家族的定义到底摆在哪里? 

就在里头。 为了真正的原型宣告,当你用了fcntl,可能你也想含 
括标头档进来。 

一般而言,函数的manual page会在SYNOPSIS章节内列出需要的标头档。 

select()的计时---程式执行时会处於忙碌-等待的状态 

很久很久以前,,select()的计时参数只有唯读的性而已。即使到了最近 
,manual pages仍然有下面这段的警告: 

select()应该是藉由修正时间的数值(如果有的话),再传回自原始计时开始 
後所剩馀的时间。未来的版本可能会使这项功能实现。因此,就目前而言,若 
以为呼叫select()之後,计时指标仍然不会被修正过,可是一种非常不明智的 
想法喔! 

未来就在我们的眼前了!至少,在这儿你绝对可以看到。函数select()传回的, 
是扣除等待尚未到达的资料所耗费的时间後,其剩馀的时间数值。如果在计时结 
束时,都没有资料传送进来,计时引数便会设为0;如果接著还有任何 
的select(),以同样的计时structure来呼叫,那麽select()便会立刻结束。 

若要修正这项问题,只要每次呼叫select()前,都把计时数值放到计时 
structure内,就没有问题了。把下面的程式码, 

struct timeval timeout; 
timeout.tv_sec = 1; timeout.tv_usec = 0; 
while (some_condition) 
select(n,readfds,writefds,exceptfds,&timeout); 

改成, 

struct timeval timeout; 
while (some_condition) { 
timeout.tv_sec = 1; timeout.tv_usec = 0; 
select(n,readfds,writefds,exceptfds,&timeout); 


这个问题,在有些版本的Mosaic里是相当著名的,只要一次的等待,Mosaic就挂 
在那里了。Mosaic的萤幕右上角,是不是有个圆圆的、会旋转的地球动画。那颗 
球转得愈快,就表示资料从网路上传送过来的速率愈慢! 

产生中断的系统呼叫 

特徵: 

当一支程式以Ctrl-Z中止、然後再重新执行时□或者是其它可以产生Ctrl-C中断 
信号的情况,如子程序的终结等□系统就会抱怨说"interrupted system call"或 
是"write: unknown error",或者诸如此类的讯息。 

问题点: 

POSIX的系统检查信号的次数,比起一些旧版的Unix是要多那麽一点。如果 
是Linux,可能就会执行signal handlers了□ 

* 非同步地(计时器的滴答声) 
* 系统呼叫的传回值 
* 在下列系统呼叫的执行期间∶ select(), pause(), connect(),accept(), 
read() on terminals, sockets, pipes or files in /proc, write() on 
terminals, sockets, pipes or the line printer, open() on FIFOs, 
PTYs or serial lines,ioctl() on terminals, fcntl() with command 
F_SETLKW, wait4(), syslog(), any TCP or NFS operations. 

就其它的作业系统而言,你需要的可能就是下面这些系统呼叫了: creat(), 
close(), getmsg(), putmsg(), msgrcv(), msgsnd(), recv(), send(), 
wait(), waitpid(), wait3(), tcdrain(), sigpause(), semop() to this 
list. 

在系统呼叫期间,若有一信号(那支程式本身应准备好handler因应了)产生 
,handler就会被呼叫。当handler将控制权转移回系统呼叫时,它会侦测出它已 
经产生中断,而且传回值会立刻设定成-1,而errno设定成EINTR。程式并没有想 
到会发生这种事,所以就挂了。 

有两种修正的方法可以选择: 

(1) 对每个你自行安装的signal handler,都须在sigaction的旗号加 
上SA_RESTART。例如,把下列的程式, 

signal (sig_nr, my_signal_handler); 

改成, 

signal (sig_nr, my_signal_handler); 
{ struct sigaction sa; 
sigaction (sig_nr, (struct sigaction *)0, &sa); 
#ifdef SA_RESTART 
sa.sa_flags |= SA_RESTART; 
#endif 
#ifdef SA_INTERRUPT 
sa.sa_flags &= ~ SA_INTERRUPT; 
#endif 
sigaction (sig_nr, &sa, (struct sigaction *)0); 


要注意的是,当这部份的变更大量应用到系统呼叫之後,呼叫read()、write() 
、ioctl()、 select()、 pause() 与 connect()时,你仍然得自行检查EINTR。 
如下所示: 

(2) 你自己得很明确地检查EINTR: 

这里有两个针对read()与ioctl()的例子。 

原始的程式片段,使用read(): 

int result; 
while (len >; 0) { 
result = read(fd,buffer,len); 
if (result < 0) break; 
buffer += result; len -= result; 


修改成, 

int result; 
while (len >; 0) { 
result = read(fd,buffer,len); 
if (result < 0) { if (errno != EINTR) break; } 
else { buffer += result; len -= result; } 


原始的程式片段,使用ioctl(): 

int result; 
result = ioctl(fd,cmd,addr); 

修改成, 

int result; 
do { result = ioctl(fd,cmd,addr); } 
while ((result == -1) && (errno == EINTR)); 

注意一点,有些版本的BSD Unix,其内定的行为是重新执行系统呼叫。若要让系 
统呼叫中断,得使用 SV_INTERRUPT或SA_INTERRUPT旗号。 

可以写入的字串 

gcc对其users总怀抱著乐观的想法,相信当他们打算让某个字串当作常数来用 
时---那它就真的只是字串常数而已。因此,这种字串常数会储存在程式码的记忆 
体区段内。这块区域可以page到磁碟机的image上,避免耗掉swap的记忆体空间, 
而且任何尝试写入的举动都会造成分页的错误(segmentation fault)。这可是一 
种特色呢! 

对老旧一点的程式而言,这可能会产生一个问题。例如,呼叫mktemp(),传递的 
引数(arguments)是字串常数。 mktemp()会尝试著在*适当的位置*重新写入它的 
引数。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值