LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 4616|回复: 3

GCC-HOWTO(上)

[复制链接]
发表于 2004-3-29 17:06:57 | 显示全部楼层 |阅读模式
GCC的安装与设置


gcc的版本
如果你想得到你现在正在使用的gcc的版本号的话,打入gcc -v就会列出系统中安装的gcc的版本号,一般的输出格式是这样
$ gcc -v
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs
gcc version 2.7.2

i486 这是说明你现在正在使用的gcc是对应486CPU的可执行文件,这个标识也可能是386或者586。
box 这个标识并不重要,有时会被其他的标识替代,比如slackware或者debian,如果你喜欢自己编译gcc的话可以改成任意一个自定义的标识。
linux 这个标识可能是linuxelf或者linuxaout,这取决与你使用的gcc的版本
linux 如果版本高于2.7.0表示使用ELF,其他则为a.out
linuxaout 表示使用a.out
linuxelf 这样的gcc一般的版本都在2.6.3左右,gcc2.6.3在生成ELF时会发生错误,建 议升级
2.7.2 版本号

GCC安装在哪里?
gcc的所包含的文件一般为:
/usr/lib/gcc-lib/<编译系统名称>/<版本>/ 这里包含了一些用于实际编译工作的可执行文件和一些头文件
/usr/bin/gcc 这个可执行文件为编译器的驱动器,我们将其直接用于在命令行编译文件,如果你安装了几个版本的编译器,你可以使用-V来指定使用其他版本。例如当前版本为 2.7.2,如果你想查看以往安装的2.6.3版本编译器的信息,可以在命令行打入
gcc -V 2.6.3 -v,输出
# 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/<编译系统名称>/(bin|lib|include) 如果你安装了多目标编译器(比如a.out和elf,交叉编译器等多个编译平台)的话,就会有这些目录。
/lib/,/usr/lib/ 本地系统运行所需要的库文件。许多应用程序都需要/lib/cpp(比如X窗口系统),你需要从/usr/lib/gcc-lib/<编译系统名称 >/<版本>/中拷贝一份或者建立一个符号连接。

头文件的路径
头文件的通常路径为:
/usr/include/
/usr/include/linux/
/usr/lib/gcc-lib/<编译系统名称>/<版本号>

交叉编译器
Linux为目标平台
你拿到了gcc的源代码,在XXX平台上使用./configure --target=i486-linux --host=XXX来实现,而且这需要使用Linux的内核源代码。
Linux为平台,MSDOS为目标平台
这种方式需要使用工具"emx"和"go",请参阅相关资料。


代码移植和编译

默认添加的编译参数
你可以使用-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标准

编译器文档
关于gcc的参数设置请查看gcc的info文档,在Emacs中使用C-h i调出选项后选择gcc,但也许你的系统中没有安装相关文档,也有可能你的文档版本比较老,这时你可以下载gcc的源代码并从中找到最新的文档包。
也许你喜欢查看gcc的man手册,但如手册开始时所说一样,这个手册有点太老了。

编译器参数
gcc可以对输出的代码进行优化,你可以在你的编译选项中加入 -0<n>,<n>是一个整数,代表优化等级,可能不同的gcc版本中这个整数的意义不同,但一般在当前版本中是取0到3,0代表不优化,2代表较多优化,3优化得最多。
当你使用了这个选项时,gcc用-f和-m来实现它,用-v和-Q(文档中没有提到)选项就可以看到gcc实际进行的工作,比如使用-02就是
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
使用超出最大范围的优化等级,编译器只做能达到的最高等级的优化,但这并不是一个好的习惯,因为这么做可能会出现许多问题。
从gcc2.7.0升级到2.7.2的用户请注意,你的编译器在处理-02选项时会有一个bug,就是编译器不会削减生成的程序的长度,有个源代码补丁可以解决这个问题,但需要你重新编译gcc的源代码,还有一种方法,就是每次使用gcc编译时都加上-fno-strength-reduce

关于处理器
-0选项不会去调用-m但是它仍然很重要,比如-m386和-m486,告诉gcc分别对386和486做优化,参数这两个中的任意一个后编译出的程序都可以在另外一种CPU上运行,生成的对应486的程序更大,但运行速度更快。
gcc暂时没有-mpentium和-m586选项。Linus建议说,使用-m486 -malign-loops=2 -malign-jumps=2 -malign-functions=2来对486代码进行优化,这样可以避开alignment(在奔腾CPU中并不需要)过程中的漏洞。 Micheal Meissner说:“我觉得-mno-strength-reduce也会产生运行速度更快的x86代码(注意,我并不是指强度折减方面的bug,这是另外一个问题)。这是因为x86对寄存器的依赖性很强。强度折减是使用加法寄存器进行加法操作的方法去进行乘法操作,我怀疑-fcaller-saves 也是个漏洞。”“我还认为-omit-frame-pointer设计得并不成功,一方面,它能够说明有一个寄存器可以用来分配,另一方面,从x86转换它的指令编码方面看,堆栈所占用的地址空间比frame要多,这就说明分配给程序的lcache要少一些。同时,-fomit-frame- pointer意味着每次调用某个过程之后都要重新调整堆栈指针,但在frame中,在进行一些过程调用时,可以将堆栈暂时堆积起来。”
最后一段提示还是来自Linus的:
“如果你要为你的程序做优化的话,不要一味遵守我的意见,你最好在反复试验中找到自己的方法。gcc有很多编译参数,也许其中的某个参数会给你的程序带来更好的优化。

Internal compiler error:cc1 got fatal signal 11
signal 11代表SIGSEGV或者‘segment violation’,这意味着程序的指针错误或者正在向一段没有得到操作权限的内存区域写数据,也许这就是一个gcc的bug。
但是gcc现在可以说是一个可靠的软件。它使用了大量的结构变量和大量的指针,总之,gcc是一个严格RAM测试程序。如果在重复编译的过程中错误没有再次出现,那么很可能是你的硬件系统的某个问题(比如CPU,内存,主板,cache等)。不要因为系统自检成功或者Windows能够成功启动就说是 gcc的bug,也不要因为在'make zImage'过程中中断就认为是gcc的bug。make zImage需要编译多达200个文件,我们正在寻找一种解决的方法。
如果这种错误重复出现,你可以写一个小的程序证明这一点并发送给FSF组织或者发到linux-gcc的邮件列表,详细信息请参阅相关文档。


移植能力

在最近有一种说法,如果一个东西没有移植到Linux上的话那么这个东西的产生是毫无意义的:-)
尽管如果要求linux百分之百的遵守POSIX标准的话,linux仍然不需要太大的改动,但是要求将来在UNIX只需要使用'make'就能从任意一个代码包中生成可执行的话,Linux确实需要经过一些更改。

使用BSD特性(例如bsd_ioctl,daemon和<sgtty.h>)
这需要在编译时加上-I/usr/include/bsd和-lbsd。现在已经不需要在需要使用BSD信号时使用- D__USE_BSD_SIGNAL,因为这些动作已经通过使用-I/usr/include/bsd和include<signal.h> 时完成了。

‘Missing’信号(SIGBUS,SIGEMT,SIGIOT,SIGRAP,SIGSYS等)
Linux遵从POSIX标准,但这些并不是POSIX中定义的信号,因为它们与实际的动作有很大联系,并且没有办法为它们归类。执行一定的动作后便可以使用这些信号,但最好要在使用时在文档中说明使用的环境和限制。
最简单的方法是使用SIGUNUSED重新定义这些信号,就是将这些代码括起来并在适当的时候进行调用。
#ifdefs:
#ifdef SIGSYS
/* ... non-posix SIGSYS code here .... */
#endif

K&R
GCC属于遵从ANSI标准的编译器,但现有的代码都不是ANSI标准的,如果你一定要使用ANSI的话,没有别的办法,似乎只有在编译时加上- traditional,但确实存在一些好的方法作到这些,请参阅gcc的info手册。
注意-traditional只能对gcc包含的语法起作用,例如,使用-fwritable-strings就会将字符串的空间从代码段移到数据段,但这会使程序的容量增加。

宏定义和原形函数的冲突
一个常见的问题是在Linux的头文件中将一些常用的函数用宏包装起来,但编译器的预处理器会拒绝分析相似的函数原形,最常见的是atoi()和atol ()。
sprintf()
需要注意的是,sprintf(string,fmt,....)在许多情况下返回一个指向字符串string的指针,尤其在SUNOS下更是如此,但在遵从ANSI标准的Linux下是放置到字符串中的字符的个数。

fcntl和类似函数。FD_*的内容的具体位置在哪里?
在<sys/time.h>中。如果使用fcntl的话你需要在你的代码中包含unistd.h来得到函数原形。
在man手册的SYNOPSIS一节中通常会有这方面代码中需要包含的文件的列表。

select()超时,程序进入忙-等待状态
BSD手册上说“select()函数会用修改时间变量的方法返回剩余的时间,所以指出超时时间的变量在运行中会被改变。”
在一些Linux版本中已经实现这种作法,但在另外一些版本里并没有,所以最好不要在某个系统上不经实验就使用在其他系统上实验成功的代码。
常用解决方法是每次使用select()时都重新设置一遍超时时间变量的数值,比如这样:
struct timeval timeout;
while (<条件>)
{
    timeout.tv_sec=1;timeout.tv_use=0;
    select (n,readfds,writefds,exeptfds,&timeout);
}
但不要这样写:
struct timeval timeout;
timeout.tv_sec=1;timeout.tv_usec=0;
while(<条件>) select (n,readfds,writefds,exeptfds,&timeout);
某些版本的Mosaic就有这种问题,浏览器右上角的地球仪在网速越快时转得越慢。

使用用于打断程序运行的系统调用
例如:
使用Ctrl-Z中断程序运行然后再重新开始运行,或者使用Ctrl-C停止程序运行,在这种情况下一般会提示“Interrupted system call”或者“write:'unknow error”等等。

问题:
POSIX系统对这些信号检查得非常频繁。但Linux在这些情况下才检查这类信号:
    同步
    系统调用后返回
    在进行这些系统调用时:对终端,sockets,管道,或者/proc中的文件进行select(),pause(),connect(),accept (),read()操作时,对终端,sockets管道或者打印机进行write()操作时,对FIFO,PTY或者串行线路进行open()操作时,对终端进行ioctl()操作时,在fcntl()和F_SETLKW,wait4(),syslog()一起使用时,在使用任何TCP和NFS操作时。
在其他的操作系统下可能还包括下面的这些系统调用:
creat(),close(),getmsg(),putmsg(),msgrcv(),msgsnd(),recv(),send(),wait(),
waitpid(),wait3(),tcdrain(),sigpause(),semop().

如果在一个系统调用的过程中发出一个信号,那么相应的处理程序就会被调用,在处理程序返回系统调用之后系统就会查觉这个信号并且返回一个-1并且使 errno=EINTR,然后程序撤出。

下面有两个关于上面提到的EINTR的解决方案:
(1)向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(),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系统中默认的动作是重新执行(restart),要使用中断程序运行的系统调用时需要加上SV_INTERRUPT或SA_INTERRUPT选项。

使用可以写入的字符串,避免程序段错误
如果使用字符串常量的话,这些字符串将会保存在程序段中,这样就会将其保存在程序的磁盘区域,不占用swap的空间,读写这些字符串常量就会引起段错误。
在一些老的程序中会产生这种错误,比如在使用mktemp()时使用字符串常量作参数,mktemp()就会常试向参数的空间重新写入数据。
解决方法有:(a)在编译时加入-fwritable-string,让gcc将字符串常量写入数据空间,(b)将字符串拷贝到变量空间后再使用。

为什么execl()调用失败?
很可能因为使用方法不当,调用execl时的第一个参数是需要执行的程序,后面的参数是程序需要的argv数组,但如果程序不需要参数的话调用时也要加上,就象下面这样
execl("/bin/ls","ls",NULL);
而不应当写成
execl("/bin/ls",NULL);
什么参数都不加的话,就是列出程序需要使用的动态库,a.out是这样,ELF可能有所不同。如果需要关于动态库的信息的话,有许多方法可以使用,详情请参看动态库的加载章节和ldd的man手册。
发表于 2004-4-9 10:55:47 | 显示全部楼层
期待着下呢....
发表于 2008-1-16 08:38:47 | 显示全部楼层
顶贴顶贴顶够六个字
回复 支持 反对

使用道具 举报

发表于 2008-5-20 11:51:00 | 显示全部楼层
好帖,收下了,谢谢
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表