000_Makefile Study(1)
一、编译指令学习
1、预处理(头文件引入/宏展开/指令的处理)
gcc -E *.c > *.txt # 将c文件预处理内容输入到文本文件中
2、编译
gcc -c *.c # 默认将*.c编译成*.o
3、链接
gcc 入口文件.o 依赖文件.o -o 可执行程序名称 #生成可执行程序
1.1 示例——编译文件基础
main.exe:main.o add.o jian.o
gcc main.o add.o jian.o -o main.exe
main.o:main.c
gcc -c main.c -o main.o
add.o:add.c
gcc -c add.c -o add.o
jian.o:jian.c
gcc -c jian.c -o jian.o
clean:
rm -f *.o main.exe
二、Makefile基本语法
目标:依赖
Tab键 命令
目标:一般是指要编译的目标,也可以是一个动作
依赖:指执行当前目标所要依赖的选项,包括其它目标,某个具体文件或库等
一个目标可以有多个依赖
命令:该目标下要执行的具体命令,可以没有,也可以有多条。
多条时,每条命令一行
make 常用选项
make [-f file] [options] [target]
Make 默认在当前目录中寻找GUNmakfile,makefile,Makefile的文件作为make的输入文件
-f 可以指定除上述文件名之外的文件作为输入文件
-v 显示版本号
-n 只输出命令,但并不执行,一般用来测试
-s 只执行命令,但不显示具体命令,此处可在命令中用@符抑制命令输出
-w 显示执行前后的路径
-C dir 指定makefile 所在的目录
没有指定目标时,默认使用第一个目标
如果指定,则执行对应的命令
2.1 示例——单一目标
a:
@echo "hello makefile" #加@表示抑制输出,否则会把‘echo "hello makefile"’这句代码也输出出来
b:
@echo "hello makefile b" #同一个makefile文件中,默认只会执行第一句
2.2 示例——目标有依赖项
a:b #表示a依赖于b,那么b目标下面的命令就会先于a目标下面的命令执行
@echo "hello makefile" #加@表示抑制输出,否则会把‘echo "hello makefile"’这句代码也输出出来
b:
@echo "hello makefile b" #同一个makefile文件中,默认只会执行第一句
2.3 示例——单一目标多条指令
a:
@echo "hello world a"
@ls ./
gcc -lstdc++ xxx.cpp #gcc编译c++,执行这个makefile文件会生成a.out文件
clean:
rm -rf *.out #清除命令
三、makefile 中的变量
3.1 系统变量
$* 不包括扩展名的目标文件名称
$+ 所有的依赖文件,以空格分隔
$< 表示规则中的第一个条件,依次去匹配依赖
$? 所有时间戳比目标文件晚的依赖文件,以空格分隔
$@ 目标文件的完整名称
$^ 所有不重复的依赖文件,以空格分隔
$% 如果目标是归档成员,则该变量表示目标的归档成员名称
示例 ——使用通配符来解决问题
main.exe:main.o add.o jian.o
gcc *.o -o $@
%.o:%.c
gcc -c $< -o $@
clean:
rm -f *.o main.exe
3.2 系统常量(实质就是一些宏定义)
AS 汇编程序的名称 默认为as
CC C编译器名称 默认为cc
CPP C预编译器名称 默认为cc -E
CXX C++编译器名称 默认为g++
RM 文件删除程序别名 默认rm -f
示例——利用系统常量来实现跨平台
main.exe:main.o add.o jian.o
$(CC) *.o -o $@
%.o:%.c
$(CC) -c $< -o $@
clean:
$(RM) *.o main.exe
3.3 自定义变量
定义:变量名=变量值
使用:$(变量名)/${变量名}
示例
CC = gcc # CC是变量名,gcc是变量值
CFLAGS = -lm -Wall -g # 将一串命令定义为一个Flag
#如何使用变量
main: main.c
$(CC) $(CFLAGS) main.c -o main
3.4 多个入口函数
#默认只执行能够生成可执行文件的第一条指令
CC = gcc
CFLAGS = -lm -Wall -g # 将一串命令定义为一个Flag
all: test1 test2 #指定多个执行程序的名称
四、gcc编译流程
gcc -lstdc++ main.cpp #直接从源代码到目标可执行文件了
把过程拆分
预处理 gcc -E main.cpp > main.ii
编译 gcc -S main.ii [-o xxx.s] # 默认是main.s的汇编文件
汇编 gcc -c main.s #得到名为main.o(windows是.obj)的二进制文件
链接 gcc -lstdc++ main.o #得到名为 a.out的可执行文件
图
五、Makefile中的伪目标和模式匹配
5.1 伪目标
.PHONY:目标名
声明目标为伪目标之后,makefile将不会判断目标是否存在或该目标是否需要更新
%.o:%.cpp .o依赖于对应的.cpp
wildcard $(wildcard ./*.cpp) #获取当前目录下所有的.cpp文件
patsubst $(patsubst %.cpp, %.o, ./*.cpp) 将对应的cpp文件名替换成.o文件名
示例——伪目标使用场景
使用·touch clean·生成clean文件,如果没有伪目标的声明,那么再执行make clean时将无法执行
.PHONY:clean
main.exe:main.o add.o jian.o
$(CC) *.o -o $@
%.o:%.c
$(CC) -c $< -o $@
clean:
$(RM) *.o main.exe
示例——模式匹配
.PHONY:clean
OBJ=$(patsubst %.c,%.o,$(wildcard ./*.c))
TARGET=maiin
$(TARGET):$(OBJ)
$(CC) *.o -o $@
%.o:%.c
$(CC) -c $< -o $@
clean:
$(RM) *.o main.exe
show:
echo $(wildcard ./*.c)
echo ./*.c
echo $(patsubst %.c,%.o,./*.c)
六、makefile编译动态链接库(.dll /.so 库文件)
动态链接库:不会把代码编译到二进制文件中,而是运行时才去加载,所以只需要维护一个地址
-fPIC 产生位置无关的代码
-shared 共享
-l(小L) 指定动态库
-I 指定头文件目录,默认当前目录
-L 手动指定库文件搜索目录,默认只链接共享目录
动态链接库: 好处时程序可以和库文件分离,可以分别发版,然后库文件可以被多处共享
动态:运行时才去加载,动态加载
链接:指库文件和二进制程序分离
库:.dll/.so
6.1 Linux 示例
g++ -shared -fPIC animal.cpp -o libanimal.so #将animal.cpp编译成so库
#testAnimal.cpp在命令行中的位置要放对,否则会出错误
g++ testAnimal.cpp -lanimal -L./ -o testAnimal
#指定动态库的寻找路径
#error while loading shared libraries: xx.so: cannot open shared object file: No such file 错误的原因和解决办法
# https://blog.csdn.net/my_angle2016/article/details/113524560
export LD_LIBRARY_PATH=~/StudySpace/000_makefileStudy/002_demo:$LD_LIBRARY_PATH
#执行文件
./testAnimal
testAnimal:libanimal.so
$(CXX) testAnimal.cpp -lanimal -L./ -o testAnimal
#so库的名字就是原文件名前面加上lib
libanimal.so:
$(CXX) -fPIC -shared animal.cpp -o libanimal.so
clean:
$(RM) *.so testAnimal
七、makefile中编译静态库
静态链接库:会把库中的代码编译到二进制文件中,当程序编译完成后,该库文件可以删除。动态链接库不行,动态链接库必须与程序同时部署,还要保证程序能加载得到库文件。与动态链接相比,静态库可以不用部署(已经被加载到程序里面了),而且运行时速度更快,但是会导致程序体积更大,并且库中的内容如果有更新,就需要重新编译生成程序。
库:.lib/.a
objdump -DC testAnimal > main.txt #反汇编
7.1 Linux示例
g++ -c xxx.cpp -o xxx.o #生成静态库所需要的.o文件
ar -r libxxx.a xxx.o #生成静态库
#指定静态库的查找路径
export LS_LIBRARY_PATH=~/StudySpace/000_makefileStudy/002_demo:$LS_LIBRARY_PATH
g++ testAnimal.cpp -lanimal -L./ -lxxx -L./ -o testAnimal #生成可执行文件
testAnimal:libanimal.so libdog.a
$(CXX) testAnimal.cpp -lanimal -L./ -ldog -L./ -o testAnimal
libanimal.so:
$(CXX) -fPIC -shared animal.cpp -o libanimal.so
libdog.a:
$(CXX) -c dog.cpp -o dog.o
ar -r libdog.a dog.o
clean:
$(RM) *.a *.so testAnimal
八、makefile通用部分做公共头文件
= 赋值操作。不管变量调用写在变量定义还是定义后,都是调用的变量最终值
:= 赋值操作。只受当前行及之前的代码影响,而不会受后面的赋值影响
8.1 示例1
#cpp文件
TARGET=c
include ../makefile
#c文件
# TARGET=z
include ../makefile
#公共头文件
SOURCE=$(wildcard ./*.cpp ./*.c)
OBJ=$(patsubst %.cpp,%.o,$(SOURCE)) #将对应的.cpp文件替换为.o文件
OBJ:=$(patsubst %.c,%.o,$(OBJ)) #将对应的.cp文件替换为.o文件
.PHONY:clean
ifndef TARGET
TARGET=test
endif
$(TARGET):$(OBJ)
$(CXX) $^ -o $@
clean:
rm -f *.o $(TARGET)