Linux之gcc编译器

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所有参数后面,位置不限制。路径可以是相对路径也可以是绝对路径。


感谢大家!!!

相关推荐
m0_748257186 分钟前
海康威视摄像头RTSP使用nginx推流到服务器直播教程
运维·服务器·nginx
Pou光明29 分钟前
1_linux系统网络性能如何优化——几种开源网络协议栈比较
linux·运维·网络·网络协议·开源
fen_fen1 小时前
Docker如何运行一个python脚本Hello World
运维·docker·容器
TianyaOAO2 小时前
inmp+discuz论坛
linux·运维·服务器
星光璀璨山河无恙2 小时前
【Linux】grep命令
大数据·linux
寒月6582 小时前
黑盒白盒测试
运维·服务器
I love this bad girl2 小时前
防火墙旁挂部署+故障切换
服务器·网络·数据库
echo爱学易语言2 小时前
Linux ufw命令丨Linux网络防火墙ufw命令详解
服务器·网络·数据库
海绵波波1072 小时前
zerotier实现内网穿透(访问内网服务器)
运维·服务器
CL_IN2 小时前
金蝶云星空与华为云AX无缝数据集成技术详解
服务器·前端·华为云