C语言编译过程分析
一个.c文件的编译的全过程包括:
预处理(Preprocessing):在预处理阶段,预处理器会读取C语言源代码,对其中的预编译指令(如#include,#define,#pragma等)进行处理。这一步会展开宏定义、处理条件编译指令、插入头文件内容、删除注释等。处理后的代码文件通常以.i为拓展名,但该文件还是C代码。
bash
gcc -E hello.c -o hello.i
cpp hello.c -o hello.i
编译(Compilation):在编译阶段,编译器将预处理后的代码转换为机器可识别的语言,也就是汇编代码。这一步包括语法分析、语义分析、检查代码的规范性,确保没有语法错误。处理后的代码通常以.s为扩展名。
bash
gcc -S hello.i -o hello.s
汇编(Assembly):在汇编阶段,汇编器将编译阶段生成的汇编代码转换为机器码,生成目标文件,通常以.o为扩展名,但该文件还不能执行。这一步还会生成调试信息和符号表。
bash
gcc -c hello.s -o hello.o
链接(Linking):在链接阶段,链接器将目标文件转化为可执行程序。这一步涉及函数库的调用和解决外部符号的引用。处理可重定位文件,把各种符号引用和符号定义转换成可执行文件中的合适信息,通常是虚拟地址。(符号决议)
bash
gcc hello.o -o hello.out #.out为Linux下的常用扩展名
gcc hello.o -o hello.exe #.exe为Windows下常用拓展名
gcc指令使用
普通使用:
在上述分析过程中的每一个步骤,都可以直接使用gcc来直接将该阶段文件编译到最终结果:a.out
bash
gcc hello.c #将c语言代码文件直接编译成a.out("直接"是指:这四步自动连续执行,不会跳过步骤的)
gcc hello.c -o hello #在上一行的基础上 将默认的a.out文件更名为hello
常用选项:
【-E】:执行预处理
【-S】:执行预处理、编译
【-c】:执行预处理、编译、汇编
【无参】:执行预处理、编译、汇编、链接
【-o】:为生成的文件进行重命名。
【-Wall】:Warning All----提示所有警报信息
【-v / --v / --version】查看gcc版本号
【-g】:包含调试信息
【-On n=0~3】:编译优化,n越大优化的越多
【-D<DEF>】编译时定义宏,尖括号与D之间没有空格
【-M】生成.c文件与头文件依赖关系以用于makefile,包括系统库的头文件
【-MM】生成.c文件与头文件的依赖关系以用于makefile,不包括系统库的头文件
这里我们可以看出,-S、-c等选项不止执行单步的操作,还会将前面的操作连带着进行下来。
【-v】选项:
只适用于查看版本号,给大家演示一下就过了:
【-Wall】选项:
我们可以看到,明显的问题就是:打印时没有给占位符传入一个数据会报错。我们编译一下试一下:
但是如果我们打开开启所有警报的选项后:
这些小的问题:未使用的变量,也会给出警报信息。
【-g】选项:
开启-g选项生成的文件包含调试信息,可以被gdb调试器进行调试,也就是我们所谓的断点调试的调试功能。如果没有-g选项,我们就无法进行调试,因为内部没有调试信息符号,不能被gdb识别。
我们可以看到hello文件和hello2文件都是由main.c通过gcc编译出来的文件,但二者大小有所差异,这个差异就是调试信息,但我们看不到。
【-On】选项:
编译优化,默认等级n=2。如果想关闭优化,那么就加上选项-On 0。
什么是优化呢?
假如一个文件中:
cpp
int a=1;
a=0;
a=1;
a=0;
a=0;
a=1;
中间的哪些会被优化掉,最后只剩下:int a=1;a=1;
但是在嵌入式中,我们想使用a控制灯的开灭,那么优化掉的话,就不能准确表达中间状态了。此时就需要关闭优化。
【-D<DEF>】选项
该选项目的是向文件中注册一个宏。
在一个文件中:
cpp
#include<stdio.h>
#ifdef HELLO
#define HI 20
#endif
int main(){
printf("%d",HI);
return 0;
}
如果我们直接编译,编译器不认识HI这个宏,因为HI只有在HELLO这个宏被定义时才会被定义。
但是我们又不想在文件中定义,那么就可以使用选项向文件注册一个宏。多用于开发阶段。
分文件编译--情况一:
这个情况是:调用外部函数
如果在一个文件中写了一个函数,想在另一个函数中使用,那么我们可以这么使用:
第一步:建立被调用函数所在文件:thank.c
第二步:在调用的文件hello.c中,声明一下调用的函数。
第三步:在调用的文件hello.c中的main函数中,调用thank函数
第四步:使用gcc,分别传入thank.c和hello.c
bash
gcc thank.c hello.c
#使用 -o选项可以为生成的可执行文件起名字
分文件编译--情况二:
这个情况是:包含头文件
如果我们使用头文件与源文件分离的方式编写代码,编译时我们可能会遇到的情况:
首先,我们在hpp文件夹下创建一个头文件和一个main.c文件,内容如下:
正常情况下,我们对main.c进行编译:
但是,假如我们的头文件没有放在当前文件夹下的话,就会出现问题,为了分析这个问题,我们将头文件放到上一层文件夹去:
此时,我们已经模拟出了头文件与源程序文件不在同一文件夹下的情况。我们现在尝试重新编译一下main.c文件:
此时我们可以看到,提示main.c文件中有错误,找不到thanks.h文件,程序的执行被中断。那么就必须要将两个文件放到一个文件夹下吗?不是的,我们可以使用选项来执行包含的头文件的路径:
这里有一个选项:-I(大写的 i),后面是头文件所在的路径,选项与路径之间不一定需要空格,取决于个人习惯。-I的位置也可以放在紧跟gcc后面,也可以放在gcc所有参数后面,位置不限制。路径可以是相对路径也可以是绝对路径。
感谢大家!!!