第一部分:
Makefile
一个工程中的源文件不计其数,其按类型,功能,模块分别放在若干目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至进行更复杂的功能操作。
Make与Makefile的关系
make是一个命令工具,它解释Makefile中的指令。在Makefile文件中描述了整个工程所有文件的编译顺序,编译规则。
Makefile命令规则
Makefile或makefile, 一般使用Makefile。
重点
make是一个命令工具,Makefile是一个文件,make执行的时候,去读取Makefile文件中的规则,重点是makefile得自己写。
第二部分:
从一个简单的示例开始
Makefile基本语法
目标:依赖
Tab 命令
目标:一般是指要编译的目标,也可以是一个动作。
依赖:指执行当前目标所要的先项,包括其它目标,某个具体文件或库等,一个目标可以有多个依赖。
命令:该目标下要执行的具体命令,可以没有,也可以有多条。多条时,每条命令一行。
例1:a表示目标,其下是要执行的具体命令,b也表示目标,make默认执行第一个目标
a:
@echo "Hello World"
b:
@echo "Hello Make"
执行结果:
[blctrl@main-machine Firtst]$ make
Hello World
[blctrl@main-machine Firtst]$ make b
Hello Make
例2:a b c都是目标,a依赖b和c
a:b c
@echo "Hello World"
b:
@echo "Hello Make"
c:
@echo "Hello C"
执行结果:
[blctrl@main-machine make-exer]$ make
Hello Make
Hello C
Hello World
例3:a是目标,执行的语句先显示当前目录下内容,然后编译main.cpp源文件为main,编译语句用gcc -lstdc++ main.cpp -o main替代也可以。
a:
@ls ./
g++ main.cpp -o main
执行结果:显示了当前目录下内容,然后进行编译生成可执行文件main,然后在当前命令行执行这个程序。
[blctrl@main-machine Firtst]$ make
cmake-build-debug CMakeLists.txt main.cpp make-exer Makefile
g++ main.cpp -o main
[blctrl@main-machine Firtst]$ ./main
Hello, World!
例4:
a:
@ls ./
gcc -lstdc++ main.cpp -o main
clean:
@rm -rf main
@echo "make clean successfully!"
执行make后,在当前路径中产生一个main的可执行文件,执行make clean后,从当前路径中删除名为main的文件。
[blctrl@main-machine Firtst]$ make
cmake-build-debug CMakeLists.txt main.cpp make-exer Makefile
gcc -lstdc++ main.cpp -o main
[blctrl@main-machine Firtst]$ ls
cmake-build-debug CMakeLists.txt main main.cpp make-exer Makefile
[blctrl@main-machine Firtst]$ make clean
make clean successfully!
[blctrl@main-machine Firtst]$ ls
cmake-build-debug CMakeLists.txt main.cpp make-exer Makefile
make常用选项
make [-f file] [options] [target]
Make默认在当前目录寻找GUNmakefile, makefile, Makefile的文件作为make的输入文件
- -f:可以指定除了上述文件名之外的文件作为输入文件。
- -v:显示版本号
- -n:只执行命令,单步显示具体命令,此处在命令中用@符号抑制命令输出。
- -w:显示执行前执行后的路径
- -C dir:指定makefile文件所在的目录
没有指定目录时,默认使用第一个目标,如果指定,则执行对应的命令。
第三部分
gcc编译流程详解
gcc -lstdc++ main.cpp 直接从源文件到目标可执行文件
把过程分拆:
- 预处理:gcc -lstdc++ main.cpp > main.ii
- 编译:gcc -S main.ii 得到main.s的汇编文件
- 汇编:gcc -c main.s 得到名为main.o的二进制文件
- 链接:gcc -lstdc++ main.o 得到名为a.out的可执行文件
例1:如下一个源文件:
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
对以上源文件进行逐步编译:
[blctrl@main-machine Firtst]$ gcc -E main.cpp > main.ii
[blctrl@main-machine Firtst]$ gcc -S main.ii
[blctrl@main-machine Firtst]$ gcc -c main.s
[blctrl@main-machine Firtst]$ gcc -lstdc++ main.o
[blctrl@main-machine Firtst]$ ./a.out
Hello, World!
例2:在一个指定目录中提供以下头文件和源文件:
//add.h头文件
#ifndef FIRTST_ADD_H
#define FIRTST_ADD_H
int add(int, int);
#endif //FIRTST_ADD_H
//add.cpp源文件
#include "add.h"
int add(int a, int b)
{
return a + b;
}
//sub.h头文件
#ifndef FIRTST_SUB_H
#define FIRTST_SUB_H
int sub(int , int );
#endif //FIRTST_SUB_H
//sub.cpp源文件
#include "sub.h"
int sub(int a, int b)
{
return a - b;
}
//mul.h头文件
#ifndef FIRTST_MUL_H
#define FIRTST_MUL_H
int mul(int , int );
#endif //FIRTST_MUL_H
//mul.cpp源文件
#include "mul.h"
int mul(int a, int b)
{
return a * b;
}
//calc.cpp源文件
nclude <stdio.h>
#include "add.h"
#include "sub.h"
#include "mul.h"
int main(int argc, char * argv[])
{
int a = 3, b = 2;
printf("a + b = %d\n", add(a, b));
printf("a - b = %d\n", sub(a, b));
printf("a * b = %d\n", mul(a, b));
return 0;
}
在相同目录下,新建一个名为Makefile的文件,内容如下:
calc:add.o sub.o mul.o calc.o
gcc add.o sub.o mul.o calc.cpp -o calc
add.o:add.cpp
gcc -c add.cpp -o add.o
mul.o:mul.cpp
gcc -c mul.cpp -o mul.o
sub.o:sub.cpp
gcc -c sub.cpp -o sub.o
clean:
rm -rf *.o calc
在以上路径中,执行make命令,在执行后生成了源文件的对象文件,并且产生了一个可执行文件calc,执行这个calc程序,查看结果:
[blctrl@main-machine Second]$ ls
add.cpp add.h calc.cpp Makefile mul.cpp mul.h sub.cpp sub.h
[blctrl@main-machine Second]$ make
gcc -c add.cpp -o add.o
gcc -c sub.cpp -o sub.o
gcc -c mul.cpp -o mul.o
g++ -c -o calc.o calc.cpp
gcc add.o sub.o mul.o calc.cpp -o calc
[blctrl@main-machine Second]$ ls
add.cpp add.h add.o calc calc.cpp calc.o Makefile mul.cpp mul.h mul.o sub.cpp sub.h sub.o
[blctrl@main-machine Second]$ ./calc
a + b = 5
a - b = 1
a * b = 6
执行make clean可以删除以上的.o文件以及calc可执行文件:
[blctrl@main-machine Second]$ make clean
rm -rf *.o calc
[blctrl@main-machine Second]$ ls
add.cpp add.h calc.cpp Makefile mul.cpp mul.h sub.cpp sub.h
第四部分
Makefile中的变量
1) 系统变量
$*:不包括扩展名的目标文件名称
$+:所有的依赖文件,以空格分隔
$<:表示规则中的第一条件
$?:所有时间戳比目标文件晚的依赖文件,以空格分隔
$@:目标文件的完整名称
$^:所有不重复的依赖文件,以空格分隔
$%:如果目标是归档成员,则该变量表示目录的归档成员名称
2)系统常量
AS 汇编程序的名称,默认为as
CC C编译器名称,默认cc
CPP C预编译器名称,默认cc -E
CXX C++编译器名称,默认g++
RM 文件删除程序别名,默认rm -f
3) 自定义变量
定义:变量名=变量值
使用:$(变量名)/${变量名}
用Makefile中的变量,来改写以上Makefile文件,内容如下:
OBJS=add.o sub.o mul.o calc.o
TARGET=calc
$(TARGET):$(OBJS)
$(CXX) $^ -o $(TARGET)
add.o:add.cpp
$(CXX) -c $^ -o $@
mul.o:mul.cpp
$(CXX) -c $^ -o $@
sub.o:sub.cpp
$(CXX) -c $^ -o $@
calc.o:calc.cpp
$(CXX) -c $^ -o $@
clean:
$(RM) *.o $(TARGET)
show:
echo $(CC)
echo $(AS)
echo $(CPP)
echo $(CXX)
echo $(RM)
执行make show能够查看以上指定的系统常量的值:
[blctrl@main-machine Second]$ make show
echo cc
cc
echo as
as
echo cc -E
cc -E
echo g++
g++
echo rm -f
rm -f
第五部分
Makefile中伪目标和模式匹配
伪目标 .PHONY:clean
声明伪目标后,makefile将不会判断目标是否存在或者该目标是否需要更新。
1) %.o:%.cpp .o依赖对应的.cpp文件
2) wildcard $(wildcard ./*.cpp)获取当前目录下所有的.cpp文件
3) patsubst $(patsubst %.cpp, %.o, ./*.cpp)将对应的cpp文件名替换成.o文件名
用伪目标和模式匹配来重写上一部分中的Makefile文件:
注意:
1)目标和依赖的相同部分用%来匹配
2)OBJS是来自当前目录中所有.cpp文件的文件名的后缀被替换成了.o
OBJS=$(patsubst %.cpp, %.o, $(wildcard ./*.cpp))
TARGET=calc
$(TARGET):$(OBJS)
$(CXX) $^ -o $(TARGET)
%.o:%.cpp
$(CXX) -c $^ -o $@
clean:
$(RM) *.o $(TARGET)
show:
echo $(CC)
echo $(AS)
echo $(CPP)
echo $(CXX)
echo $(RM)
echo $(wildcard ./*.cpp)
echo $(patsubst %.cpp, %.o, ./*.cpp)
.PHONY:clean show
第六部分
Makefile中编译动态链接库
动态链接库:不会把代码编译到二进制文件中,而是在运行时才去加载,所以只需要维护一个地址。
- -fPIC:产生位置无关的代码。
- -shared:共享
- -l(小写L):指定动态库
- -I(大写i):指定头文件目录,默认当前目录
- -L:手动指定文件搜索目录,默认只链接共享目录
动态链接库
动态:运行时才去加载,动态加载
链接:指库文件和二进制程序分离,用某种特殊手段维护两者之间的关系。
库:库文件.dll(windows) .so(linux)
Linux默认动态库路径配置文件:
/etc/ld.so.conf /etc/ld.so.conf.d/*.conf
1) 动态库编译完成之后要发布(即把产生的.so文件复制到以下两个目录之一中),否则程序运行找不到。
/usr/lib
/usr/local/lib
2) 指定一个环境变量LD_LIBRARY_PATH=<库文件所在的路径>
LD_LIBRARY_PATH=<库文件所在的路径>
export LD_LIBRARY_PATH
或者
export LD_LIBRARY_PATH=<库文件所在的路径>
例1:在一个路径下建立以下两个目录,其目录结构如下:
[blctrl@main-machine Firtst]$ ls dynamic/
SoTest.cpp SoTest.h test.cpp
[blctrl@main-machine Firtst]$ ls Dynamic-Test/
main.cpp SoTest.h
dynamic路径下有一个SoTest类,其头文件和源文件内容如下:
/* SoTest.h */
#ifndef FIRTST_SOTEST_H
#define FIRTST_SOTEST_H
class SoTest {
public:
void function1();
virtual void function2() ;
virtual void function3()=0;
};
/* SoTest.cpp */
#include <stdio.h>
#include "SoTest.h"
void SoTest::function1() {
printf("SoTest::function1\n");
}
void SoTest::function2() {
printf("SoTest::function2\n");
在dynamic目录下,将上面的源文件编译为一个动态库文件:
[blctrl@main-machine dynamic]$ ls
SoTest.cpp SoTest.h
[blctrl@main-machine dynamic]$ g++ -shared -fPIC SoTest.cpp -o libSoTest.so
[blctrl@main-machine dynamic]$ ls
libSoTest.so SoTest.cpp SoTest.h
切换到Dynamic-Test目录中,复制dynamic目录中的SoTest.h头文件,并且新建一个main.cpp的源文件,内容如下:
#include <stdio.h>
#include "SoTest.h"
class Test: public SoTest{
public:
virtual void function2(void);
virtual void function3(void);
};
void Test::function2() {
printf("Test::function2\n");
}
void Test::function3() {
printf("Test::function3\n");
}
int main(int argc, char * argv[])
{
Test test;
test.function1();
test.function2();
test.function3();
return 0;
}
对以上源文件和动态链接库文件进行编译,生成一个可执行文件main,执行这个可执行文件进行测试:
}[blctrl@main-machine Dynamic-Test]$ g++ -lSoTest -L ../dynamic main.cpp -o main
[blctrl@main-machine Dynamic-Test]$ ./main
./main: error while loading shared libraries: libSoTest.so: cannot open shared object file: No such file or directory
[blctrl@main-machine Dynamic-Test]$ export LD_LIBRARY_PATH=../dynamic
[blctrl@main-machine Dynamic-Test]$ ./main
SoTest::function1
Test::function2
Test::function3
例2:建立一个以下目录结构
[blctrl@bjAli dynamic]$ ls
main.cpp Makefile SoTest.cpp SoTest.h
以上源文件和头文件与例1中相同,新建一个Makefile文件,内容如下:
TARGET=main
main:libSoTest.so
$(CXX) -lSoTest -L ./lib main.cpp -o $@
libSoTest.so:SoTest.cpp
$(CXX) -shared -fPIC $^ -o $@
@mkdir lib
@mv $@ lib
clean:
$(RM) lib/*.so $(TARGET)
$(RM) -rf lib
在以上路径中,执行make进行编译,并且查看新产生的目录结构:
[blctrl@bjAli dynamic]$ make
g++ -shared -fPIC SoTest.cpp -o libSoTest.so
g++ -lSoTest -L ./lib main.cpp -o main
[blctrl@bjAli dynamic]$ ls -R
.:
lib main main.cpp Makefile SoTest.cpp SoTest.h
./lib:
libSoTest.so
第七部分:Makefile中编译静态链接库 .a
静态链接库,会把库中的代码编译到二进制文件中,当程序编译完成后,该库文件可以被删除,而动态链接库不行,动态链接库必须与程序同时部署,还要保证程序能加载得到库文件。与动态库相同,静态库可以不用部署(已经被加载到程序里面了),而且运行时速度更快。但会导致程序提及更大,并且库中的内容如果有更新,则需要重新编译生成程序。
例:生成一个以下的目录结构
[blctrl@main-machine static-lib]$ ls
aTest.cpp aTest.h main.cpp
以上三个文件的内容如下:
aTest.h:
#ifndef FIRTST_ATEST_H
#define FIRTST_ATEST_H
class aTest {
public:
virtual void function1();
virtual void function2();
virtual void function3();
};
#endif //FIRTST_ATEST_H
aTest.cpp:
#include <stdio.h>
#include "aTest.h"
void aTest::function1() {
printf("aTest::function1\n");
}
void aTest::function2() {
printf("aTest::function2\n");
}
void aTest::function3() {
printf("aTest::function3\n");
}
main.cpp:
#include <stdio.h>
#include "aTest.h"
class Test: public aTest{
public :
virtual void function2();
virtual void function3();
};
void Test::function2() {
printf("Test::function2\n");
}
void Test::function3() {
printf("Test::function3\n");
}
int main(int argc, char * argv[])
{
Test test;
test.function1();
test.function2();
test.function3();
return 0;
}
以下步骤演示手动进行编译静态库文件,并且用用静态库文件编译生成可执行文件的过程:
# 编译源文件,生成二进制对象文件
[blctrl@main-machine static-lib]$ g++ -c aTest.cpp -o aTest.o
# 将二进制对象文件生成静态库文件
[blctrl@main-machine static-lib]$ ar crv libaTest.a aTest.o
a - aTest.o
# 查看静态库文件的内容
[blctrl@main-machine static-lib]$ ar t libaTest.a
aTest.o
# 编译并且链接静态库文件生成可执行文件
[blctrl@main-machine static-lib]$ g++ main.cpp -L. -laTest -o main
# 执行可执行文件,进行测试
[blctrl@main-machine static-lib]$ ./main
aTest::function1
Test::function2
Test::function3
把以上手动编译的过程用一个Makefile文件替代:
TARGET=main
LDFLAGS=-L.
LIBS=-laTest
$(TARGET):main.o libaTest.a
$(CXX) $< $(LDFLAGS) $(LIBS) -o $(TARGET)
%.o:%.c
$(CXX) -c $^ -o $@
libTest.a:aTest.o
@ar crv $^ $@
clean:
$(RM) *.o $(TARGET)
.PHONY:clean
执行make clean和make分别进行测试:
[blctrl@main-machine static-lib]$ make clean
rm -f *.o main
[blctrl@main-machine static-lib]$ ls
aTest.cpp aTest.h libaTest.a main.cpp Makefile
[blctrl@main-machine static-lib]$ make
g++ -c -o main.o main.cpp
g++ main.o -L. -laTest -o main
[blctrl@main-machine static-lib]$ ls
aTest.cpp aTest.h libaTest.a main main.cpp main.o Makefile
[blctrl@main-machine static-lib]$ ./main
aTest::function1
Test::function2
Test::function3