1、为什么要了解编译
不了解这个,怎么写CMakeLists.txt呀。
2、那什么是编译?
编译是指一个源代码文件(这里指的是编译型程序源文件,与之对应的是解释型程序),如C/C++文件要经过预处理(preprocessing)、编译(compliation)、汇编(assembly)和链接(linking)等4步才能变成可执行文件,在日常交流中通常使用“编译”统称这四个步骤。下面详细介绍这四个步骤。
也就是说,源代码文件是人写出来的,而可执行文件是用来给计算机执行的,将源代码向可执行文件的转换过程就是编译喽。
3、编译的四个步骤(从源文件到可执行文件的四个步骤)
1、预处理(preprocessing)
问:什么是预处理?
C/C++源文件中,以“#”开头的命令就是预处理命令,如包含命令“#include”、条件编译命令“#if”、“ifdef”、宏定义命令“#define”等。预处理就是将要包含(include)的文件插入源文件中、根据条件编译命令选择要使用的代码、将宏定义展开,最后将这些代码输出到一个“.i”文件,也就是说还是源代码文件。
问:为什么需要预处理?
一般预处理都是些简单的替换、拷贝和选择,这些涉及多个文件预处理的结果是将每个源文件所需要的代码都放在自己文件里,然后方便下一步处理(ps:因为编译时,编译器每次读入一个文件,输出一个文件,不支持同时处理多个文件-来源于编译原理书籍)
2、编译(compilation)
问:什么是编译?
编译就是把C/C++代码(比如上述的“.i”文件)翻译成汇编代码,生成“.s”文件,这部分设计复杂的编译原理。
问:为什么要翻译成汇编,而不直接生成机器码?
原因有两个:
1、最开始的底层开发语言是汇编,而高级语言是在底层语言基础上发展的,自然而然会将成熟的工具(汇编器)利用起来,同时实现软件分层可以有效地减弱编译器编写的负责性,“编译”所拆分的四大步骤也是如此道理。
2、方便优化和调试,汇编语言是机器指令的助记符,一个汇编指令就对应一条机器指令,因此汇编语言更贴近机器特性,因此比高级语言调试起来更有优势。(这里我不是很懂)
3、汇编(assembly)
问:什么是汇编
汇编(注意这里的汇编指的是编译器的一个编译动作,不是汇编语言),是利用汇编器将第二步输出的汇编代码翻译成符合一定格式的机器代码,就是我们熟悉的目标文件(*.o),在Linux系统一般变现为ELF格式文件。
可以使用readelf工具查看:
readelf -a hello.o
4、链接(linking)
问:什么是链接?
ELF 全称 “Executable and Linkable Format”,即可执行可链接文件格式,目前常见的Linux、 Android可执行文件、共享库(.so)、目标文件( .o)以及Core 文件(吐核)均为此格式。
链接就是将汇编生成的OBJ文件、系统库的OBJ文件、库文件链接起来,也就是将各个ELF文件重新排序成一个ELF文件,最终生成可以在特定平台运行的可执行程序。(万剑归一,一个程序本省就可以写成一个文件)
查看OBJ文件,需要用到readelf工具:(假设hello是汇编生成的OBJ文件)
readelf -a hello
问:什么是系统库文件?
系统库文件:一个应用程序要运行在系统上,就需要系统标准启动文件,提供给系统用的。
一般gcc自动加入的系统标准启动文件有:crt1.o、crti.o、crtbeigin.o、crtend.o、crtn.o。对于一般应用程序,这些启动是必须的。
通过查看gcc详细编译过程可以看到
问:什么是标准库文件?
就是库文件啦,如果代码用到标准库函数,而gcc集成了常用的库,链接时自动检索加入哦。
问:什么是动态链接?
动态链接使用动态链接库进行链接,生成的程序在执行的时候要加载所需的动态库才能运行。动态链接生成的程序体积较小,但是必须依赖所需的动态库,否则无法执行。
gcc默认使用动态库链接
问:什么是静态链接?
静态链接使用静态库进行链接,生成的程序包括程序运行所需要的全部库,可以直接运行。不过静态链接生成的程序体积较大。
3、编译需要什么工具?
gcc是运行在PC平台上的一种编译工具链,其编译出来的程序在x86平台上运行。
4、编译工具gcc的简单使用
gcc选项很多,一般大型开发项目都会使用很多控制选项,这里仅介绍简单常用选项:
-v:查看gcc编译器的版本,显示gcc执行时的详细过程
-o <file> Place the output into <file> (指定输出文件名为file,这个名称不能跟源文件名同名)
-E Preprocess only; do not compile, assemble or link(只预处理,不会编译、汇编、链接)
-S Compile only; do not assemble or link(只编译,不会汇编、链接)
-c Compile and assemble, but do not link(编译和汇编,不会链接 )
假如我们现在写好了源代码文件hello.c,想生成一个可执行文件
1)方式1:四个步骤
gcc -E -o hello.i hello.c
gcc -S -o hello.s hello.i
gcc -c -o hello.o hello.s
gcc -o hello hello.o
小结:
输入文件的后缀名和选项共同决定gcc到底执行哪些操作。
在编译过程中,除非使用了-E、-S、-c选项(或者编译出错阻止了完整的编译过程),否则最后的步骤都是链接。
gcc会对.c文件默认进行预处理操作,-c再来指明编译、汇编,从而得到*.o文件(object file,就是我们常说的目标文件啦),再通过gcc -o hello hello.o将.o文件进行链接,得到可执行应用程序。所以有了方式2:
2)方式2-开发项目中常用的方式(写在CMakeLists.txt中?)
gcc -c -0 hello.o hello.c
gcc -o hello hello.o
3)方式3
gcc -o hello hello.c 输出hello,然后./hello来执行该应用程序
注:如果不指定输出文件名,则默认生成a.out(命令行常用吧?)
gcc hello.c 输出一个a.out,然后./a.out来执行该应用程序
“编译”的四个步骤总结如下表:
操作 | 说明 | 输出文件 | 输入文件 |
---|---|---|---|
预处理 gcc -E -o | 对源代码处理 | hello.i | hello.c |
编译 gcc -S -o | 源代码转汇编代码 | hello.s | hello.i |
编译及汇编 gcc -c -o | 汇编代码转机器代码,即OBJ文件 | hello.o | hello.s |
链接 gcc -o | 汇编得到的OBJ文件、系统库的OBJ文件、库文件链接起来,各个ELF文件重新排序(万剑归一) | hello | hello.o |
5、编译与交叉编译有啥不一样?
当我们开发目标是一个嵌入式设备时,便需要在PC机上编译出能在该嵌入式设备上运行的可执行文件,这里编译主机与目标运行主机不是同一个设备,那么该过程就称为交叉编译。
要编译出能在ARM平台上运行的程序,则须使用交叉编译工具arm-linux-gcc、arm-linux-ld、arm-linux-objcopy、arm-linux-objdump等。
PC与ARM的编译工具使用方法一致,单纯就是名字不一样。当然喽,有些开发工具,将编译工具集成其中,熟悉这些开发工具本身就是开发过程中必要的一部分。
下图是编译和交叉编译的图解:
参考: