• Makefile——Linux下C/C++编译方法


    1. C

    linux下常见的C语言项目相关的文件如下图所示。
    在这里插入图片描述

    1.1 编译C

    通常使用GCC来编译C文件。编译过程为源文件.c文件 -> 预编译成.i文件 -> 编译成汇编语言.s -> 汇编成.o文件 -> 链接成可执行文件。编译命令为gcc -参数 .c -o 输出文件名称

    1. 预处理:将头文件拷贝进.c文件内容中,执行预编译命令。采用gcc -E命令
    gcc -E [.c源文件] -o [输出文件名.i]
    
    • 1
    1. 编译成汇编:将c语言代码编译为对应的汇编指令。采用gcc -S命令
    gcc -S [.c源文件] -o [输出文件名.s]
    
    • 1
    1. 编译成目标文件:二进制文件
    gcc -c [.c源文件] [.c源文件] [...] -o [文件名]
    
    • 1
    1. 编译成可执行文件:
    gcc [.c源文件] [.c源文件] [...] -o [输出文件名]
    gcc [.c源文件] [.c源文件] [...] -o [输出文件名] -l[库名] -L[库所在路径]
    
    • 1
    • 2

    1.2 创建静态库

    程序库是已写好的、供使用的可复用代码,每个程序都要依赖很多基础的底层库。从本质上,库是一种可执行代码的二进制形式。静态库可以在编译c项目时,将引用的库一起链接到可执行文件中,可执行文件在运行时不再需要库的支持,但可执行文件会变大。

    1. 将库的.c源文件编译成.o目标文件
    gcc -c [.c] -o [自定义文件名] 
    gcc -c [.c] [.c] ...
    
    • 1
    • 2
    1. 将.o目标文件编译成静态库
    ar -r [lib自定义库名.a] [.o] [.o] ...
    
    • 1
    1. 将C程序和静态库链接,编译成可执行文件。库名为lib***.a的***。
    gcc [.c] -o [输出文件名] -l[库名] -L[库所在路径]
    
    • 1

    1.3 创建动态库

    动态库在程序编译时不会链接到目标代码中,而是在程序运行时才被调用,可执行文件比静态链接的可执行文件要小。不同的程序可以共享相同的动态库。

    1. 将库的.c源文件编译成.o目标文件(要加入-fpic选项)
    gcc -c -fpic [.c/.cpp][.c/.cpp]... 
    
    • 1
    1. 将.o目标文件编译成动态库
    gcc -shared [.o][.o]... -o [lib自定义库名.so]
    
    #将1和2合并为一步
    gcc -fpic -shared [.c源文件] [.c源文件] [...] -o lib[库名].so
    
    • 1
    • 2
    • 3
    • 4
    1. 将C程序和动态库链接,编译成可执行文件。库名为lib***.so的***。
    gcc [.c/.cpp] -o [自定义可执行文件名]  -l[库名] -L[库路径] -Wl,-rpath=[库路径]
    
    • 1

    2. C++

    编译C++的流程与编译C几乎一致,只不过用的时g++命令。将上述编译.c的gcc改为g++即可编译cpp文件。静态库与动态库同理。

    3. Makefile

    makefile是自动化编译的一款工具。实际中C/C++工程项目可能十分庞大,直接用手一条一条的敲gcc/g++效率极低。在makefile文件中编写编译流程可以简化编译工作。

    • make 会在当前目录下找到一个名字叫 Makefile 或 makefile 的文件
    • 如果找到,它会找文件中第一个目标文件(target),并把这个文件作为最终的目标文件
    • 如果 target 文件不存在,或是 target 文件依赖的 .o 文件(prerequities)的文件修改时间要比 target 这个文件新,就会执行后面所定义的命令 command 来生成 target 这个文件
    • 如果 target 依赖的 .o 文件(prerequisties)也存在,make 会在当前文件中找到 target 为 .o 文件的依赖性,如果找到,再根据那个规则生成 .o 文件
    #makefile格式
    targets : prerequisties
    [tab键]command
    
    targets为生成的目标文件;
    prerequisties为为了生成targets目标而所需的依赖目标文件;
    conmmand为需要运行的命令
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    为了避免 target 和 Makefile 同级目录下 文件/文件夹 重名的这种情况,我们可以使用一个特殊的标记 .PHONY 来显式地指明一个目标是 “伪目标”,如下例中的clean

    .PHONY : clean
    
    • 1

    3.1 变量

    1. 变量定义用:=
    cpp := src/main.cpp 
    obj := objs/main.o
    
    • 1
    • 2
    1. 使用变量()或{}
    cpp := src/main.cpp 
    obj := objs/main.o
    
    $(obj) : ${cpp} #cpp中的字符串代表的依赖文件,obj为生成的目标文件
    	@g++ -c $(cpp) -o $(obj)  
    	#等价于g++ -c src/main.cpp -o objs/main.o
    compile : $(obj) #compile为伪目标,可执行make compile命令
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 预定义变量:
      $@: 目标(target)的完整名称
      $<: 第一个依赖文件(prerequisties)的名称
      $^: 所有的依赖文件(prerequisties),以空格分开,不包含重复的依赖文件
    cpp := src/main.cpp 
    obj := objs/main.o
    
    $(obj) : ${cpp}
    	@g++ -c $< -o $@
    	#等价于g++ -c src/main.cpp -o objs/main.o
    	@echo $^
    	#执行shell命令echo,打印出cpp的内容
    
    compile : $(obj)
    .PHONY : compile
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 特殊符号:\为续行符,*通配符(匹配任意字符串,可用与目录名和文件名中),%通配符(匹配任意字符串,并将匹配到的字符串作为变量使用)

    3.2 常用函数

    函数执行格式通常为$(fn, arguments) or ${fn, arguments}

    1. shell:调用shell命令,返回shell命令执行结果,使用格式为
      $(shell )
    # shell 指令,src 文件夹下找到 .cpp 文件
    cpp_srcs := $(shell find src -name "*.cpp") 
    # shell 指令, 获取计算机架构
    HOST_ARCH := $(shell uname -m)
    
    • 1
    • 2
    • 3
    • 4
    1. subst:把字串 中的 字符串替换成 ,使用格式为
      $(subst ,,)
    cpp_srcs := $(shell find src -name "*.cpp")
    cpp_objs := $(subst src/,objs/,$(cpp_objs)) 
    
    • 1
    • 2
    1. patsubst:从 text 中取出 patttern(其中%为通配符), 替换成 replacement,使用格式为 $(patsubst ,,)
    cpp_srcs := $(shell find src -name "*.cpp") #shell指令,src文件夹下找到.cpp文件
    cpp_objs := $(patsubst %.cpp,%.o,$(cpp_srcs)) #cpp_srcs变量下cpp文件替换成 .o文件
    
    • 1
    • 2
    1. foreach:把字串中的元素逐一取出来,执行包含的表达式,使用格式为 $(foreach ,,)
    library_paths := /lib \
                     /usr/local/lib64
    #每一个元素前面加上-I
    I_flag := $(foreach item,$(library_paths),-I$(item))
    #等价于
    I_flag := $(include_paths:%=-I%)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. dir:从文件路径+文件名字符串序列中取出目录部分,使用格式为
      $(dir )
    $(dir src/foo.c hacks)    # 返回值是“src/ ./”。
    
    • 1
    1. notdir:与dir函数相反,去除目录部分,只保留文件名
    #嵌套使用,先用shell中的find命令寻找lib开头的文件,然后去掉这些文件的目录前缀
    libs   := $(notdir $(shell find /usr/lib -name lib*))
    
    • 1
    • 2
    1. filter:从字符串序列中,过滤出满足条件的
    libs    := $(notdir $(shell find /usr/lib -name lib*))
    a_libs  := $(filter %.a,$(libs)) #过滤出静态库
    so_libs := $(filter %.so,$(libs))  #过滤出动态库
    
    • 1
    • 2
    • 3
    1. basename:去掉文件名后缀
    libs    := $(notdir $(shell find /usr/lib -name lib*))
    #去掉后缀和lib前缀,得到库名
    a_libs  := $(subst lib,,$(basename $(filter %.a,$(libs))))
    so_libs := $(subst lib,,$(basename $(filter %.so,$(libs))))
    
    • 1
    • 2
    • 3
    • 4
    1. filter-out:去除不要的字符串
    objs := objs/add.o objs/minus.o objs/main.o
    cpp_objs := $(filter-out objs/main.o, $(objs))
    
    • 1
    • 2

    3.3 makefile编译文件

    编译选项:
    -m64: 指定编译为 64 位应用程序
    -std=: 指定编译标准,例如:-std=c++11、-std=c++14
    -g: 包含调试信息
    -w: 不显示警告
    -O: 优化等级,通常使用:-O3
    -I: 加在头文件路径前
    fPIC: (Position-Independent Code), 产生的没有绝对地址,全部使用相对地址,代码可以被加载到内存的任意位置,且可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的

    链接选项:
    -l: 加在库名前面
    -L: 加在库路径前面
    -Wl,<选项>: 将逗号分隔的 <选项> 传递给链接器
    -rpath=: “运行” 的时候,去找的目录。运行的时候,要找 .so 文件,会从这个选项里指定的地方去找

    cpp_srcs := $(shell find src -name *.cpp)
    cpp_objs := $(patsubst src/%.cpp,objs/%.o,$(cpp_srcs))
    
    include_dirs :=/home/include
    I_options := $(include_dirs:%=-I%)
    compile_options := -g -O3 -w $(I_options)
    
    lib_dirs := /home/lib #库路径
    link_libs := xxx      #库名
    L_options := $(lib_dirs:%=-L%)
    l_options := $(link_libs:%=-l%)
    link_options := $(l_options) $(L_options) $(I_options)
    
    #每个.cpp生成一个对应的目标文件
    objs/%.o : src/%.cpp
    	#创建文件夹,@表示make的时候不打印make信息
    	@mkdir -p $(dir $@)
    	@g++ -c $^ -o $@ $(compile_options)
    
    workspace/exec : $(cpp_objs)
    	@mkdir workspace/exec
    	@g++ $^ -o $@ $(link_options)
    	
    # 输入make run即可生成run伪目标
    run : workspace
    	@./$<
    	
    # 输入make clean即可生成clean伪目标
    clean :
    	@rm -rf objs workspace/exec
    
    debug :
    	@echo $(as_files)
    
    .PHONY : debug run clean
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
  • 相关阅读:
    第三章 常用布局
    分布式文件存储系统MinIO笔记
    SpringCache
    从正射到倾斜,Mavic 3E详细使用报告
    如何从命令行运行3dMax脚本(MAXScript或Python)?
    Pdfjs使用
    Docker Hub 公有镜像在国内拉取加速配置
    Windows10不常用操作(录屏、开启超级管理员、关闭自动IP配置、Edge崩溃等)
    【设计模式】单例模式、“多例模式”的实现以及对单例的一些思考
    Golang 切片作为函数参数传递的陷阱与解答
  • 原文地址:https://blog.csdn.net/weixin_44248258/article/details/133851001