c/c++语言基础——编译跟链接
用惯了图形化的IDE,对编译和链接部分的东西知道的病不多,这里补习一下。
1. 首先编译和链接是两个东西,虽然在IDE上按一个键结果就出来了,这是因为IDE在后台自己做了不少的事情。
2. 常见的编译器有gcc/g++,前者是给c用的,后者是给c++用的,两者的编译操作差别不大;常见的连接器有ld。
3. gcc会把链接的事情也做掉(除非加参数),所以对ld的了解更少...
下面以gcc和ld为例说明c语言(c++也差不多,只是要换编译器为g++)的编译和链接。
首先看下面的一个最简单的例子:
#include <stdio.h> int main(int argc, const char * argv[]) { printf("Hello, World!\n"); return 0; }在IDE下,比如这边使用的Xcode,只要Cmd+R,就可以直接得到运行结果了:
虽然不明白IDE打的做了什么,但是觉得很厉害的样子。
对大部分只关注代码实现的人来说,能够正确使用就OK了,但是如果希望进一步了解代码的编译和链接,则需要关掉IDE,打开shell,来探索gcc跟ld了。
gcc的基本格式如下:
gcc [option] files
先不管参数部分,下面是对上面的main.c代码运行gcc的结果:
可以看到也能得到与Xcode执行时一样的结果。
连ld都省了。
到底gcc做了些什么,可以参考Journey of a C Program to Linux Executable in 4 Stages这篇文章。
接下来需要介绍一些常用的gcc选项,上面的例子中,虽然我们没有使用编译选项,但是gcc还是会根据默认的选项值来进行编译。
当我们了解了gcc的编译选项,就可以更好的控制编译的过程。
下面就是一些常用的选项:
-o file:给输出文件命名,在上例中,我们没有指定名称,所以使用了默认的a.out,如果你指定了名称,比如下面这样,就会有不同的结果:
-Wall:这个是用来显示编译时的告警的。比如在我们的代码中添加一个声明了但未使用的变量:
如果不加-Wall参数,就不会报错。
开启-Wall可以让上报一些我们自己没有发现的问题,值得推荐。
-Werror:这个选项的意思是把编译中的warning当作错误。上面的例子中,虽然-Wall之后编译时会有提示,但是最终还是会生成a.out,即整个编译过程是会运行完成的,而如果又加了-Werror,在编译过程中就报错并终止了:
-Wall -Werror一起使用,能够不放过任何的编译问题。
-S:用来生成对应的汇编代码:
查看汇编代码:
可以看到生成的汇编代码是AT&T格式的,但是如果只知道Intel的汇编格式呢?可以再加上-masm=intel这个参数:
这样生成的就是intel格式的汇编代码了:
现在需要用到汇编的机会不多,除非是性能要求需要优化代码,或者debug的时候才会去用吧。
-E:加入了-E选项后,gcc并不进行编译,而是进行预编译,就是把宏进行替换、头文件展开等。在原来的代码上加一个宏,下面是执行-E后的情况:
-c:指定这个选项后,gcc只负责编译,但是不会进行链接:
生成的.o文件需要经过链接才能成为可执行的文件,这里就需要用到ld了,先不介绍。
-save-temps:使用这个命令可以保留在gcc编译和链接过程中的中间文件:
这些中间文件对于了解gcc的工作过程很有帮助。很多文件在之前已经介绍过了,另外的,main.i就是预处理后的代码,main.bc是什么还不清楚...
-l和-L:这两个是用来指定库的,前者指定库名,后者指定库的路径,可以参考http://blog.****.net/jiangwei0512/article/details/51559030的说明。
-v:加上这个参数可以在编译时看到不少额外的信息:
-D:后接宏来控制代码的走向。
#include <stdio.h> #define MAX 255 int main(int argc, const char * argv[]) { #ifdef ERROR aaaa; #endif int i = MAX; printf("Hello, World!\n"); return 0; }上面的代码中,ERROR宏控制的代码是错的,如果使用-DERROR,则在编译的时候就会包含aaaa这样的代码,导致编译错误:
而不加-D,就不会报错。
这里需要注意下-D和宏之间没有空格。
编译参数介绍到这里,下面需要介绍链接器ld。
ld和gcc使用的方式差不多,也是ld +选项 +文件。
这里的文件需要时.o文件,即gcc -c编译后的文件。
ld最重要的作用当然是生成可执行的文件。但是还有一个非常重要的作用,就是找到我们的代码中并没有定义的那些函数,然后在当前平台上找到相应的实现。
比如我们的代码中有一个printf,它是c标准库中实现的,所以链接时就需要找到相应的库,并获取到printf的实现代码。
下面是链接的操作过程:
首先,当然要生成.o文件,参见前面讲-c时的图片;
然后,像使用gcc一样,什么参数也不加来执行下:
发现有错误,并没有生成可执行文件。
前两个warning是针对mac的,通过加参数-macosx_version_min就可以解决。
后一个错误是因为没有找到_printf的实现,这是因为我们没有指定库。
下面是修改后的执行命令:
可以看到能够生成a.out了,但是还是有报之前的warning,原因是因为没有指定系统架构,修改成下面的形式:
就可以得到我们需要的结果了。
下面也介绍下ld常用的选项(像-macox_verson_min这种就不说了):
-arch:用来指定系统架构,比如Intel 32位机是i386,Intel 64位机是x86_64。
-lx:对应上面的命令就是-lc,表示的是寻找libc.a或者libc.dylib这样的库,因为printf就在这些库中实现的。
-e:用来指定入口,默认是_main,对应到代码中就是main,所以我们不需要显式得指定,当然指定也没有关系:
从这里我们也可以得出一个设想,即是否可以将main换成其它名字,例如start:
#include <stdio.h> int start(int argc, const char * argv[]) { int i; printf("Hello, World!\n"); return 0; }下面是编译和链接的命令:
可以看到确实是可行的!虽然并没有多少实际的意义...
以上就是对gcc和ld的介绍,它们可以带的选项还有很多,要了解具体所有选线,可以在shell下使用man gcc/ld来查看。
PS:发现一个问题,在mac中gcc被指向了clang编译器,所以上面的gcc操作实际上都是clang的操作......不过基本的功能都没有变,参数在gcc中也能够正常使用,所以基本上也算是gcc的说明吧......