关于make及makefile的工作笔记

之前一直是用java的,最近工作中需要在Linux中写一个C++程序,之前的写法很不规范,只有一个CPP。记录一下关于makefile的相关知识
想要完整的了解相关内容,推荐看这本书《程序员的自我修养--链接、装载与库》
想要快速的了解可以看这个大神写的博客https://seisman.github.io/how-to-write-makefile/overview.html
写的比较早了,可以挑着看

为什么使用make和makefile

在linux系统中make是一个非常重要的编译命令,不管是自己进行项目开发还是安装应用软件,我们都经常要用到make或makeinstall。利用make工具,我们可以将大型的开发项目分解成为多个更易于管理的模块,一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。makefile 带来的好处就是“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率

make

1.make的工作原理

当make 命令被执行时,它会扫描当前目录下Makefile或makefile文件找到目标以及其依赖。如果这些依赖自身也是目标,继续为这些依赖扫描Makefile 建立其依赖关系,然后编译它们。一旦主依赖编译之后,然后就编译主目标,假设你对某个源文件进行了修改,你再次执行make 命令,它将只编译与该源文件相关的目标文件,因此,编译完最终的可执行文件节省了大量的时间

## 2.make命令的参数

-f:指定“makefile”文件;

-i:忽略命令执行返回的出错信息;

-s:沉默模式,在执行之前不输出相应的命令行信息;

-r:禁止使用build-in规则;

-n:非执行模式,输出所有执行命令,但并不执行;

-t:更新目标文件;

-q:make操作将根据目标文件是否已经更新返回"0"或非"0"的状态信息;

-p:输出所有宏定义和目标文件描述;

-d:Debug模式,输出有关文件和检测时间的详细信息。

-C dir:在读取makefile 之前改变到指定的目录dir;

-I dir:当包含其他makefile文件时,利用该选项指定搜索目录;

-h:help文挡,显示所有的make选项;

-w:在处理makefile之前和之后,都显示工作目录。

3.make命令的隐藏过程

这部分在上面那本书里有很详细的解释,但是有好几百页,想了解的话自己看吧。

1.预编译

预编译过程相当于执行
gcc -E hello_world.c -o hello_world.i

预编译过程主要处理以#开头的预处理指令,#include #define等,处理过程如下:

1 将所有#define删除,并展开所有宏定义

2 处理所有条件预编译指令#if #ifdef #elif #else #endif等

3 处理#include 预编译指令,将包含的文件插入到预编译指令的位置(递归进行,所包含的文件可能包含其他文件)

4 删除所有注释// /* */

5 添加行号和文件名标识,如:#2 hello_kitty.c 2 ,用于编译时编译器产生调试信息和编译时产生的错误和警告时能显示行号。

6 保留说有的#pragma编译器指令

经过预编译的.i文件,不包含任何宏定义,并且所包含的文件也被插入进来。

宏定义:https://www.cnblogs.com/zhizhiyu/p/10155614.html

make命令的隐藏过程

1.预编译

预编译过程相当于执行
gcc -E hello_world.c -o hello_world.i

预编译过程主要处理以#开头的预处理指令,#include #define等,处理过程如下:

1 将所有#define删除,并展开所有宏定义

2 处理所有条件预编译指令#if #ifdef #elif #else #endif等

3 处理#include 预编译指令,将包含的文件插入到预编译指令的位置(递归进行,所包含的文件可能包含其他文件)

4 删除所有注释// /* */

5 添加行号和文件名标识,如:#2 hello_kitty.c 2 ,用于编译时编译器产生调试信息和编译时产生的错误和警告时能显示行号。

6 保留说有的#pragma编译器指令

经过预编译的.i文件,不包含任何宏定义,并且所包含的文件也被插入进来。

宏定义:https://www.cnblogs.com/zhizhiyu/p/10155614.html

2.编译

编译过程就是将预编译产生的.i文件经过一系列的词法分析,语法分析,语义分析,中间代码生成,目标代码生成与优化。

编译原理

3.汇编

汇编器将汇编代码转换成成机器指令,每一条汇编语句对应一条或几条机器指令,生成目标文件。

4.链接(模块拼装)

将库文件与目标文件链接成可执行文件的过程。

概念

动态链接库:是一种不可执行的二进制程序文件,它允许程序共享执行特殊任务所必需的代码和其他资源。Windows平台上动态链接库的后缀名是”.dll”,Linux平台上的后缀名是“.so”。Linux上动态库一般是libxxx.so;相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。

静态链接库:这类库的名字一般是libxxx.a;利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。

人们将每个源代码模块独立的编译,然后按照需要将它们组装起来,这个组装模块的过程就是链接

连接过程主要包括了地址空间分配、符号决议和重定位等

步骤:
1、 合并段表:将多个二进制可重定位文件中的段信息整合放到一个文件中
2、 调整段偏移:段表经过合并之后大小发生了变化,所以地址需要适当的偏移
3、 合并符号表:将多个二进制可重定位文件中的符号整合到一个文件中
4、 完成符号的重定位:连接器把每个符号定义与一个虚拟地址联系起来,然后修改所有对这些符号的引用,使得它们指向这个存储位置,从而重定位这些节。

makefile

包含内容:

  1. 显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。

  2. 隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。

  3. 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。

  4. 文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。

  5. 注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“/#”。

还有模式规则、自动化变量、检查规则等等很多的内容,由于不是每次都要用,就不再写了。

大概流程:

  1. 执行make指令,在当前目录下找到makefile文件
  2. 进入makefile文件找到最终目标
  3. 逐层查找最终目标的依赖
  4. 编译文件
  5. 链接可执行文件及库文件生成一个最终的可执行文件
  6. 删除中间生成的那些可执行文件
    写的文件不能放出来,可以参考一下这个:https://blog.csdn.net/X_BoLuoPi/article/details/106246779

还有一个常用模板
https://www.cnblogs.com/cyyljw/p/8137015.html

常用选项

1.CFLAGS、CXXFLAGS、LDFLAGS、LIBS

https://blog.csdn.net/jfkidear/article/details/8262260?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2.channel_param

posted @ 2020-08-03 21:11  xiuzhublog  阅读(327)  评论(0编辑  收藏  举报