Makefile基础

Makefile基础

1 编译原理

1.1 编译过程

四个阶段:预处理(生成.i文件)——>编译(生成.s文件)——>汇编(生成.o文件)——>链接(生成可执行文件)

1.2 编译过程的命令

预处理阶段

gcc -E main.c # 不会生成 .i 文件
gcc -E main.c -o helloworld.i # 生成 .i 文件
  • -E 选项告诉编译器只进行预处理操作
  • -o 选项把预处理的结果输出到指定文件
    编译阶段
gcc -S main.i
gcc -S main.i -o main.s
  • -S 选项告诉编译器,将预处理文件编译成汇编文件
    汇编阶段
gcc -c main.c
gcc -c main.c -o main.o
  • -c 选项告诉编译器,将汇编文件编译为二进制的目标文件
    链接阶段
gcc main.o
gcc main.o -o main
gcc main.c -o main
  • 将目标文件或者源文件编译为最终的可执行文件

1.3 编译选项

|> 编译选项

  • -m64: 指定编译为 64 位应用程序
  • -std=: 指定编译标准,例如:-std=c++11、-std=c++14
  • -g: 包含调试信息
  • -w: 不显示警告
  • -O: 优化等级,通常使用:-O3
  • -I: 加在头文件路径前
  • fPIC: (Position-Independent Code), 产生的没有绝对地址,全部使用相对地址,代码可以被加载到内存的任意位置,且可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的
    |> 链接选项
  • -l: 加在库名前面
  • -L: 加在库路径前面
  • -Wl,<选项>: 将逗号分隔的 <选项> 传递给链接器
  • -rpath=: “运行” 的时候,去找的目录。运行的时候,要找 .so 文件,会从这个选项里指定的地方去找

1.4 默认规则

  • CC: Program for compiling C programs; default cc
    用于指定编译 C 语言程序的编译器,默认是 cc。通过设置这个变量来更改使用的 C 编译器。
  • CXX: Program for compiling C++ programs; default g++
    用于指定编译 C++ 程序的编译器,默认是 g++。通过设置这个变量来更改使用的 C++ 编译器。
  • CFLAGS: Extra flags to give to the C compiler
    用于指定额外的标志(flags)给 C 编译器。通过 CFLAGS 设置编译器的优化选项或者警告选项。
  • CXXFLAGS: Extra flags to give to the C++ compiler
    用于指定额外的标志给 C++ 编译器,类似于 CFLAGS,但专门用于 C++。
  • CPPFLAGS: Extra flags to give to the C preprocessor
    用于指定额外的标志给 C 预处理器。这可以包括宏定义、头文件搜索路径等。通常在这里设置 -I 标志以添加头文件搜索路径。
  • LDFLAGS: Extra flags to give to compilers when they are supposed to invoke the linker
    用于指定额外的标志给链接器。这可以包括库搜索路径、链接的库文件等。例如,你可以通过 LDFLAGS 添加链接库的路径。
    示例
CFLAGS=-O2 -Wall
LDFLAGS=-L/usr/local/lib

1.4 库

静态库的编译

# 先编译为.o文件
gcc -c [.c] -o [自定义文件名] 
gcc -c [.c] [.c] ...  

# 再编译为静态库文件.a
ar cr [lib库名.a] [.o] [.o] ... # cr表示create and replace,若不存在则创建,存在则替换

# 链接成可执行文件
gcc [.c] [.a] -o [自定义输出文件名]
gcc [.c] -o [自定义输出文件名] -l[库名] -L[库所在路径]

动态库的编译

# 先编译为.o文件
gcc -c -fpic [.c/.cpp][.c/.cpp]... 
# 再编译为动态库文件.so
gcc -shared [.o][.o]... -o [lib自定义库名.so]
# 链接库到可执行文件
gcc [.c/.cpp] -o [自定义可执行文件名]  -l[库名] -L[库路径] -Wl,-rpath=[库路径]  # -Wl, -rpath=[库路径] 可省略

2 Makefile

2.1 基本格式

targets : prerequisties
	command (tab键)
  • target:目标文件,可以是 OjectFile,也可以是执行文件,还可以是一个标签(Label)
  • prerequisite:生成 target 所需要的依赖项。
  • command:是 make 需要执行的命令
Makefile
debug :
	@echo hello #  使用tab键,若不加@则会先输出命令"echo hello",再输出命令执行的结果"hello"
终端执行:
make debug
hello

2.2 基本规则

  • make 会在当前目录下找到一个名字叫 Makefilemakefile 的文件
  • 如果找到,它会找文件中第一个目标文件(target),并把这个文件作为最终的目标文件
  • 如果 target 文件不存在,或是 target 文件依赖的 .o 文件(prerequities)的文件修改时间要比 target 这个文件新,就会执行后面所定义的命令 command 来生成 target 这个文件
  • 如果 target 依赖的 .o 文件(prerequisties)也存在,make 会在当前文件中找到 target 为 .o 文件的依赖性,如果找到,再根据那个规则生成 .o 文件
  • 伪目标:为了避免 target 和 Makefile 同级目录下 文件/文件夹 重名的这种情况,可以使用一个特殊的标记 .PHONY 来显式地指明一个目标是 “伪目标”,向 make 说明,不管是否有这个文件/文件夹,这个目标就是 “伪目标”
    如,在当前文件夹下存在一个叫clean的文件,然后我的目标文件也是clean
clean :
 	@rm -rf deleted 

.PHONY : clean  # 生成一个伪目标

2.3 变量

**变量的初始化与引用:变量在声明时需要给予初值(空值也是初值),而在使用时,需要给在变量名前加上 $ 符号,并用小括号 ()或者{} 把变量给包括起来。

2.3.1 变量的定义与引用

cpp := src/main.cpp  # 直接赋初值
obj := objs/main.o

# 使用()或者{}
$(obj) : ${cpp}
	@g++ -c $(cpp) -o $(obj)

compile : $(obj)
make complie  # 编译:main.cpp ——> main.o

预定义变量

  • $@: 目标(target)的完整名称
  • $<: 第一个依赖文件(prerequisties)的名称
  • $^: 所有的依赖文件(prerequisties),以空格分开,不包含重复的依赖文件
cpp := src/main.cpp 
obj := objs/main.o

$(obj) : ${cpp}
	@g++ -c $< -o $@
	@echo $^

compile : $(obj)
.PHONY : compile

2.4 常用符号

2.4.1 = 简单赋值

  • 简单的赋值运算符
  • 用于将右边的值分配给左边的变量
  • 如果在后面的语句中重新定义了该变量,则将使用新的值
HOST_ARCH   = aarch64
TARGET_ARCH = $(HOST_ARCH)

# 更改了变量 a
HOST_ARCH   = amd64

debug:
	@echo $(TARGET_ARCH)

:与 := 的区别

VAR1 = one
VAR2 = $(VAR1) two
VAR1 = three

# 使用 :=
VAR3 := four
VAR4 := $(VAR3) five
VAR3 := six

all:
	@echo "Using =:"
	@echo "VAR1 = $(VAR1)"
	@echo "VAR2 = $(VAR2)"
	@echo ""
	@echo "Using :=:"
	@echo "VAR3 = $(VAR3)"
	@echo "VAR4 = $(VAR4)"
Using =:
VAR1 = three
VAR2 = three two

Using :=:
VAR3 = six
VAR4 = four five  # 使用了VAR3一开始展开的值
  1. = 赋值: 这是最基本的赋值方式,也是最简单的。它会在使用变量的地方展开所有引用。
  2. := 赋值: 这是一种更高效的赋值方式。它会在赋值时展开右侧的变量,但在后续使用时不再重新展开。
  3. 在大型项目中,为了避免不必要的性能开销,常常会使用 := 赋值方式

2.4.2 ?= 默认赋值

  • 默认赋值运算符
  • 如果该变量已经定义,则不进行任何操作
  • 如果该变量尚未定义,则求值并分配
HOST_ARCH  = aarch64  # "HOST_ARCH ="  也算赋值
HOST_ARCH ?= amd64

debug:
    @echo $(HOST_ARCH)

2.4.3 += 累加赋值

include_path := src
CXXFLAGS := -m64 -fPIC -g -O0 -std=c++11 -w -fopenmp
CXXFLAGS += $(include_paths) # CXXFLAGS = CXXFLAGS + $(include_paths)
debug :
 	@echo $(CXXFLAGS)
-m64 -fPIC -g -O0 -std=c++11 -w -fopenmp src

2.4.4 \ 续行符

LDLIBS := cudart opencv_core \
          gomp nvinfer protobuf cudnn pthread \
          cublas nvcaffe_parser nvinfer_plugin 
cudart opencv_core gomp nvinfer protobuf cudnn pthread cublas nvcaffe_parser nvinfer_plugin

2.4.5 */% 通配符

 `*`: 通配符表示匹配任意字符串,可以用在目录名或文件名中 
 `%`: 通配符表示匹配任意字符串,并将匹配到的字符串作为变量使用

2.5 常用函数

书写规则:

$(fn, arguments) or ${fn, arguments}
  • fn: 函数名
  • arguments: 函数参数,参数间以逗号 , 分隔,而函数名和参数之间以“空格”分隔
  • 常见函数:shell、subst、patsubst、foreach、dir
  • 其他函数:notdir、filter、basename
    目录结构如下:
    ![[Pasted image 20231130230923.png]]

2.5.1 shell

shell命令函数

$(shell <command> <arguments>)
  • 名称:shell 命令函数 —— shell
  • 功能:调用 shell 命令 command
  • 返回:函数返回 shell 命令 command 的执行结果
    示例
# 1. 找到src目录下的.cpp文件: shell
cpp_srcs := $(shell find src -name *.cpp)
debug :
	@echo $(cpp_srcs)

.PHONY : debug

make debug
src/func1.cpp src/func2.cpp src/func3.cpp

2.5.2 subst

替换字符串函数

$(subst <from>,<to>,<text>)
  • 名称:字符串替换函数——subst
  • 功能:把字串 <\text> 中的 <\from> 字符串替换成 <\to>
  • 返回:函数返回被替换过后的字符串
    示例
# 2. subst : 字符串替换函数
cpp_srcs := $(shell find src/ -name *.cpp)
cpp_objs := $(subst src/,objs/,$(cpp_srcs))  # 替换目录名为objs
cpp_objs := $(subst .cpp,.o,$(cpp_objs)) # 替换文件名为.o
debug :
	@echo $(cpp_srcs)
	@echo $(cpp_objs)

make debug
src/func1.cpp src/func2.cpp src/func3.cpp
objs/func1.o objs/func2.o objs/func3.o

2.5.3 patsubst

$(patsubst <pattern>,<replacement>,<text>)
  • 名称:模式字符串替换函数 —— patsubst
  • 功能:通配符 %,表示任意长度的字串,从 text 中取出 patttern, 替换成 replacement
  • 返回:函数返回被替换过后的字符串
    示例
# 3. patsubst : 模式字符串替换函数,一次性可以替换多个文件
cpp_srcs := $(shell find src -name *.cpp)
cpp_objs := $(patsubst src/%.cpp,objs/%.o,$(cpp_srcs)) # 相当于subst的组合效果
debug :
	@echo $(cpp_srcs)
	@echo $(cpp_objs)

make debug
src/func1.cpp src/func2.cpp src/func3.cpp
objs/func1.o objs/func2.o objs/func3.o

2.5.4 foreach和:%=%

循环遍历字符串,空格作为分隔符

$(foreach <var>,<list>,<text>)
  • 名称:循环函数——foreach。
  • 功能:把字串<list>中的元素逐一取出来,执行<text>包含的表达式
  • 返回:<text>所返回的每个字符串所组成的整个字符串(以空格分隔)
    示例
# 4. foreach: 循环函数——> 替换{变量:%=-I%}
include_paths := /usr/include \
				/usr/local/include

include_paths_foreach := $(foreach item,$(include_paths),-I$(item))  # 以空格符作为分割符
# 使用{变量:%=-I%}
include_paths_I = $(include_paths:%=-I%) # 和foreach效果一样,推荐这种写法
debug :
	@echo $(include_paths_foreach)
	@echo $(include_paths_I)
make debug
-I/usr/include -I/usr/local/include
-I/usr/include -I/usr/local/include

2.5.5 dir

返回目录的路径或者从路径中提取目录路径

$(dir <names...>)
  • 名称:取目录函数——dir。
  • 功能:从文件名序列<\names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前
    的部分。如果没有反斜杠,那么返回“./”。
  • 返回:返回文件名序列<\names>的目录部分。
    示例
# 5. dir: 取目录函数
# 先删除objs目录
cpp_srcs := $(shell find src -name *.cpp)
cpp_objs := $(patsubst src/%.cpp,objs/%.o,$(cpp_srcs))
debug :
	@echo $(cpp_srcs)
	@echo $(cpp_objs)
# 目的:将src目录下的.cpp文件编译生成.o目标文件存放在objs目录下
objs/%.o : src/%.cpp
	@mkdir -p $(dir $@)           
	@g++ -c $^ -o $@

compile : $(cpp_objs)  # make compile 编译命令,找到依赖项cpp_objs-=objs/%.o,执行该编译命令

make compile

![[Pasted image 20231130232129.png]]

2.5.6 notdir和filter

  • notdir: 在一个绝对路径中取最后一个非目录的文件名称
  • filter: 按照一定的规则对字符串进行过滤
# notdir
$(notdir <names...>)
# filter
$(filter <names...>)

示例

# 6. notdir:在/usr/lib目录下,分别取静态文件名和动态文件名
libs := $(notdir $(shell find /usr/lib -name lib*))
# 7 filter:过滤出静态或者动态文件
a_libs := $(filter %.a,$(libs))
so_libs := $(filter %.so,$(libs))
debug :
	@echo $(a_libs)
	@echo $(so_libs)

make debug

![[Pasted image 20231130232450.png]]

2.5.7 basename

取文件名(不包括后缀)

$(basename <names...>)

示例

# 7 filter:过滤出静态或者动态文件
a_libs := $(filter %.a,$(libs))
so_libs := $(filter %.so,$(libs))
# debug :
# 	@echo $(a_libs)
# 	@echo $(so_libs)
# 8 basename: 取库名(不包含后缀)
a_libs_basename := $(basename $(a_libs))
so_libs_basename := $(basename $(so_libs))
debug :
	@echo $(a_libs_basename)
	@echo $(so_libs_basename)

make debug

![[Pasted image 20231130232754.png]]
示例

# 去掉指定的字符串,使用subst
libs_name := $(subst lib, ,$(a_libs_basename))
debug :
	@echo $(libs_name)

2.5.8 filter-out

剔除字符串

$(filter-out <names...>)

示例

# 8 filter-out: 剔除objs/func1.o字符串
cpp_objs := $(shell find objs -name *.o)
cpp_objs_filtered = $(filter-out objs/func1.o,$(cpp_objs))

debug :
	@echo $(cpp_objs)
	@echo $(cpp_objs_filtered)
make debug
objs/func1.o objs/func2.o objs/func3.o
objs/func2.o objs/func3.o

2.5.9 wildcard

通配符
示例

cpp_srcs := $(wildcard src/*.cc src/*.cpp src/*.c)
debug :
	@echo $(cpp_srcs)
make debug
src/func1.cpp src/func2.cpp src/func3.cpp

示例 1 编译四个阶段的程序

  • 编译四个阶段的程序
    目录结构:
    Test
    -src
    -main.cpp
# 用于检查每个编译阶段的输出
debug :
	@echo $(cpp_exe)

# 清空指定文件夹下的文件
clean :
	@rm -rf src/*.i
# 1 进行预编译
cpp_srcs := $(shell find src -name *.cpp)
cpp_I := $(patsubst src/%.cpp,src/%.i,$(cpp_srcs))

src/%.i : src/%.cpp
	@g++ -E $^ -o $@
preprocess : $(cpp_I)

# 2 进行汇编
cpp_s := $(patsubst src/%.i,src/%.s,$(cpp_I))

src/%.s : src/%.i
	@g++ -S $^ -o $@

assemble : $(cpp_s)

# 3 目标文件生成
cpp_o := $(patsubst src/%.s,objs/%.o,$(cpp_s))

objs/%.o : src/%.s
	@mkdir -p $(dir $@)
	@g++ -c $^ -o $@
object : $(cpp_o)

# 4 链接可执行文件
cpp_exe := $(patsubst objs/%.o,bin/%,$(cpp_o))

bin/% : objs/%.o
	@mkdir -p $(dir $@)
	@g++ $^ -o $@

executable : $(cpp_exe)

# 运行
run : bin/main
	@./$<
.PHONY : debug clean preprocess assemble object executable run
make run

经过一系列编译链接后生成的目录
![[Pasted image 20231201094511.png]]

示例 2 编译带头文件的程序

  • 编译带头文件的程序
    目录结构
    Test2
    -include
    -add.h
    -minus.h
    -src
    -main.cpp
    -add.cpp
    -minus.cpp
    Makefile
// add.h
#ifndef ADD_H
#define ADD_H

int add(int, int);

#endif // !ADD_H

// add.cpp
#include "add.h"
int add(int a, int b)
{
    return a + b;
}

// minus.h
#ifndef MINUS_H
#define MINUS_H

int minus(int, int);

#endif // !MINUS_H

// minus.cpp
#include "minus.h"

int minus(int a, int b)
{
    return a - b;
}

// main.cpp
#include<iostream>
#include "add.h"
#include "minus.h"

int main()
{
    int a = 11;
    int b = 12;
    int add_res = add(a, b);
    int minus_res = minus(a, b);
    std::cout << "a + b = " << add_res << "\n";
    std::cout << "a - b = " << minus_res << "\n";
    return 0;
}

Makefile编译

# 1 通过源文件的文件名替换为目标文件的文件名
cpp_srcs := $(shell find src -name *.cpp)
cpp_objs := $(patsubst src/%.cpp,objs/%.o,$(cpp_srcs))

# 2 编译源文件为目标文件
# 先设置库文件的路径,自定义的头文件若要被包含需要指定头文件的路径,不然编译器会找不到
include_path := /home/gaoxl/vscode/makefile_learning/Test2/include
# 加上-I
I_flags := $(include_path:%=-I%)
# 加上编译选项,先创建目录,不然编译会报错
compile_options := -std=c++11 -g -O3 -w $(I_flags)
objs/%.o : src/%.cpp
	@mkdir -p $(dir $@)
	@g++ -c $^ -o $@ $(compile_options)

# 3 生成可执行文件,先创建目录,不然编译会报错
bin/exec : $(cpp_objs)
	@mkdir -p $(dir $@)
	@g++ $^ -o $@

# 4 通过run执行
run : bin/exec
	@./$<	

# 5 补充:再次编译时清空objs下的目标文件
clean : objs/%.o
	rm -rf $^

debug :
	@echo $(cpp_srcs)
	@echo $(cpp_objs)
	@echo $(I_flags)
	@echo $(compile_options)
.PHONY : debug run clean 
make run

生成目录:
![[Pasted image 20231201105421.png]]
结果:
a + b = 23
a - b = -1

示例3 静态库的编译

目录结构
Test3
-src
-main.cpp
-add.cpp
-minus.cpp
-include
-add.h
-minus.h
Makefile
编译流程:先将add.cpp minus.cpp编译为目标文件,存放在objs目录下(编译时主要需要用到源文件的头文件路径);然后将objs目录下的目标文件add.o``minus.o编译为静态文件;最后,与main.cpp或者main.o文件与静态文件链接为可执行文件(若选择main.cpp则需要用到对应的头文件路径;静态文件需要用到对应的静态库路径和静态文件名称,如-lxxx的格式)

// add.h
#ifndef ADD_H
#define ADD_H

int add(int, int);

#endif // !ADD_H

// add.cpp
#include "add.h"
int add(int a, int b)
{
    return a + b;
}

// minus.h
#ifndef MINUS_H
#define MINUS_H

int minus(int, int);

#endif // !MINUS_H

// minus.cpp
#include "minus.h"

int minus(int a, int b)
{
    return a - b;
}

// main.cpp
#include<iostream>
#include "add.h"
#include "minus.h"

int main()
{
    int a = 11;
    int b = 12;
    int add_res = add(a, b);
    int minus_res = minus(a, b);
    std::cout << "a + b = " << add_res << "\n";
    std::cout << "a - b = " << minus_res << "\n";
    return 0;
}

Makefile

# 目标:编译链接静态库
# 1.先找到除了main函数的源文件
cpp_srcs := $(filter-out src/main.cpp,$(shell find src -name *.cpp))
cpp_objs := $(patsubst src/%.cpp,objs/%.o,$(cpp_srcs))
# 设置头文件路径
inlcude_paths := ./include
I_options := $(inlcude_paths:%=-I%)
compile_options := -std=c++11 -g -w -O3 $(I_options)
# 然后编译为目标文件
objs/%.o : src/%.cpp
	@mkdir -p $(dir $@)
	@g++ -c $^ -o $@ $(compile_options)
# 编译为静态文件
lib/liboprator.a : $(cpp_objs)
	@mkdir -p $(dir $@)
	@ar -cr $@ $^
static_lib : lib/liboprator.a

lib_paths := ./lib/liboprator.a
# 链接文件
# 需要静态库名、静态库路径
lib_paths := ./lib
L_options := $(lib_paths:%=-L%)
lib_name := $(subst lib,,$(basename $(notdir $(shell find lib -name *.a))))  # 提取静态库名称oprator
l_options := $(lib_name:%=-l%)  # 加上选项-loprator
linking_flags := $(L_options) $(l_options)  # 链接选项-loprator -L./lib
bin/exec : src/main.cpp
	@mkdir $(dir $@)
	@g++ $< $(compile_options) -o $@ $(linking_flags)

run : bin/exec  # 执行可执行文件exec
	@./$<
debug :
	@echo $(cpp_srcs)   
	@echo $(cpp_objs)
	@echo $(I_options)
	@echo $(compile_options)
	@echo $(L_options)
	@echo $(l_options)
	@echo $(linking_flags)
.PHONY : debug static_lib run
# debug测试输出字符串
make debug
src/add.cpp src/minus.cpp
objs/add.o objs/minus.o
-I./include
-std=c++11 -g -w -O3 -I./include
-L./lib
-loprator
-L./lib -loprator
# run执行可执行文件
make run
a + b = 23
a - b = -1

示例4 动态库的编译

主要流程

  • 编译成动态库文件
    • 源文件.cpp编译为目标文件.o,需要用到头文件路径及选项
    • 编译除了main.o以外的.o文件为动态库文件.so
  • 链接成可执行文件
    • 将main.o与动态库链接生成可执行文件,需要用到动态库路径和选项
      目录结构
      Test4
      -src
      -main.cpp
      -add.cpp
      -minus.cpp
      -inlude
      -add.h
      -minus.h
      Makefile
// add.h
#ifndef ADD_H
#define ADD_H

int add(int, int);

#endif // !ADD_H

// add.cpp
#include "add.h"

int add(int a, int b)
{
    return a + b;
}
// minus.cpp
#include "minus.h"

int minus(int a, int b)
{
    return a - b;
}
// minus.h
#ifndef MINUS_H
#define MINUS_H

int minus(int, int);

#endif // !MINUS_H
// main.cpp
#include<iostream>
#include "add.h"
#include "minus.h"

int main()
{
    int a = 11;
    int b = 12;
    int add_res = add(a, b);
    int minus_res = minus(a, b);
    std::cout << "a + b = " << add_res << "\n";
    std::cout << "a - b = " << minus_res << "\n";
    return 0;
}

Makefile


# 编译动态库

# 头文件库的路径
inlcude_paths := ./include
I_options := $(inlcude_paths:%=-I%)

# 头文件编译选项
compile_options := -g -O3 -w -std=c++11 -fPIC $(I_options)

# 编译目标文件
cpp_srcs := $(shell find src -name *.cpp)  # 源文件路径
cpp_objs := $(patsubst src/%.cpp,objs/%.o,$(cpp_srcs))  # 目标文件路径

objs/%.o : src/%.cpp
	@mkdir -p $(dir $@)
	@g++ -c $^ -o $@ $(compile_options)

compile_objs : $(cpp_objs)

# 生成动态库
so_objs := $(filter-out objs/main.o,$(cpp_objs))  # 将需要的.o文件提取出来
lib/libop.so : $(so_objs)
	@mkdir -p $(dir $@)
	@g++ -shared $^ -o $@
so_run : lib/libop.so

# 链接动态库执行
# 设置动态库文件路径和选项
so_paths := ./lib
so_L := $(so_paths:%=-L%)
so_name := op
so_l := $(so_name:%=-l%)
r_options := $(so_paths:%=-Wl,rpath=%)
linking_flags := $(so_L) $(so_l) $(r_options)

bin/exec : objs/main.o compile_objs so_run
	@mkdir -p $(dir $@)
	@g++ $< -o $@ $(linking_flags)
run  : bin/exec
	@./$<
debug :
	@echo $(compile_options)
	@echo $(cpp_srcs)
	@echo $(cpp_objs)
	@echo $(linking_flags)
.PHONY : debug compile_objs
make debug
# 输出
-g -O3 -w -std=c++11 -fPIC -I./include
src/main.cpp src/add.cpp src/minus.cpp
objs/main.o objs/add.o objs/minus.o
-L./lib -lop -Wl,rpath=./lib

make run
# 输出
a + b = 23
a - b = -1
  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值