了解gcc编译过程
日常我们开发完程序,可能是直接用gcc xxx.c -o xxx,或者是makefile来完成编译,有时我们还会遇到无法链接库文件等问题,那么gcc从源文件到到可执行文件的过程中究竟发生了什么,这次我们来简单探讨下。
首先,我们编写个简单的c程序。
1 2 3 4 5 6 7 8 | //0720.c #include #include main(){ int a=1; printf("%d",a+a++); } |
如下面的流程图,我们按照“预处理—>编译—>汇编—>链接”四个步骤,一步步操作看看每一步的得到的是什么。
①预处理:gcc将头文件载入、常量进行置换,形成一个新的文件0720.i
②编译:编译阶段gcc会检查代码的规范性,语法词法等,以确保程序无误,在这一步执行后gcc会生成一个汇编文件0720.s
③汇编:汇编阶段则是把我们的汇编文件给操作系统看,然后生成二进制的目标文件0720.o
④链接:如果能成功经过前面三个阶段得到目标文件,说明我们的代码逻辑是没问题的,现在最后一步就是需要把我们的目标文件中所依赖的库文件链接起来,换句话说就是能找到库文件中函数具体定义的位置,这样我们的可执行文件才能正确调用到库函数。
上面我们讲到了头文件和库文件,那么这两者之间有什么差别呢?简单来说,头文件是以.h结尾,ASCII编码的文件;而库文件则是以.a(静态库)或.so(动态库)结尾,二进制的文件。日常我们编码时只需要引入头文件,而头文件里面也仅仅是对库函数进行声明,并不会有具体的函数内容,这样我们在编译的时候编译器gcc就能知道我们的代码中函数的原型是怎样的,并不会具体关心库函数是怎么样实现的。在链接的时候,就需要把库函数带上了,这样生成的可执行文件才能正确调用到库文件中的具体函数的位置。我们日常中最常用的函数标准库是libc.so.6,类似<stdio.h>、<string.h>都在这个库下。上面说到库也分静态库和动态库,具体的可以看这篇文章Linux静态库与动态库。
了解清楚编译和链接的区别后,接下来我们看看日常gcc会遇到的几个问题:
1、undefined reference to ‘xxx’.
2、/usr/bin/ld:cannot find -lxxx.
3、xxx.h:No such file or directory.
首先,这几个问题都不是编译错误,是链接错误,也就是如果出现的是这几个错误,说明你的源程序本身没有问题,是你的编译选项用的不对或者缺少相关的库文件或者头文件。前两个问题是找不到库文件的问题,后一个问题是找不到头文件的问题。gcc的三个参数能够很好的解决这一类问题,-I(大写I)、-L、-l(小写l)。
例:gcc -o hello hello.c -I /home/hello/include -L /home/hello/lib -lworld
-I /home/hello/include表示将/home/hello/include目录作为第一个寻找头文件的目录,寻找的顺序是:/home/hello/include–>/usr/include–>/usr/local/include;-L /home/hello/lib表示将/home/hello/lib目录作为第一个寻找库文件的目录,寻找的顺序是:/home/hello/lib–>/lib–>/usr/lib–>/usr/local/lib; -lworld表示在上面的lib的路径中寻找libworld.so动态库文件(如果gcc编译选项中加入了“-static”表示寻找libworld.a静态库文件)。
文章参考:
“undefined reference to” 问题解决方法
Greenplum常用语法记录 linux下的头文件和库文件搜索路径