本文共 9775 字,大约阅读时间需要 32 分钟。
The History of GCC
警告信息
C语言标准
生成特定格式的文件
以hello.c为例子,可以设置选项生成hello.i, hello.s, hello.o以及最终的hello文件:
如果你不通过-o指定生成可执行文件名,那么会默认生成a.out. 不指定生成文件名肯能覆盖你上次生成的a.out. e.g. $-c生成.o文件时,默认生成与源代码的主干同名的.o文件。比如对应hello.c生成hello.o. 但也可在生成目标文件时指定目标文件名(注意同时要给出.o后缀): $gcc -c -o demo.o demo.c |
多文件编译、连接
如果原文件分布于多个文件中:file1.c, file2,c
若对其中一个文件作了修改,则可只重新编译该文件,再连接所有文件:
注意:若编译器在命令行中从左向右顺序读取.o文件,则它们的出现顺序有限制:含有某函数定义的文件必须出现在含有调用该函数的文件之后。好在GCC无此限制。
编译预处理
包含头文件
下面看这个求立方的小程序(阴影语句表示刚开始不存在):
#include <stdio.h> #include <math.h> int main(int argc, char *argv[]) { |
使用gcc-2.95来编译它(-lm选项在后面的连接选项中有介绍, 这里只讨论头文件的包含问题):
$程序编译成功,但gcc给出警告: pow函数隐式声明。
$明显执行结果是错误的,在源程序中引入头文件(#include <math.h>),消除了错误。
不要忽略Warning信息!它可能预示着,程序虽然编译成功,但运行结果可能有错。故,起码加上"-Wall"编译选项!并尽量修正Warning警告。 |
搜索路径
首先要理解 #include<file.h>和#include"file.h"的区别:
#include<file.h>只在默认的系统包含路径搜索头文件 #include"file.h"首先在当前目录搜索头文件, 若头文件不位于当前目录, 则到系统默认的包含路径搜索头文件.UNIX类系统默认的系统路径为:
头文件,包含路径: /usr/local/include/
对于标准c库(glibc或其它c库)的头文件, 我们可以直接在源文件中使用#include <file.h>来引入头文件.
如果要在源文件中引入自己的头文件, 就需要考虑下面的问题:
1, 如果使用非系统头文件, 头文件和源文件位于同一个目录, 如何引用头文件呢?
——我们可以简单地在源文件中使用 #include "file.h", gcc将当前目录的file.h引入到源文件. 如果你很固执, 仍想使用#include <file.h>语句, 可以在调用gcc时添加"-I."来将当前目录添加到系统包含路径. 细心的朋友可能会想到: 这样对引用其它头文件会不会有影响? 比如, #include<file.h>之后紧接着一个#include<math.h>, 它能正确引入math.h吗? 答案是: 没有影响. 仍然能正确引用math.h. 我的理解是: "-I."将当前目录作为包含路径的第一选择, 若在当前目录找不到头文件, 则在默认路径搜索头文件. 这实际上和#include"file.h"是一个意思.2, 对于比较大型的工程, 会有许多用户自定义的头文件, 并且头文件和.c文件会位于不同的目录. 又该如何在.c文件中引用头文件呢?
—— 可以直接在.c文件中利用#include“/path/file.h", 通过指定头文件的路径(可以是绝对路径, 也可以是相对路径)来包含头文件. 但这明显降低了程序的可移植性. 在别的系统环境下编译可能会出现问题. 所以还是利用"-I"选项指定头文件完整的包含路径.针对头文件比较多的情况, 最好把它们统一放在一个目录中, 比如~/project/include. 这样就不需为不同的头文件指定不同的路径. 如果你嫌每次输入这么多选项太麻烦, 你可以通过设置环境变量来添加路径: $ |
3, 还有一个比较猥琐的办法: 系统默认的包含路径不是/usr/include或/usr/local/include么? 我把自己的头文件拷贝到其中的一个目录, 不就可以了么? 的确可以这样, 如果你只想在你自己的机器上编译运行这个程序的话.
前面介绍了三种添加搜索路径的方法,如果这三种方法一起使用,优先级如何呢? 命令行设置 > 环境变量设置 > 系统默认与外部库连接
有一个例外: libc.a或libc.so (C标准库,它包含了ANSI C所定义的C函数)是不需要你显式连接的, 所有的C程序在运行时都会自动加载c标准库. |
两者的共同点:
两者的区别:
静态库
下面我们来看一个简单的例子,计算2.0的平方根(假设文件名为sqrt.c):
#include <math.h> #include <stdio.h> int main (void) { double x = sqrt (2.0); printf ("The square root of 2.0 is %f\n", x); return 0; } |
用gcc将它编译为可执行文件:
$现在我用2.95版的gcc把sqrt.c再编译一次:
$使用下列的命令可以成功编译:
$C库文件默认位于/usr/lib, /usr/local/lib系统目录中; gcc默认地从/usr/local/lib, /usr/lib中搜索库文件。(在我的Ubuntu系统中,C库文件位于/urs/lib中。 |
这里还要注意连接顺序的问题,比如上述命令,如果我改成:$
正如读取目标文件的顺序,gcc也在命令行中从左向右读取库文件——任何包含某函数定义的库文件必须位于调用该函数的目标文件之后!
指定库文件的绝对路径比较繁琐,有一种简化方法,相对于上述命令,可以用下面的命令来替代: $
上面所提到的"libm.a"就是静态库文件,所有静态库文件的扩展名都是.a!
$正如前面所说,默认的库文件位于/usr/lib/或/usr/local/lib/目录中。其中,libm.a是静态库文件,libm.so是后面会介绍的动态共享库文件。
如果调用的函数都包含在libc.a中(C标准库被包含在/usr/lib/libc.a中,它包含了ANSI C所定义的C函数)。那么没有必要显式指定libc.a:所有的C程序运行时都自动包含了C标准库!(试试 $ gcc-2.95 -Wall hello.c -o hello)。
共享库
正因为共享库的优点,如果系统中存在.so库,gcc默认使用共享库(在/usr/lib/目录中,库文件以共享和静态两种版本存在)。
运行:$
正如前面所说,共享库以.so为扩展名(so == shared object)。
那么,如果不想用共享库,而只用静态库呢?可以加上 -static选项
$$ gcc-2.95 -Wall sqrt.c -static -lm -o sqrt_2.95_static
$ gcc-2.95 -Wall sqrt.c -lm -o sqrt_2.95_default $ gcc-2.95 -Wall sqrt.c /usr/lib/libm.a -o sqrt_2.95_a $ gcc-2.95 -Wall sqrt.c /usr/lib/libm.so -o sqrt_2.95_so$ ls -l sqrt*
-rwxr-xr-x上述用四种方式编译sqrt.c,并比较了可执行文件的大小。奇怪的是,-static -lm 和 /lib/libm.a为什么有区别?有知其原因着,恳请指明,在此谢谢了! :)
如果libNAME.a在当前目录,应执行下面的命令:
$利用GNU archiver创建库
$
关于创建库的详细介绍,可参考本blog的
调试
GCC提供-g选项,将调试信息加入到目标文件或可执行文件中。
$解决方法:
$优化
GCC提供了下列优化选项:
注意:此处为O!(非0或小写的o,-o是指定可执行文件名)。
检验优化结果的方法:$ time测量指定程序的执行时间,结果由三部分组成:
user和sys的和被称为CPU时间.
注意:对代码的优化可能会引发警告信息,移出警告的办法不是关闭优化,而是调整代码。