从零Makefile落地算法大项目,完整案例教程

41 篇文章 2 订阅

从零Makefile落地算法大项目,完整案例教程

转自:从零Makefile落地算法大项目,完整案例教程

作者:手写AI

前言

  1. 在这里,你能学到基于Makefile的正式大项目的使用方式和考虑,相信我,其实可以很简单。而且写一次到处用,新项目复制即可用
  2. 本教程带你一步步完成,ppt很长是因为细,内容不多
  3. 相比cmake,Makefile更加轻量简洁,侵入性低,掌控力强。语法更少更简单
  4. 使用Makefile你可以进行更细粒度的掌控,虽然cmake简化了这些,但是对于大型项目,细节的掌控是必须的
  5. 对于算法落地,我们会面临各种库包,理清楚非常有利于降低问题的发生
  6. 这也是高级算法工程师系列课程的基础,贯穿后续课程的存在。后续会有CUDA编程、TensorRT、基于C++实现BP、流媒体等等

准备环境

  1. VSCode (Visual Studio Code),作为IDE
  2. 安装VSCode的C++插件
  3. 准备Linux系统(Ubuntu),推荐使用VSCode的SSH插件远程连接服务器(另一个电脑)进行开发。本地电脑可以是windows/mac
  4. 熟悉C++的基本语法,我们主讲Makefile但是会有C++编码部分(不多)

目录

  1. g++指令介绍
  2. C++的编译链接过程、编译时、运行时介绍
  3. Makefile基本语法,依赖关系定义
  4. 基于Makefile的标准工程结构
  5. 基于Makefile实现的完整功能项目
  6. 分析程序依赖项,readelf、ldd
  7. 配置C++的调试功能
  8. 头文件修改cpp自动编译的处理方法

GITHUB项目地址(PPT、代码均在这里)

https://github.com/shouxieai/makefile_tutorial_project

1. g++指令介绍

1.1 g++/gcc是什么,有什么区别

  • g++和gcc都是gnu推出的cpp编译器,时代不同
  • g++和gcc都可以进行cpp编译
  • g++和gcc一样,都属于driver,即驱动编译,他会调用cclplus/ccl/ld/as等指令实现编译链接等工作,他们俩只是默认选项上的处理不同
  • 这里我们采用g++而不是gcc
  • g++ 等价于 gcc -xc++ -lstdc++ -shared-libgcc
  • 参考:知乎问题

1.2 g++的编译过程

4种情况,注意指令的大小写很重要

  • 预处理:g++ -E main.cpp -o main.i
  • 汇编:g++ -S main.i -o main.s
  • 编译:g++ -c main.s -o main.o
  • 链接:g++ main.o -o main.bin

g++可以允许跨过中间步骤,例如:

  • g++ -S main.cpp -o main.s
  • g++ main.s -o main.bin
  • g++ main.cpp -o main.bin
  • 结果是等价的

比较常用的是编译-链接:

  • 编译,代码编译到二进制:g++ -c main.cpp -o main.o
  • 链接,多个二进制链接成执行程序:g++ main.o -o main.bin

预处理指令效果:g++ -E main.cpp -o main.i

在这里插入图片描述

汇编指令效果:g++ -S main.i -o main.s

在这里插入图片描述

编译指令效果:g++ -c main.s -o main.o

在这里插入图片描述

链接指令效果:g++ main.o -o out.bin

在这里插入图片描述

2. C++编译链接 / 编译时和运行时

2.1 C++编译链接流程图

在这里插入图片描述

2.2 C++的声明和实现

在这里插入图片描述

2.3 C++的编译过程-案例

2.3.1 代码结构,main.cpp和test.cpp

在这里插入图片描述

2.3.2 main.cpp的汇编代码

在这里插入图片描述

2.3.3 test.cpp的汇编代码

在这里插入图片描述

2.3.4 两者汇编代码对比
  1. main.s里面没有add函数的具体实现,只有call add操作
  2. add的具体实现在test.s里面

在这里插入图片描述

2.3.5 带有命名空间时的名字编码

在这里插入图片描述

2.4 C++编译过程

在这里插入图片描述

2.5 C++链接过程

在这里插入图片描述

2.6 C++实际的链接过程

在这里插入图片描述

2.7 若add函数在动态库,lib3rd.so中时

在这里插入图片描述

2.8 若add函数在静态库,libpkg.a中时

在这里插入图片描述

2.9 编译链接成一个完整程序的过程

在这里插入图片描述

2.10 C++链接时,查找so文件、a文件方式的方式

g++ -lpkg,这里是小写的L

在这里插入图片描述

2.11 C++运行时,查找so文件的方式

在这里插入图片描述

2.12 C++编译时,头文件的查找方式

  • 这里是大写的i,-lfolder

在这里插入图片描述

3. Makefile基础

3.1 Makefile基础-解决的问题是什么

在这里插入图片描述

3.2 Makefile基础-代码域

在这里插入图片描述

3.3 Makefile基础-语法

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 生成项可以没有依赖项,那么如果该生成项文件不存在,command将永远执行

3.2 依赖关系定义

在这里插入图片描述

  • 第一次执行make a.o时,由于a.o不存在,执行了command
  • 第二次执行make a.o时,由于a.cpp时间没有比a.o新,打印a.o is up to date,不需要编译
  • 生成项和依赖项,从来都是当成文件来看待的

3.3 编译和链接结合起来

在这里插入图片描述

  • 定义好依赖后make out.bin后,会自动查找依赖关系,并自动按照顺序执行command
  • 这是makefile为我们解决的核心问题,剩下就是如何玩的更方便罢了。比如自动检索a.cpp、b.cpp,自动定义a.o依赖a.cpp。等等

3.4 总结

  1. 变量赋值有4种方式var = 123, var := 123, var ?= 123, var += 123。其中var := 123常用,var += 123常用
  2. 取变量值有两种,$(var)${var}。小括号大括号均可以
  3. 数据类型只有字符串和字符串数组,空格隔开表示多个元素
  4. $(function arguments)是调用make内置函数的方法,具体可以参考官方文档的函数大全。但是常用的其实只有少数两个即可
  5. 依赖关系定义中,如果代码修改时间比生成的更新/生成不存在时,command会执行。否则只会打印main.o is up to date。这是makefile解决的核心问题
  6. 依赖关系可以链式的定义,即b依赖a,c依赖b,而make会自动链式的查找并根据时间执行command
  7. command是shell指令,可以使用$(var)来将变量用到其中。前面加@表示执行执行时不打印原指令内容。否则默认打印指令后再执行指令
  8. make不写具体生成名称,则会选择依赖关系中的第一项生成

还有问题

在这里插入图片描述

4. 基于Makefile的标准工程结构

4.1 Makefile工程结构

在这里插入图片描述

  • 一个标准工程,我们做如下定义:
  • 具有src目录,存放我们的代码,可能有多级,例如main.cpp,foo/foo.cpp等
  • 具有workspace目录,存放我们编译后的可执行程序、资源、数据
  • 具有objs目录,存放由cpp编译后得到的o文件等中间文件
  • .vscode目录,存放vscode的cpp配置,用于语法解析器。vscode的c++插件所使用。ctrl+shift+p后搜索c++,找到JSON那一项就是
  • Makefile文件,当前工程的Makefile代码

4.2 写代码

在这里插入图片描述

  • 这里简单定义了foo.hpp和foo.cpp,目的是链接为可执行程序后,可以执行
  • ifndef是防止重复包含

4.3 解决多级目录cpp检索问题

在这里插入图片描述

4.4 替换src/为objs/,o文件放到objs中

在这里插入图片描述

4.5 定义依赖关系,通配

在这里插入图片描述

  • objs/%.o和src/%.cpp代表了通配依赖关系,模式匹配,%相当于变量部分

4.6 为o文件创建目录

4.6.1 编译失败,因为目录不存在

在这里插入图片描述

  • 原因是,试图创建objs/foo/foo.o文件时失败。因为objs/foo这个目录不存在造成。对于高版本g++(例如9.0)不会报错并为你创建objs/foo目录。
  • 因此我们需要创建objs/foo目录,需要执行类似mkdir -p dir($@),通过dir($@)获取其目录后创建,这里的mkdir -p指多级目录也一并创建
4.6.2 使用mkdir -p $(dir $@)获取生成项目录

在这里插入图片描述

4.7 链接所有o文件生成可执行程序

在这里插入图片描述

  • 我们定义workspace/pro的生成,依赖自所有的o文件。pro是我们的可执行程序

4.8 完善一下Makefile

在这里插入图片描述

  • 添加make pro,简洁的编译程序
  • 添加make run,编译后顺便执行一下,注意: cd到workspace是为了让运行程 序后的当前工作目录在workspace中
  • 添加make clean,清理编译后的垃圾
  • 添加.PHONY,让我们作为指令存在的东西,不要被视作为文件。即make这东西时永远执行command

4.9 完整版本的Makefile

在这里插入图片描述

4.10 可以愉快的玩耍了

在这里插入图片描述

4.11 修改一个cpp后观察效果

在这里插入图片描述

  • 对,这就是我们想要的,nice!

5. 基于Makefile实现的完整功能项目

5.1 Makefile工程-一个复杂的例子,实现http请求

  • 实现的目的:

  • 具有两个依赖,openssl、libcurl

  • 存在include、libs依赖

  • 可以锻炼一个完整的相对完善的工程案例。还可以锻炼到代码调试

  • 实现的效果:

    实现一个程序,可以从任何网站上下载东西

  • 准备:

5.2 下载和编译libcurl/openssl

在这里插入图片描述

  • 创建build目录,用于储存下载后的文件,准备用来编译
  • 创建lean目录,用于存放编译后的结果,作为依赖项目录
  • 将下载后的.tar.gz放到 build目录下,并解压出来

5.3 编译openssl

在这里插入图片描述

cd openssl-1.1.1j
./config --prefix=/data/sxai/makefile/make7/lean/openssl-1.1.1j
make all -j16 && make install -j16
  • ./config是配置并生成Makefile,指定install到/data/sxai/makefile/make7/lean/openssl-1.1.1j目录 make all -j16 && make install -j16 这里-j16是同时16个线程执行操作,编译后,执行安装
  • 请把这里的lean目录修改为你当前自己想放的位置

5.4 编译libcurl

在这里插入图片描述

./configure --prefix=/data/sxai/makefile/make7/lean/curl7.78.0 \
   --with-openssl=/data/sxai/makefile/make7/lean/openssl-1.1.1j
make all -j16 && make install -j16
  • --prefix同样是为了设置安装目录,最后编译好的curl放在哪里
  • --with-openssl指定刚才我们编译安装后的目录
  • ./configure同样是为了配置curl生成Makefile文件
  • 执行make all -j16实现编译
  • 执行make install -j16实现安装

5.5 编译后结果

在这里插入图片描述

5.6 配置IntellSense和browse路径

在这里插入图片描述

  • 变量${workspaceFolder}代表了我们的当前目录,即/data/sxai/makefile/make7

5.7 配置Makefile

5.7.1 第一步

在这里插入图片描述

5.7.2 第二步

在这里插入图片描述

  • 好,我们齐活了。至此整个makefile已经非常完备了。该makefile可以通用了

给出代码:


srcs := $(shell find src -name "*.cpp")
objs := $(srcs:.cpp=.o)
objs := $(objs:src/%=objs/%)
mks  := $(objs:.o=.mk)

include_paths := lean/curl7.78.0/include \
				 lean/openssl-1.1.1j/include

library_paths := lean/curl7.78.0/lib     \
				 lean/openssl-1.1.1j/lib

# 把library path给拼接为一个字符串,例如a b c => a:b:c
# 然后使得LD_LIBRARY_PATH=a:b:c
empty := 
library_path_export := $(subst $(empty) $(empty),:,$(library_paths))

ld_librarys   := curl ssl crypto

# 把每一个头文件路径前面增加-I,库文件路径前面增加-L,链接选项前面加-l
run_paths     := $(library_paths:%=-Wl,-rpath=%)
include_paths := $(include_paths:%=-I%)
library_paths := $(library_paths:%=-L%)
ld_librarys   := $(ld_librarys:%=-l%)

compile_flags := -std=c++11 -w -g -O0 $(include_paths)
link_flags := $(library_paths) $(ld_librarys) $(run_paths)

# 所有的头文件依赖产生的makefile文件,进行include
# -include表示如果有异常请不要打印出来
# 这里判断,如果是clean指令,则不需要生成mk文件
ifneq ($(MAKECMDGOALS), clean)
-include $(mks)
endif

objs/%.o : src/%.cpp
	@echo 编译$<,生成$@,目录是:$(dir $@)
	@mkdir -p $(dir $@)
	@g++ -c $< -o $@ $(compile_flags)

workspace/pro : $(objs)
	@echo 链接$@
	@g++ $^ -o $@ $(link_flags)

objs/%.mk : src/%.cpp
	@echo 生成依赖$@
	@mkdir -p $(dir $@)
	@g++ -MM $< -MF $@ -MT $(@:.mk=.o) $(compile_flags)

pro : workspace/pro
	@echo 编译完成

run : pro
	@cd workspace && ./pro

clean :
	@rm -rf workspace/pro objs

.PHONY : pro run debug clean

export LD_LIBRARY_PATH:=$(LD_LIBRARY_PATH):$(library_path_export)

5.8 写代码

5.8.1 第一段

在这里插入图片描述

  • 这里写一个download函数,接收url,然后返回下载后的数据
5.8.2 第二段

在这里插入图片描述

  • 实现一个main函数,调用download。给定地址是一个图片下载好后储存为文件

5.9 执行并观察结果

在这里插入图片描述

  • 文件下载成功,至此。整个http的访问工程就达成了。你学会如何控制,头文件、库文件路径了吗?还有o文件存放工作目录等

6. 分析程序依赖项

6.1 使用readelf -d workspace/pro分析

在这里插入图片描述

6.2 使用ldd workspace/pro分析

在这里插入图片描述

7. 配置C++的调试功能

7.1 配置task.json

在这里插入图片描述

  • task.json是配置用来执行调试之前的编译工作。即,按下F5,编译程序,进入调试

7.2 配置launch.json

在这里插入图片描述

  • 这个文件可用通过直接按下F5后自动产生,也可以手动敲哈
  • 如果有参数,可以加到args中
  • stopAtEntry表示启动后就停止到main函数里边

7.3 进行调试

  • 好了,我们在main.cpp的29行这个文字左侧点击后后个红点,作为断点,然后按下F5键,看看会怎么样

在这里插入图片描述

7.4 界面介绍

在这里插入图片描述

7.5 恭喜

  • 到这里,恭喜你,已经掌握了如何使用
  • Makefile在linux下开发的技能了!
  • Congratulations!!!

8. 头文件修改后自动编译

  • 代码请到github上下载:https://github.com/shouxieai/makefile_tutorial_project

在这里插入图片描述

8.1 新建工程

  • 我们有如下代码。头文件a.hpp中定义了Number 888

在这里插入图片描述

在这里插入图片描述

8.2 分析原因

  • 原因:缺少a.o对hpp依赖关系的定义。makefile中没有定义a.o : a.hpp,没有要求编译a.cpp需要检查a.hpp的时间
  • 解决方案?:直接增加a.o : a.cpp a.hpp吗?是可以。强制要求 a.o生成时检查a.hpp

在这里插入图片描述

8.3 解决方案

8.3.1 使用g++ -MM a.cpp -MF a.mk -MT prefix/a.o生成makefile文件a.mk

在这里插入图片描述

在这里插入图片描述

  • g++ -MM a.cpp -MF a.mk -MT prefix/a.o生成的makefile文件
8.3.2 通过include a.mk包含生成的文件,使其生效
  • 我们使用g++ -MM a.cpp -MF a.mk -MT a.o
  • 为了使编译后的a.mk生效,我们可以通过include a.mk包含进来

在这里插入图片描述

8.3.3 整合起来

注意,这里include a.mk修改为-include a.mk就不会提示报错了

在这里插入图片描述

8.3.4 集成到项目中去

在这里插入图片描述

8.4 把代码拆分出头文件用于检验效果

在这里插入图片描述

8.5 至此,完整的Makefile工程搞定

  • 谢谢

CPP工程模版,请参见:

https://github.com/shouxieai/cpp-proj-template

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值