makefile入门(代码片段)

faf4r faf4r     2023-03-27     392

关键词:

Makefile入门总结

【Makefile 20分钟入门,简简单单,展示如何使用Makefile管理和编译C++代码】的学习笔记

C/C++多文件编译

以C++为例,假设有main.cpp, A.cpp, B.cpp这三个源文件和head.h头文件,
要编译出可执行文件main(这个命名自定)
演示代码在附录直接下载

直接编译

$ g++ main.cpp A.cpp B.cpp -o main
# 或者
$ g++ -o main main.cpp A.cpp B.cpp

-o 后面接输出文件名(目标),其它的都是依赖,就是靠这些依赖,也就是源代码生成可执行文件。
所以上面的步骤可以简化为

gcc或g++ -o 目标 依赖
或者
gcc或g++ 依赖 -o 目标

单独编译源文件,然后链接

$ g++ A.cpp -c     # 生成A.o文件
$ g++ -c B.cpp     # 生成B.o文件
$ g++ -c main.cpp  # 生成main.o文件

.o(Windows下是.obj)就是object,目标代码,已经是机器码了,链接后就是可执行文件
(注意这里的目标不是最终的目标,只是这个文件叫这个而已,DOS系统叫对象文件)

另外这里的-c放哪里都行,没有顺序要求。
其次,也可以使用通配符一次性生成.o文件

$ g++ -c *.cpp
# 或
$ g++ *.cpp -o

进行链接:

$ g++ *.o -o main

同理,链接时可以一个一个写出.o文件,也可用通配符来写

这只是用到的编译中间过程,具体详细的中间过程

参考:C语言的编译过程详解

Makefile的工作原理(个人理解)

Makefile本质就是帮你简化了编译命令的编写,当你在命令行输入make的时候,就跟运行shell脚本一样,它会按照规则运行相应的命令。

下面以实际的例子来学习。

提示命令make会知道去pwd找Makefile文件去运行,如果你的makefile不是默认名,那就要用

$ make -f 文件名

(PS: makefile 小写也是默认的,一样)

Makefile Level 1 -- 命令和依赖

# Level 1
# 注释语法和Python、shell一样,用的#号

main: main.cpp A.cpp B.cpp head.h
	g++ -o main main.cpp A.cpp B.cpp

这就是最基本的Makefile语法,最后两行才是起作用的。

其中,顶格的main就是我们要生成的目标,而冒号后面的就是依赖
再下一行,开头是一个\\t而不是空格,就是一个tab开头是一个\\t而不是空格,就是一个tab开头是一个\\t而不是空格,就是一个tab。在markdowm里显示是4个空格,但一定记住这是这是\\t
tab后面就是需要运行的真正的shell命令。

关于依赖

冒号后面的是依赖(冒号后可空格可不空),这些依赖你不写也是可以的,比如

main:
    g++ -o main main.cpp A.cpp B.cpp

这代表这个目标没有依赖,直接运行下面的命令了。

所以,依赖有什么用呢?
简单来说就是,Makefile会帮你检测依赖和目标是否更新

比如,运行make后再运行make,它的结果是这样的:

make: “main”已是最新。

可见,当目标(也就是main)已经存在时,它就不一定帮你重新编译了(毕竟大型项目你重新编译一下需要的时间太久了)。
那么它怎样才会重新编译呢?就是看依赖了。

如果你有指定依赖,那么,
Makefile会检测目标和依赖之间的修改时间,从而确定目标是否需要更新

比如你修改了A.cpp,如果依赖有A.cpp,那么Makefile就会检测到更新从而重新编译main
如果依赖没有A.cpp,那么无论你怎么修改A.cpp它都不会编译。
同理,即使你在依赖加一个无关的文件比如test.txt,那么只要你修改了test.txt,运行make它就会重新编译。

Makefile Level 2 -- 引入变量

# level 2

CXX = g++      # 指定编译器
TARGET = main  # 指定目标
SRC = main.cpp A.cpp B.cpp

$(TARGET): $(SRC) head.h
    $(CXX) -o $(TARGET) $(SRC)

可以看到,这里引入了变量来简化编写,而且语法规则应该和shell命令是一样的。

变量名可以自定,实际命令把变量值代入即可。

注意这里SRC是指代.cpp文件,没有头文件,因此依赖那手动加一下,或者把头文件也做成变量:

# level 2

CXX = g++      # 指定编译器
TARGET = main  # 指定目标
SRC = main.cpp A.cpp B.cpp
HEAD = head.h

$(TARGET): $(SRC) $(HEAD)
    $(CXX) -o $(TARGET) $(SRC)

这样,引入变量后,项目变更时只需修改变量即可。

Makefile Level 3 -- 分开编译

# level 3

CXX = g++
TARGET = main
OBJ = main.o A.o B.o
HEAD = head.h

$(TARGET): $(OBJ) $(HEAD)
    $(CXX) -o $(TARGET) $(OBJ)

main.o: main.cpp
    $(CXX) -c main.cpp

A.o: A.cpp
    $(CXX) -c A.cpp

B.o: B.cpp
    $(CXX) -c B.cpp

可以看到,这里我们不是使用所有源文件直接编译,而是单独编译每个源文件,然后链接所有目标代码。

因此-o那一行只是链接的命令,那么目标代码哪里来呢?就是下面的其它目标了。

首先,目标只有一个:即第一个目标$(TARGET),我叫它主目标。
Makefile会检查目标和依赖,发现都没有,下面又有依赖的生成方式,那么它就会先完成下面的子目标,使得依赖满足,然后进行编译。

如果依赖发生了更新,但因为你更新的是源代码,那么下面的子目标就会检测到需要更新,于是它会更新相应的依赖,但不会全部更新。然后再链接所有的依赖。

这样做,就避免了大型项目改动之后全部重新编译的弊端。
但是要注意子目标和主目标的依赖要同时编辑。

同时要明确,只有第一个目标是主目标,下面的都是子目标。

# level 3

CXX = g++
TARGET = main
OBJ = main.o A.o B.o
HEAD = head.h

A.o: A.cpp
    $(CXX) -c A.cpp
    
$(TARGET): $(OBJ) $(HEAD)
    $(CXX) -o $(TARGET) $(OBJ)

main.o: main.cpp
    $(CXX) -c main.cpp

B.o: B.cpp
    $(CXX) -c B.cpp

比如我调换一下顺序,删除生成的main,再运行make

make: “A.o”已是最新。

可见,它检查的是第一个目标,而不是后面的子目标。
因为主目标的A.oA.cpp都是最新的,所以就有上面的输出。

简单理解子目标:

主目标的依赖也利用Makefile来生成,子目标是对依赖的补充,也就是对主目标的补充。其实就是把主目标给拆分了

简单实验一下,如果把A.cpp删了,清除所有输出,会发生什么呢?

g++ -c main.cpp
make: *** 没有规则可制作目标“A.o”,由“main” 需求。 停止。

可以看到,它说目标A.o是main所需要的,也就是依赖,“没有规则”说明当依赖没有时,它会自己寻找下面有没有子目标是生成依赖的,有的话就生成,没有就出现上面的输出。

同时我们在Makefile里加一个新的无关的子目标,无论是编译还是修改了这个无关目标的文件,发现对整个正常过程没有任何影响。说明它只看主目标,子目标是根据需要才看的

Makefile Level 4 -- 分开编译+通配符(高级变量)

# level 4

CXX = g++
TARGET = main
OBJ = main.o A.o B.o
HEAD = head.h

$(TARGET): $(OBJ) $(HEAD)
    $(CXX) -o $@ $^

%.o: %.cpp
    $(CXX) -c $< -o $@

这里先声明一下,视频里没有考虑头文件,但个人还是考虑了。
因为链接时加入头文件并无影响,所以我加入了头文件作为依赖。

为什么说这个呢,下面一一解释代码的含义。

$@

这是Makefile的内置变量,代表的就是目标(它所在的那一个目标)
因此每一个目标的-o后面都接了$@

$^

这个箭头向上,代表所在目标的所有依赖
因此主目标在链接时,会把头文件也带上。

$<

这个箭头向左,代表所在目标的第一个依赖
在子目标里,实际执行的命令诸如:

$ g++ -c A.cpp -o A.o

其实和前面一样,对单个源文件编译,所以只有一个依赖。
这当然不是说%.cpp代表很多个依赖,其实也是一个,所以在这个子目标里,也可以用$^

%.o: %.cpp
	$(CXX) -c $^ -o $@

关于这个-o

其实这里的-o(指定输出文件名)也可以省略:

%.o: %.cpp
	$(CXX) -c $^

它自己会生成%.o形式的目标,加一个-o只是明确指定它的输出文件名和目标一致。

这个建议加上,比如在别的系统g++ -c file.cpp默认生成的可能是file.obj而不是file.o,虽然本质都一样,但是因为名称不对就出现bug了。所以加上这个能减少出bug的几率。

%通配符

%.o: %.cpp
    $(CXX) -c $< -o $@

这个%其实和命令行里的*是一样的,就是个通配符。

那么通配符的内容来自哪呢?这要再了解一下Makefile的机制。

运行make,所有文件都有了,此时只删除A.o(这是主目标的依赖之一)
再运行make,Makefile检查主目标和依赖,发现依赖更新了(没了就是和原来不一样了就是更新了),于是需要重新编译。
还是分开编译的原理,因为其它依赖B.omain.o都是最新的,不需编译。
只有A.o需要编译,那么它就会找子目标看有没有编译它的规则。
%.oA.o都是和A.o匹配的,所以这个带通配符的目标与需要的依赖匹配,它就会自动代入进去,也就是成了:

A.o: A.cpp
    $(CXX) -c $< -o $@

因此看Makefile给出的输出就只有两条编译命令:

g++ -c A.cpp -o A.o
g++ -o main main.o A.o B.o head.h

另外注意它只是匹配%.o,而%.cpp也是跟着它的规则来的,而如果写的是%而不是%.cpp则会有点问题。

Makefile Level 5 -- clean && -Wall

# level 5

CXX = g++
TARGET = main
OBJ = main.o A.o B.o
HEAD = head.h
CXXARGS = -c -Wall

$(TARGET): $(OBJ) $(HEAD)
	$(CXX) -o $@ $^

%.o: %.cpp
	$(CXX) $(CXXARGS) $< -o $@

.PHONY: clean
clean:
	rm -f *.o $(TARGET)

同样的,利用变量,把g++的参数也变量化了,但这次多了一个参数-Wall,这是g++编译的命令。

-Wall中的W表示warning警告,all就是全部。这个命令表示在编译时输出所有的警告。

.PHONY

这是Makefile的内置变量,含义就是不检测这个目标是否存在或更新。

若先注释掉.PHONY: clean,对整个Makefile没有影响,但是这个clean目标怎么用呢?

clean是子目标,子目标只有在主目标依赖需要时才运行,明显这个clean是永远不可能运行的。
所以我们需要手动运行它:

$ make clean

通过make加子目标将主目标转为我们指定的目标。
比如还可make A.o来指定编译A.cpp甚至不存在的文件,比如make FIle.o,反正只要符合规则即可

而这个目标没有依赖,也就是说,只要生成这个目标,就会运行目标下面的编译代码,只是现在把编译代码换成了删除命令而已。
正如起名,这行代码就是删除所有输出的.o文件和目标main

那么.PHONY有什么用呢?
如果我们touch clean创建一个叫clean的文件,然后再make clean试试?
发现因为这个clean已经存在了,所以它不生成了,所以它就不会运行下面的命令。

.PHONY的作用就是忽略目标是否更新,不管怎样都要再生成目标,即保证我们运行下面的命令。

最后再说这个clean,一般Makefile会有这个,保证编译时清除了以前的残留。

Makefile Level 6 -- 更高级更简便

# level 6

CXX = g++
TARGET = main
SRC = $(wildcard *.cpp)
OBJ = $(patsubst %.cpp, %.o, $(SRC))
HEAD = $(wildcard *.h)
CXXARGS = -c -Wall

$(TARGET): $(OBJ) $(HEAD)
	$(CXX) -o $@ $^

%.o: %.cpp
	$(CXX) $(CXXARGS) $< -o $@

.PHONY: clean
clean:
	rm -f *.o $(TARGET)

这里用到了Makefile的内置函数吧。$(函数名 参数1, 参数2)这样的格式

$(wildcard *.cpp)

这是取当前文件夹(不包括子文件夹)所有的匹配的文件名,自动添加赋值,方便了很多

$(patsubst %.cpp, %.o, $(SRC))

这是个路径替换,规则是把cpp替换为o,替换的文本为SRC。

至此,Makefile入门就此结束,收货很多,很有用。

附录

文件下载
main.cpp

#include "head.h"

int main()
    use_function_A();
    use_function_B();

    return 0;
 

A.cpp

#include <iostream>
#include "head.h"

using namespace std;

void use_function_A()
    cout << "used function A!" << endl;

B.cpp

#include <iostream>
#include "head.h"

using namespace std;

void use_function_B()
    cout << "used function B!" << endl;

head.h

#ifndef HEAD
#define HEAD

void use_function_A();
void use_function_B();

#endif

makefile入门(代码片段)

相信大家对makefile都不陌生,在Linux下编写程序基本都离不开makefile的编写,我们都知道多个.c文件经过编译器编译后得到多个.o文件,这些文件是互相独立的,但最终我们要得到一个可正常运行的文件,很显然这个过程就是连接... 查看详情

golanggin实践番外请入门makefile(代码片段)

GolangGin实践番外请入门Makefile原文地址:GolangGin实践番外请入门Makefile前言含一定复杂度的软件工程,基本上都是先编译A,再依赖B,再编译C...,最后才执行构建如果每次都人为编排,又或是每新来一个同事就问你项目D怎么构建... 查看详情

入门c/c++自动构建利器之makefile(代码片段)

...C++编译本质一篇文章入门C/C++自动构建利器之Makefile升级构建工具,从Makefile到CMakeMakefile简介上一篇浅析C/C++编译本质已经比较详细地介绍了C/C++编译的流程和初步探讨了编译过程中底层 查看详情

makefile入门(超详细一文读懂)(代码片段)

1、Makefile编译过程  Makefile文件中的命令有一定规范,一旦该文件编写好以后在Linux命令行中执行一条make命令即可自动编译整个工程。不同厂家的make可能会稍有不同,并且语法上也有区别,不过基本思想都差不多&#x... 查看详情

makefile入门:用最美味的例子(代码片段)

目录入门为什么存在Makefile?Make有哪些替代方案?运行示例生成文件语法初学者示例变量目标全部目标多个目标自动变量和通配符*通配符%通配符自动变量花式规则静态模式规则静态模式规则和过滤器隐含规则模式规则双... 查看详情

makefile入门(代码片段)

目录MakeMakefileMakefile文件格式Makefile工作流程Make编译、链接、构建:把源文件编译成中间代码文件,在Windows下也就是.obj文件,UNIX下是.o文件,即ObjectFile,这个动作叫做编译(compile)然后再把大量的ObjectFile合成执行文件,这个... 查看详情

conan入门(二十六):使用make编译erpc/erpcgen(makefile)(代码片段)

conan使用make编译erpc/erpcgen(makefile)conan是个包管理工具,不仅仅支持cmake编译,还支持很多常用的构建工具如configure/make,msbuild,VisualStudo,meson,本文以NXP的EmbeddedRPC为例说明conan中如何使用make来构建项目。NXP的eRPC(EmbeddedRPC)是用... 查看详情

makefile入门(代码片段)

Makefile入门总结【Makefile20分钟入门,简简单单,展示如何使用Makefile管理和编译C++代码】的学习笔记C/C++多文件编译以C++为例,假设有main.cpp,A.cpp,B.cpp这三个源文件和head.h头文件,要编译出可执行文件main(这个命名自定)演示代码在... 查看详情

makefile入门-初步了解(代码片段)

自己开始学习makefile是由于VScode配置工程文件,看别人的配置不是很懂,于是决定入门学习下makefile。先来说说makefile是做什么用的:对于只有一个或两三个源文件的程序,我们可以直接用g++命令进行编译链接。例如以下程序,fun... 查看详情

linuxmakefile工具的使用入门(代码片段)

01什么是MakeFile02MakeFile文件命名和规则03MakeFile的工作原理检查规则中的依赖是否存在检查更新04变量05模式匹配使用模式匹配简化生成依赖的规则06函数1)函数wildcard获取指定目录下指定类型的文件列表2)函数patsubst将符... 查看详情

升级构建工具,从makefile到cmake(代码片段)

...C++编译本质一篇文章入门C/C++自动构建利器之Makefile升级构建工具,从Makefile到CMake上一篇文章一篇文章入门C/C++自动构建利器之Makefile介绍了构建项目的脚本工具Makefile,相比于手动执 查看详情

makefile编写(代码片段)

入门见<并行程序设计(第四版)>以yolo源码中的makefile文件为例GPU=0CUDNN=0OPENCV=0OPENMP=0DEBUG=0ARCH=-gencodearch=compute_30,code=sm_30-gencodearch=compute_35,code=sm_35-gencodearch=compute_50,code=[sm_50,compute_50]-g 查看详情

unix环境编程gcc编译器|makefile基础入门|gdb调试教学(代码片段)

...f1a;本文将介绍如何使用GCC编译器编译,并详细介绍了Makefile的基本构造、创建Makefile文件以及Makefile变量,以提高编译效率。此外,本文还将探讨GDB调试器的使用,包括调试前的准备、readelf读取ELF文件信息、显示... 查看详情

cmake入门实战(代码片段)

...等等。这些Make工具遵循着不同的规范和标准,所执行的Makefile格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的Make工具,就得为每一种标准写一次Makefile,... 查看详情

qt高级——qmake快速入门(代码片段)

...qmake是Trolltech公司创建的用来为不同的平台和编译器书写Makefile的工具。qmake是一个用来简化在不同平台间开发工程的构建过程的工具。qmake会自动生成MakeFile文件,可以用于任何软件项目中,无论是否由Qt编写。qmake会注意所有的... 查看详情

利用makefile给多文件多目录c源码建立工程(代码片段)

0.前言粉丝留言,想知道如何使用Makefile给多个文件和多级目录建立一个工程,必须安排!关于Makefile的入门参考文章,可以先看这篇文章:《Makefile入门教程》为了让大家有个更加直观的感受,一口君将之... 查看详情

linux内核移植入门(代码片段)

文章目录基本概念内核源码目录结构内核配置主目录Makefile各子目录Makefile如何配置内核?1.配置仓库选取2.交叉编译器的修改3.体系结构体的选择4.修改配置文件内核编译编译结果:几种linux内核文件的区别开发板上U-Boot启动linux内核... 查看详情

make工具和makefile基础使用(代码片段)

参考:make工具和Makefile基础入门作者:~莘莘发布时间:2021-07-1318:48:52网址:https://blog.csdn.net/lcx1837/article/details/118706593?spm=1001.2014.3001.5501目录初次使用一、编写Makefile文件二、make三、makecle 查看详情