- 论坛徽章:
- 0
|
GCC笔记
![]()
---------- The History of GCC ----------
1984年,Richard Stallman发起了自由软件运动,GNU (Gnu's Not Unix)项目应运而生,3年后,最初版的GCC横空出世,成为第一款可移植、可优化、支持ANSI C的开源C编译器。
GCC最初的全名是GNU C Compiler,之后,随着GCC支持的语言越来越多,它的名称变成了GNU Compiler Collection。
gcc: GCC的前端,C编译器.
----------------------------------------------------------------------
常用选项:
(1)警告信息
-Wall : 显示所有常用的编译警告信息。
-W : 显示更多的常用编译警告,如:变量未使用、一些逻辑错误。
-Wconversion : 警告隐式类型转换。
-Wshadow : 警告影子变量(在代码块中再次声明已声明的变量)
-Wcast-qual :警告指针修改了变量的修饰符。如:指针修改const变量。
-Wwrite-strings : 警告修改const字符串。
-Wtraditional : 警告ANSI编译器与传统C编译器有不同的解释。
-Werror : 即使只有警告信息,也不编译。(gcc默认:若只有警告信息,则进行编译,若有错误信息,则不编译)
(2)C语言标准
默认情况下,gcc使用GNU C扩展。
-ansi : 关闭GNU扩展中与ANSI C相抵触的部分。
-pedantic : 关闭所有的GNU扩展。
-std=c89 : 遵循C89标准
-std=c99 : 遵循C99标准
注意:后三个选项可以与-ansi结合使用,也可以单独使用。
(3) 生成特定格式的文件
以hello.c为例子,可以设置选项生成hello.i, hello.s, hello.o以及最终的hello文件:
----
hello.c : 最初的源代码文件;
hello.i : 经过编译预处理的源代码;
hello.s : 汇编处理后的汇编代码;
hello.o : 编译后的目标文件,即含有最终编译出的机器码,但它里面所引用的其他文件中函数的内存位置尚未定义。
hello / a.out : 最终的可执行文件
----
$ gcc -Wall -c hello.c : 生成hello.o
$ gcc -Wall -c -save-temps hello.c : 生成hello.i, hello.s, hello.o
注意-Wall 和 -o选项的使用场合:
涉及到编译(即会生成.o文件时,用-Wall)
涉及到最终生成可执行文件,指定生成文件名时用-o(若不指定,则默认为a.out)
(4) 多文件编译、连接
如果原文件分布于多个文件中:file1.c, file2,c
$ gcc -Wall file1.c file2.c -o name
若对其中一个文件作了修改,则可只重新编译该文件,再连接所有文件:
$ gcc -Wall -c file2.c
$ gcc file1.c file2.o -c name
注意:若编译起在命令行中顺序读取.o文件,则它们的出现顺序有限制:含有某函数定义的文件必须出现在含有调用该函数的文件之后。好在GCC无此限制。
----------------------------------------------------------------------
*** 与外部库连接 ***
-----------------------------
外部库有两种: (1)静态连接库lib.a
(2)共享连接库lib.so
两者的共同点:
.a, .so都是.o目标文件的集合,这些目标文件中含有一些函数的定义(机器码),而这些函数将在连接时会被最终的可执行文件用到。
两者的区别:
静态库.a : 当程序与静态库连接时,库中目标文件所含的所有将被程序使用的函数的机器码被copy到最终的可执行文件中。
共享库.so : 与共享库连接的可执行文件只包含它需要的函数的表,而不是所有的函数代码,在程序执行之前,那些需要的函数代码被拷贝到内存中,这样就使可执行文件比较小,节省磁盘空间(更进一步,操作系统使用虚拟内存,使得一份共享库驻留在内存中被多个程序使用)。
共享库还有个优点:若库本身被更新,不需要重新编译与它连接的源程序。
正因为共享库的优点, 如果系统中存在.so库,gcc默认使用共享库:
如果我们用这样的命令: $ gcc -Wall -L. hello.c -lNAME -o hello
gcc也现检查是否有替代的libNAME.so库可用。
那么,如果不想用共享库,而只用静态库呢?可以加上 -static选项
$ gcc -Wall -static hello.c libNAME.a -o hello
它等价于:
$ gcc -Wall hello.c libNAME.a -o hello
库相当于是目标文件的集合。利用库可以使用它提供的函数:
$ gcc -Wall hello.c libNAME.a -o hello
注意:可以直接在命令行指定库的全名,也可以按缩写来: -lNAME == libNAME.a
$ gcc -Wall hello.c -lNAME -o hello
上述假设libNAME.a存在于系统默认的连接路径中(关于搜索路径,参考稍后内容)
如果libNAME.a在当前目录,应执行下面的命令:
$ gcc -Wall -L. hello.c -lNAME -o hello
-L.表示将当前目录加到连接路径。
注意:
(1)连接库在命令行中出现的顺序:含有函数定义的文件要出现在调用该函数的文件之后。(遵循与连接目标文件相同的顺序)
(2)使用库时,要在源代码中包含适当的头文件,这样才能声明库中函数的原型。(发布库时,就需要给出相应的头文件)
-----------------------------
搜索路径
#include 只在默认的系统目录搜索头文件,UNIX类系统默认的系统路径为:
头文件,包含路径: /usr/local/includes/ or /usr/includes/
库文件,连接路径: /usr/local/lib/ or /usr/lib/
若要包含不在包含路径的头文件、连接不在连接路径的库:
1,可直接在源文件中使用 #include "/../file.h",但源代码在其他机器上编译时会有问题,而且也解决不了连接路径的问题。还是在编译命令中指定路径比较好。
2,在编译命令中指定路径:
-I : 指定包含路径 -I/opt/gdbm-1.8.3/include
-L : 指定连接路径 -L/opt/gdbm-1.8.3/lib
这样,就在系统默认路径的基础上添加了指定的包含路径和连接路径。(源代码中仍用 #include )
3,通过环境变量设置:
若要多次输入编译命令,2就比较麻烦,可以设置环境变量添加路径:
$ C_INCLUDE_PATH=/opt/gdbm-1.8.3/include
$ export C_INCLUDE_PATH
$ LIBRART_PATH=/opt/gdbm-1.8.3/lib
$ export LIBRART_PATH
这样,就如果以默认的路径编译了。(可以添加多个路径,路径之间用:相隔,.代表当前目录,若.在最前头,也可省略)
当然,若想永久地添加这些路径,可以在.bash_profile中添加上述语句。(还不如直接把include, lib文件装到/usr/local/..呢)
前面介绍了三种添加搜索路径的方法,如果这三种方法一起使用,优先级如何呢?
2 > 3 > 1
既:命令行设置 > 环境变量设置 > 系统默认
-----------------------------
利用GNU archiver创建库
$ ar cr libhello.a hello_fn.o by_fn.o
从hello_fn.o和by_fn.o创建libihello.a,其中cr表示:creat & replace
$ ar t libhello.a
列出libhello.a中的内容,t == table
(也可创建libhello.so)
----------------------------------------------------------------------
*** 编译预处理 ***
(1)除了直接在源代码中用 #define NAME来定义宏外,gcc可在命令行中定义宏:-DNAME(其中NAME为宏名)
(2)gcc的编译预处理命令程序为cpp,比较新版本的gcc已经将cpp集成了,但仍提供了cpp命令。
(3)除了用户定义的宏外,有一些宏是编译器自动定义的,它们以__开头,运行: $ cpp -dM /dev/null,可以看到这些宏,注意,其中含有不以__开头的非ANSI宏,它们可以通过-ansi选项被禁止。
(3)赋值宏选项: -DNAME=value 注意等号两边不能有空格!
(4)由于宏扩展只是一个替换过程,也可以将value换成表达式,但要在两边加上双括号:-DNAME="statement"
e.g. $ gcc -Wall -DVALUE="2+2" tmp.c -o tmp
(5)如果不显示地赋值,如上例子,只给出:-DVALUE,gcc将使用默认值:1.
查看宏扩展
(1)运行 $ gcc -E test.c ,gcc对test.c进行编译预处理,并立马显示结果。(不执行编译)
(2)运行 $ gcc -c -save-temps test.c ,不光产生test.o,还产生test.i, test.s,前者是编译预处理结果,后者是汇编结果。
利用Emacs查看编译预处理结果
针对含有编译预处理命令的代码,可以利用emacs方便地查看预处理结果,而不需执行编译,更为方便的是,可以只选取一段代码,而非整个文件:
1,选择想要查看的代码
2,C-c C-e (M-x c-macro-expand)
这样,就自动在一个名为"Macroexpansion"的buffer中显示pre-processed结果。
----------------------------------------------------------------------
*** 调试 ***
一般地,可执行文件中是不包含任何对源代码的参考的,而debugger要工作,就要知道目标文件/可执行文件中的机器码对应的源代码的信息(如:哪条语句、函数名、变量名...)。debugger工作原理:将函数名、变量名,对它们的引用,将所有这些对象对应的代码行号储存到目标文件或可执行文件的符号表中。
GCC提供-g选项,将调试信息加入到目标文件或可执行文件中。
$ gcc -Wall -g hello.c -o hello
注意:若发生了段错误,但没有core dump,是由于系统禁止core文件的生成!
$ ulimit -c ,若显示为0,则系统禁止了core dump
解决方法:
$ ulimit -c unlimited (只对当前shell进程有效)
或在~/.bashrc 的最后加入: ulimit -c unlimited (一劳永逸)
----------------------------------------------------------------------
*** 优化 ***
GCC具有优化代码的功能,代码的优化是一项比较复杂的工作,它可归为:源代码级优化、速度与空间的权衡、执行代码的调度。
GCC提供了下列优化选项:
-o0 : 默认不优化(若要生成调试信息,最好不优化)
-o1 : 简单优化,不进行速度与空间的权衡优化;
-o2 : 进一步的优化,包括了调度。(若要优化,该选项最适合,它是GNU发布软件的默认优化级别;
-o3 : 鸡肋,兴许使程序速度更慢;
-funroll-loops : 展开循环,会使可执行文件增大,而速度是否增加取决于特定环境;
-os : 生成最小执行文件;
一般来说,调试时不优化,一般的优化选项用-o2(gcc允许-g与-o2联用,这也是GNU软件包发布的默认选项),embedded可以考虑-os。
检验优化结果的方法:$ time ./prog
time测量指定程序的执行时间,结果由三部分组成:
real: 所有进程运行的时间;
user: 被测量进程的实际运行时间;
sys : 系统调用执行的时间;
注意:对代码的优化可能会引发警告信息,移出警告的办法不是关闭优化,而是调整代码。
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/15962/showart_99623.html |
|