推荐文章:https://blog.csdn.net/chenlong_cxy/article/details/119183448
最近在学linux相关的操作,接触到makefile文件,在此做个笔记
gcc
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708105538938.png)
gcc常用命令选项:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708131200295.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1MDI3Njkw,size_16,color_FFFFFF,t_70#pic_center)
刚接触linux时,关于gcc编译器只会用形如 gcc hello.c
的命令来对代码进行编译(实际上这个命令是一键完成上面四个编译过程的),对整个编译过程还不了解,以至于后面在接触makefile的编写时出问题。现在先来看一看gcc的基础知识:
如上图所示,c程序的编译过程分为以上四个步骤,这四个步骤在c基础中也学过,但过于理论导致一知半解:
- 预处理:包括(宏替换,头文件展开,去掉注释),生成.i文件,(看内容实际上也是.c文件)
- 编译 :检查代码规范,语法错误等,生成汇编文件
- 汇编:转成二进制目标代码
- 链接:生成最终可执行文件
1.预处理
使用命令:gcc -E hello.c –o hello.i
,得到预处理后的文件,这里的-o是重命名的意思。如下
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708113311393.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1MDI3Njkw,size_16,color_FFFFFF,t_70)
可以用vim打开hello.i文件,可看到源代码本只有几行,经过预处理后得到几百行。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708122504547.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1MDI3Njkw,size_16,color_FFFFFF,t_70#pic_center)
2. 编译
使用命令:gcc -S hello.i -o hello.s
,生成汇编文件。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708122730353.png)
用vim打开可见:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708122846548.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1MDI3Njkw,size_16,color_FFFFFF,t_70#pic_center)
3.汇编
使用命令:gcc -c hello.s -o hello.o
,生成二进制文件,但还不能直接运行,这里-c命令是将代码转换为二进制语言,不进行链接。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708123329504.png)
关于-c这个命令,参考gcc 和 gcc -c有什么区别呢
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020070813103429.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1MDI3Njkw,size_16,color_FFFFFF,t_70)
4.链接
使用命令:gcc hello.o
。尽管3中得到了二进制文件,但还是不能直接执行,需要通过链接,得到a.out文件,才能运行。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708123735536.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1MDI3Njkw,size_16,color_FFFFFF,t_70)
makefile
了解了上述gcc编译过程之后,接下来就可学习makefile了。
先给出测试例子–三个.c文件:
main.c :
#include <stdio.h>
extern void fun1();
extern void fun2();
int main()
{
printf("main\n");
fun1();
fun2();
return 0;
}
fun1.c:
#include <stdio.h>
void fun1()
{
printf("fun1()\n");
}
fun2.c:
#include <stdio.h>
void fun1()
{
printf("fun2()\n");
}
不使用makefile
单纯的在shell界面用gcc,可以做到对以上三个文件的一起编译,如下图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708154814432.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1MDI3Njkw,size_16,color_FFFFFF,t_70)
但是,假如我只对其中的某一个文件进行修改,再次编译时仍然需要对以上三个文件都重新编译,当文件数特别多的时候,这将做很多重复工作,花很大时间。
makefile格式
1 target: prerequisites
2 commands //注意这里为Tab缩进
翻译一下就是
目标文件 :依赖项
命令
**即是用依赖项生成对应的目标文件,而依赖项是通过下面的命令生成的。
![在这里插入图片描述](https://img-blog.csdnimg.cn/89eb614b2986423c8fd605f0f7158d46.png)
第一个makefile
先看最简单的一种情形**,新建一个名为makefile文件,输入以下内容:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708154236131.png#pic_center)
再在终端输入make后得到以下结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708155358430.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1MDI3Njkw,size_16,color_FFFFFF,t_70#pic_center)
可看出,这种makefile文件同只有gcc的一样,是对所有的文件进行编译,特别耗时。
常用规则
注意到,上述第一个makefile文件是直接使用 gcc 指令对多个.c源文件直接生成源文件,这在实际中用的很少。一般是先用源文件汇编生成对应的.o二进制文件(前面有介绍),再将多个二进制文件链接生成可执行文件。
看第二个makefile
通过拆分,现在想,修改哪个文件,就只编译哪个文件,未修改的不参加编译。如下图,第一个目标cpp为终极目标,下面的为子目标。当我们想利用依赖项生成目标cpp时,会先查找后面的依赖项main.o ,没有就会向下查找对应的规则生成main.o依赖项。后面同理。即makefile向下检索,子目标是用来生成终极目标的依赖项,然后依次往上执行命令。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708161024377.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1MDI3Njkw,size_16,color_FFFFFF,t_70#pic_center)
终端执行后可看到其执行顺序为:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708161957462.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1MDI3Njkw,size_16,color_FFFFFF,t_70#pic_center)
这时修改fun2.c文件中一点点内容,重新make,可看到只是编译了fun2.c文件。原因在于
makefile更新目标文件是比较时间的,即比较依赖项和目标文件的修改时间,依赖文件修改时间新于目标文件,则更新目标文件。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708163322218.png)
以fun2.c为例,如上图,fun2.c变化前,fun2.o生成时间是晚于fun2.c; fun2.c变化后,通过比较修改时间,发现fun2.o的时间是早于fun2.c,这时说明fun2.c已修改,就需要执行上图所示命令生成新的子目标文件fun2.o,即重新编译fun2.c文件。而其他的文件对应的.c和.o时间无变化,故不需要重新编译。参考视频:C++ linux 服务器开发基础—makefile工作原理
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200708162205225.png#pic_center)
上述makefile代码冗余还很严重,可以通过变量简化该代码,具体的可见视频。