只要执行gcc时,附加 -v
这个参数(switch),就能找出你所用的这版gcc,自动帮你定义了什麽符号(symbols).例如,我的机器看起来会像这样:
$ 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独有的特性(Linux-specific features),那麽把那些无法移植的程式码(non-portable bits),以条件式编译(conditional compilation)的前置命令封括(enclose in)起来,可是个不错的主意呢!
#ifdef __linux__
/* ... funky stuff ... */
#endif /* linux */
用__linux__
即可达成目的;看仔细一点,不是linux
啊.仅管後者也有定义,毕竟,仍然不是POSIX的标准(not POSIX compliant).
gcc编译器参数(switches)的说明文件是gcc info page(在Emacs内,按下C-h i
,然後选'gcc'的选项).要是弄不出来,不是卖你CD-ROM的人,没把这个东东压给你,不然就是你现在用的是旧版的.这种情况下,最好的方法是移动尊臀到archiveftp://prep.ai.mit.edu/pub/gnu或是它的mirrors站台上,把gcc的原始档案抓回家,重新烹饪一番.
gcc manual page (gcc.1
) 可以说是已经过时了.一旦你吃饱撑著没事干要去看看它的话,它就会告诉你这件事,叫你别无聊了.
在命令列(command line)上执行gcc时,只要在它的屁股後面加上-O
n的选项,就能让gcc乖乖的替你生出最佳化後的机器码(output code).这里的n是一个可有可无的小整数.不同的gcc版本,n的意义与其正确的(exact)功效都不一样;不过,典型的□围是从0(不要鸡婆,我不要最佳化)变化到2(最佳化要多一点),再到3(最佳化要再多一点,多一点).
gcc在其内部会将这些转译成一系列的-f
与-m
选项(options).执行gcc时带上旗号(flags)-v
与-Q
,你就能很清楚的看出每一种等级的-O
是对应(maps)到那些选项(options).例如,就-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
要是你用的最佳化等级(optimization level)高於你的编译器所能支援的(e.g. -O6
),那麽它的效果,就跟你用你的编译器所能提供的最高等级的,是一样的结果.说实在的,发行出去的gcc程式码,用在编译时竟是如此处理这等问题,实非什麽好的构想.日後若是有更进步的最佳化方法具体整合到新的版本里,而你(或你的users)还是试著这样做的话,可能就会发现,gcc会中断你的程式(break your code)了.
从gcc 2.7.0到2.7.2的users应该注意到,使用时-O2
会有一个bug存在.更糟糕的是,强度折减(strength reduction)居然没有用(doesn't work)!要是你喜欢重新编译gcc的话,是有那麽一个修正的版本(patch)可以更正这项错误;不然的话,一定要确定每次编译时都会加上-fno-strength-reduce
喔!
11/12/97译
有一些-m
的旗号无法藉由各种等级的-O
来打开,然而却是十分有用的.这之中最主要的是-m386
与-m486
两种,用来告诉gcc该把正在编译的程式码视作专为386或是486机器所写的.不论是用哪一种来编译程式码,都可以在彼此的机器上执行,-m486编译出来的码会比较大,可是拿来在386的机器上跑也不会比较慢就是了.
目前尚无-mpentium
或是-m586
的旗号.Linus建议我们,可以用-m486 -malign-loops=2 -malign-jumps=2 -malign-functions=2
,来得到最佳化的486程式码(486 code optimizations),而这样做正好就可以避免alignment(Pentium并不需要)有过大的gaps发生. Michael Meissner说:
我的第六感(hunch)告诉我,-mno-strength-reduce
(嘿!我可不是在谈强度折减的bug啊,那已经是另外一个争论的战场了.)一样也可以在x86的机器上,产生较快的程式码,这是因为x86的机器对暂存器(register)有著不可磨灭的□渴在(and GCC's method of grouping registers into spill registers vs. other registers doesn't help either).传统上,强度折减的结果会使得编译器利用加法暂存器(additional registers)以加法运算(addition)来取代乘法运算(multiplication).而且,我也在怀疑(suspect)-fcaller-saves
,可能也只是个漏洞(loss)也说不定.
而我的第七感则再度的告诉我,-fomit-frame-pointer
可能会,也可能不会有任何的赚头.从这点来看,即意谓著有另一个暂存器可用来处理记忆体分配(allocation)的问题.另方面,若纯粹从x86的机器在转换(encodes)它的指令集(instruction set)成为机器码的方法上来看,便意谓著堆叠(stack)所用到的记忆体空间要比frame所用到的还要来的多;换句话说,Icache对程式码而言并没有实质上的益处.若是阁下用了-fomit-frame-pointer
的话,同时,也就是告诉编译器在每次呼叫函数(calls)之後,就必须修正堆叠的指标(stack pointer);然而,就frame来讲,若呼叫的次数不多的话,则允许堆叠暂时堆积(accumulate)起来.
有关这方面主题的最後一段话仍是来自於Linus:
要注意的是,如果你想要得到最佳状况的执行成果(optimal performance),可千万别相信我的话.无论如何,一定要进行测试.gcc编译器还有许多的参数(switches)可用,其中可能就有一种最特别的组合(set),可以给你最佳化的结果喔.
11/14/97译
Internal compiler error: cc1 got fatal signal 11
Signal 11是指 SIGSEGV, 或者 `segmentation violation'.通常这是指 说gcc对自己所用的指标感到困惑起来,而且还尝试著写入不属於它的记忆体.所以,这可能是一个gcc的bug.
然而,大部份而言,gcc是一件经过严密测试且可靠度佳的软体佳作.它也用了大量复杂的资料结构与惊人的指标数量.简言之,若是要评选本世纪最挑惕与最一丝不□的RAM测试程式(RAM tester)的话,gcc绝对可以一摘后冠的.假如你无法重新复制这只bug---当你重新开始编译时,错误的讯息并没有一直出现在同一个地方---那几乎可以确定,是你的硬体本身有问题(CPU,记忆体,主机板或是快取记忆体).千万不要因为你的电脑可以通过开机程序的测试(power-on checks),或者Windows可以跑得很顺,或者其它什麽的,就回过头来大肆宣传说这是gcc的一个bug;你所做的这些测试动作,通常没有什麽实际上的价值,而且没有价值也是很合理的结论.另外,也不要因为编译核心时,总是停留在`make zImage
'的阶段,就要大骂这是gcc的bug---当然它会停在那儿啊!做`make zImage
'时,需要编译的档案可能超过200档案;我们正在研拟一个比较小的地方来取代.
如果你可以重覆产生这个bug,而且(最好是这样啦)可以写一个短小的程式来展示这只bug的话,你就可以把它做成bug报表(bug report),然後email给FSF,或者是linux-gcc邮件表列(linux-gcc mailing list).你可以去参考gcc的说明文件,看看有什麽详细的资讯,是他们所需要的.
据报,近日来许多正面消息指出,若有某件东东到现在都还没移植到Linux上去,那麽可以肯定的是,它一定一点价值也没有.:-)
嗯!正经一点.一般而言,原始码只需要做一些局部的修改(minor changes),就可以克服(get over)Linux 100%与POSIX相容的特质(compliance).如果你做了任何的修改,而将此部份传回(passing back)给原作者,会是很有建设性的举动(worthwhile).这样日後就只需要用到'make',就能得到一个可执行的档案了.
bsd_ioctl
, daemon
与 <sgtty.h>
) 编译程式时,可以配合-I/usr/include/bsd
与连结-lbsd
的程式库.(例如:在你的Makefile档内,把-I/usr/include/bsd
加到CFLAGS
那一行;把-lbsd
加到LDFLAGS
那一行).如果你真的那麽想要BSD型态的信号行为(BSD type signal behavior),也不再需要加上-D__USE_BSD_SIGNAL
了.那是因为当你用了-I/usr/include/bsd
与含括了标头档<signal.h>
之後,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信号,那是因为它们的行为(behavior)与实作方式是息息相关的(implementations dependent),而且也无法进行适当的分门别类(adequately categorized).确认实作方式後(conforming implementations),便可以生产出(deliver)这些信号,可以必须以文件说明(document)它们是在什麽样的环境(circumstances)下生产出来的,以及指出与它们的发展相关的任何限制(any restrictions concerning their delivery)".
如欲修正此点,最简单,也是最笨的(cheesy)方法就是以SIGUNUSED
重新定义这些信号.而正确的方法应是以条件式的编译#ifdef
来处理这些问题才对:
#ifdef SIGSYS
/* ... non-posix SIGSYS code here .... */
#endif
11/15/97译
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
会打开(turn on)-fwritable-strings
,使得字串常数(string constants)移至资料记忆体空间(data space)内(从程式码记忆体空间(text space),这地方是不能任意写入的).这样做会让程式码的记忆体空间无形中增加的.
最常见的问题是,如众所皆知,Linux中有许多常用的函数都定义成巨集(macros)存放在标头档(header files)内,此时若有相似的函数原型宣告出现在程式码内,前置处理器会拒绝进行语法分析(parse)的前置作业.常见的有atoi()
与atol()
.
sprintf()
在大部份的Unix系统上, sprintf(string, fmt, ...)
传回的是string
的指标,然而,这方面Linux(遵循ANSI)传回的却是放入string内的字元数目.进行移植时,尤其是针对SunOS,需有警觉的心.
fcntl
与相关的函数; FD_*
家族的定义到底摆在哪里?
就在 <sys/time.h>
里头. 为了真正的原型宣告,当你用了fcntl
,可能你也想含括标头档<unistd.h>
进来.
一般而言,函数的manual page会在SYNOPSIS章节内列出需要的标头档
.
select()
的计时(time-out)---程式执行时会处於忙碌-等待的状态(busy-waiting).
很久很久以前, select()
的计时参数(time-out parameter)只有读的属性(read-only)而已.即使到了最近,manual pages仍然有下面这段的警告:
select()照理讲应该是藉由适当的修正时间的数值,再传回自原始计时(original time-out)开始後所剩馀的时间.未来的版本可能会使这项功能实现.因此,就目前而言,若假定在呼叫select()之後,计时指标(time-out pointer)仍然不会让人给修正过,可是一种非常不明智的想法喔!
未来就在我们的眼前了!至少,在这儿你绝对可以看到. 函数select()
传回的,是扣除等待尚未到达的资料所耗费的时间後,其剩馀的时间值.如果在计时结束时,都没有资料传送进来,计时引数(time-out argument)便会设为0;如果接著还有任何的select(),以同样的time-out structure来呼叫,那麽select()便会立刻结束.
若要修正这项问题,只要每次呼叫select()
前,都把计时数值(time-out value)放到time-out 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中止(stop),然後再重新执行(restart)时--或者是其它可以产生Ctrl-C中断(interruption)信号的情况,如子程序(child process)终结(termination)等--系统就会抱怨说"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. 就其它的作业系统而言,你需要的可能就是下面这些系统呼叫(system calls)了:
creat()
, close()
, getmsg()
, putmsg()
,
msgrcv()
, msgsnd()
, recv()
, send()
,
wait()
, waitpid()
, wait3()
, tcdrain()
,
sigpause()
, semop()
to this list.
在系统呼叫期间,若有一信号(那支程式本身应准备好handler因应了)产生,handler就会被呼叫.当handler将控制权转移回系统呼叫时,它会侦测出它已经产生中断,而且传回值会立刻设定成-1,errno设定成EINTR
.程式并没有想到会发生这种事,所以就会bottles out了.
有两种修正的方法可以选择:
(1) 对每个你自行安装(install)的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()
时,你仍然得自行检查(check for)EINTR
.如下所示.
(2) 你自己得很明确地(explicitly)检查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,其内定的行为(default behaviour)是重新执行系统呼叫.若要让系统呼叫中断,得使用 SV_INTERRUPT
或SA_INTERRUPT
旗号.
gcc对其users总怀抱著乐观的想法(optimistic view),相信当他们打算让某个字串当作常数来用时---那它就真的只是字串常数而已.因此,这种字串常数会储存在程式码的记忆体区段内(in the code area of the program).这块区域可以page到磁碟机的image上,避免耗掉swap的记忆体空间,而且任何尝试写入的举动都会造成分页的错误(segmentation fault).这可是一种特色呢!
对老旧一点的程式而言, 这可能会产生一个问题.例如,呼叫mktemp()
,传递引数(arguments)是字串常数. mktemp()
会尝试著在*适当的位置(in place)*重新写入它的引数.
修正的方法不外乎(a)以-fwritable-strings
编译,迫使gcc将此常数置放在资料记忆体空间(data space)内.或者(b)将侵犯地权的部份(offending parts)重新改写,配置一个不为常数的字串(non-constant string),在呼叫前,先以strcpy()将资料拷贝进去.
execl()
会失败?
那是因为你呼叫的方式不对.execl
的第一个引数是你想要执行的程式名.第二个与接续的引数会变成你所呼叫的程式的argv
阵列(array).记住:传统上,argv[0]
是只有当程式没有带著引数执行时,才会有设定值.所以罗,你应该这样写:
execl("/bin/ls","ls",NULL);
而不是只有,
execl("/bin/ls", NULL);
执行程式而不带任何引数(with no arguments),可解释成(construe)是一种邀请函(invitation),目的是把此程式的动态程式库独立(dynamic library dependencies)的特性印出来(print out).至少,a.out是这样的.就ELF而言,事情就不是这样了.
(如果你想得知此程式库的资讯,有一些更简单的介面可用;参考动态载入(dynamic loading)那一章节,或是ldd
的manual page.)
11/16/97译