中山住房和建设局网站,小程序搭建的方式,wordpress静态404,福州市住房和城乡建设局网站从零Makefile落地算法大项目#xff0c;完整案例教程 转自#xff1a;从零Makefile落地算法大项目#xff0c;完整案例教程 作者#xff1a;手写AI 前言
在这里#xff0c;你能学到基于Makefile的正式大项目的使用方式和考虑#xff0c;相信我#xff0c;其实可以很简单…从零Makefile落地算法大项目完整案例教程 转自从零Makefile落地算法大项目完整案例教程 作者手写AI 前言
在这里你能学到基于Makefile的正式大项目的使用方式和考虑相信我其实可以很简单。而且写一次到处用新项目复制即可用本教程带你一步步完成ppt很长是因为细内容不多相比cmakeMakefile更加轻量简洁侵入性低掌控力强。语法更少更简单使用Makefile你可以进行更细粒度的掌控虽然cmake简化了这些但是对于大型项目细节的掌控是必须的对于算法落地我们会面临各种库包理清楚非常有利于降低问题的发生这也是高级算法工程师系列课程的基础贯穿后续课程的存在。后续会有CUDA编程、TensorRT、基于C实现BP、流媒体等等
准备环境
VSCode (Visual Studio Code)作为IDE安装VSCode的C插件准备Linux系统Ubuntu推荐使用VSCode的SSH插件远程连接服务器(另一个电脑)进行开发。本地电脑可以是windows/mac熟悉C的基本语法我们主讲Makefile但是会有C编码部分不多
目录
g指令介绍C的编译链接过程、编译时、运行时介绍Makefile基本语法依赖关系定义基于Makefile的标准工程结构基于Makefile实现的完整功能项目分析程序依赖项readelf、ldd配置C的调试功能头文件修改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而不是gccg 等价于 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.sg main.s -o main.bing 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 两者汇编代码对比
main.s里面没有add函数的具体实现只有call add操作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基础-解决的问题是什么
编译代码是一个很耗时的事情尤其是代码量大、CPU差时边缘端参考官方文档查看更多定义http://www.gnu.org/software/make/manual/make.html 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 总结
变量赋值有4种方式var 123, var : 123, var ? 123, var 123。其中var : 123常用var 123常用取变量值有两种$(var)${var}。小括号大括号均可以数据类型只有字符串和字符串数组空格隔开表示多个元素$(function arguments)是调用make内置函数的方法具体可以参考官方文档的函数大全。但是常用的其实只有少数两个即可依赖关系定义中如果代码修改时间比生成的更新/生成不存在时command会执行。否则只会打印main.o is up to date。这是makefile解决的核心问题依赖关系可以链式的定义即b依赖ac依赖b而make会自动链式的查找并根据时间执行commandcommand是shell指令可以使用$(var)来将变量用到其中。前面加表示执行执行时不打印原指令内容。否则默认打印指令后再执行指令make不写具体生成名称则会选择依赖关系中的第一项生成
还有问题 4. 基于Makefile的标准工程结构
4.1 Makefile工程结构 一个标准工程我们做如下定义具有src目录存放我们的代码可能有多级例如main.cppfoo/foo.cpp等具有workspace目录存放我们编译后的可执行程序、资源、数据具有objs目录存放由cpp编译后得到的o文件等中间文件.vscode目录存放vscode的cpp配置用于语法解析器。vscode的c插件所使用。ctrlshiftp后搜索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依赖 可以锻炼一个完整的相对完善的工程案例。还可以锻炼到代码调试 实现的效果 实现一个程序可以从任何网站上下载东西 准备 下载opensslhttps://www.openssl.org/source/old/1.1.1/openssl-1.1.1j.tar.gz 这是用于实现加密通信的加密算法库。用于访问https开头的链接 下载libcurlhttps://curl.se/download/curl-7.78.0.tar.gz 这个是用于实现http/https的访问操作。如果要访问https则依赖openssl
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/includelibrary_paths : lean/curl7.78.0/lib \lean/openssl-1.1.1j/lib# 把library path给拼接为一个字符串例如a b c a:b:c
# 然后使得LD_LIBRARY_PATHa: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 : -stdc11 -w -g -O0 $(include_paths)
link_flags : $(library_paths) $(ld_librarys) $(run_paths)# 所有的头文件依赖产生的makefile文件进行include
# -include表示如果有异常请不要打印出来
# 这里判断如果是clean指令则不需要生成mk文件
ifneq ($(MAKECMDGOALS), clean)
-include $(mks)
endifobjs/%.o : src/%.cppecho 编译$生成$目录是$(dir $)mkdir -p $(dir $)g -c $ -o $ $(compile_flags)workspace/pro : $(objs)echo 链接$g $^ -o $ $(link_flags)objs/%.mk : src/%.cppecho 生成依赖$mkdir -p $(dir $)g -MM $ -MF $ -MT $(:.mk.o) $(compile_flags)pro : workspace/proecho 编译完成run : procd workspace ./proclean :rm -rf workspace/pro objs.PHONY : pro run debug cleanexport LD_LIBRARY_PATH:$(LD_LIBRARY_PATH):$(library_path_export)5.8 写代码
5.8.1 第一段 这里写一个download函数接收url然后返回下载后的数据
5.8.2 第二段
注意这里的地址换为http://www.zifuture.com:1556/fs/sxai/2021/07/pro-18432c111ca44aa9bba49eab650f466c.jpg 实现一个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