目录
Makefile库文件
前言
学习杜老师推荐的Makefile教程视频,链接。记录下个人学习笔记,仅供自己参考。
之前有转载过杜老师的从零Makefile落地算法大项目文章,感兴趣的可以看看。
本次主要学习Makefile编译库文件的相关内容。(from ChatGPT)
1.库文件
在实际的程序开发中,为了提高程序的复用性,会将一些常见的功能或算法封装成库文件,供其它程序调用。常见的库文件包括静态库和动态库两种。本篇博文的目的是利用Makefile将add.cpp文件编译成库文件(静态库和动态库)并在main.cpp中使用
文件的总体目录如下:
include
└─add.hpp
src
├─add.cpp
└─main.cpp
Makefile
其中,include文件夹下存放着add.hpp
头文件,src文件夹下存放着add.cpp
和main.cpp
源文件。
add.hpp内容如下:
#ifndef ADD_HPP
#define ADD_HPP
int add(int a, int b);
#endif // ADD_HPP
add.cpp内容如下:
int add(int a, int b)
{
return a+b;
}
main.cpp内容如下:
#include <stdio.h>
#include "add.hpp"
int main()
{
int a=10; int b=5;
int res = add(a, b);
printf("a + b = %d\n", res);
return 0;
}
2.静态库
2.1 定义
静态库是指编译时被链接到目标文件中的库文件,它包含了预编译的代码和数据。当程序运行时,操作系统会将静态库中的代码和数据直接复制到进程的地址空间中,供程序调用。静态库的优点是链接方便,不需要额外的运行时支持库,但缺点是程序的体积会增大,因为静态库中的代码和数据会被完整地复制到目标文件中。
静态库的创建可以使用ar
命令,下面是一个创建静态库的示例:
$ g++ -c add.cpp sub.cpp
$ ar -r libmath.a add.o sub.o
静态库的使用可以在编译命令中加入-l
和-L
选项。其中-l
选项用于指定要链接的库名,-L
选项用于指定库文件的搜索路径,下面是一个使用静态库的示例:
$ g++ main.cpp -lmath -L./
其中-lmath
表示要链接的库名为libmath.a
(注意在链接时的库名是去除lib和.a之后剩余的部分),-L./
表示库文件在当前目录下。
2.2 创建静态库
示例如下:
add_src := src/add.cpp
add_obj := objs/add.o
compile_options := -g -O3 -w
$(add_obj) : $(add_src)
@mkdir -p $(dir $@)
@g++ -c $^ -o $@ $(compile_options)
libadd.a : $(add_obj)
@ar r $@ $^
staticlib : libadd.a
@echo "static_library created successfully!"
debug :
@echo $(compile_options)
clean :
@rm -rf objs libadd.a
.PHONY : debug clean staticlib
具体而言,该Makefile文件的各个部分及其含义如下:
add_src := src/add.cpp
:定义一个变量add_src
,表示源文件路径。add_obj := objs/add.o
:定义一个变量add_obj
,表示目标文件路径。compile_options := -g -O3 -w
:定义一个变量compile_options
,表示编译选项,包括调试信息、优化等级和警告提示。$(add_obj) : $(add_src)
:定义一个规则,表示将源文件编译成目标文件。@mkdir -p $(dir $@)
:如果目录objs
不存在,则创建该目录。@g++ -c $^ -o $@ $(compile_options)
:将源文件编译成目标文件,并指定编译选项。
libadd.a : $(add_obj)
:定义一个规则,表示将目标文件打包成静态库。@ar r $@ $^
:将目标文件打包成静态库。
staticlib : libadd.a
:定义一个规则,表示创建静态库。@echo "static_library created successfully!"
:输出一条提示信息。
debug :
:定义一个规则,表示输出编译选项。@echo $(compile_options)
:输出编译选项。
clean :
:定义一个规则,表示删除目标文件和静态库。@rm -rf objs libadd.a
:删除目标文件和静态库。
.PHONY : debug clean staticlib
:声明规则debug
、clean
和staticlib
为“伪目标”,表示不产生对应的文件,只是作为一个操作名称。
执行make staticlib
运行效果如下:
ar: creating libadd.a
static_library created successfully!
2.3 使用静态库
示例如下:
add_src := src/add.cpp
add_obj := objs/add.o
include_paths := include
include_paths := $(patsubst %,-I%,$(include_paths))
compile_options := -g -O3 -w $(include_paths)
$(add_obj) : $(add_src)
@mkdir -p $(dir $@)
@g++ -c $^ -o $@ $(compile_options)
libadd.a : $(add_obj)
@ar r $@ $^
staticlib : libadd.a
@echo "static_library created successfully!"
workapce/pro : src/main.cpp libadd.a
@mkdir -p $(dir $@)
@g++ $< -o $@ -L./ -ladd $(compile_options)
run : workapce/pro
@./$<
debug :
@echo $(compile_options)
clean :
@rm -rf objs libadd.a workapce/pro
.PHONY : debug clean static_library run
具体而言,workspace/pro
目标依赖于src/main.cpp
和之前生成的静态库libadd.a
,当执行make workspace/pro
命令时,将编译生成可执行文件pro
。-L./ -ladd
指定链接器需要从当前目录下查找静态库文件,并使用静态库文件libadd.a
。run
目标依赖于workspace/pro
,当执行make run
命令时,会执行可执行文件workspace/pro
。
执行make run
运行效果如下:
ar: creating libadd.a
a + b = 15
3.动态库
3.1 定义
动态库是指程序运行时被加载地库文件,它不会被复制到目标文件中。当程序需要调用动态库中的函数时,操作系统会将动态库中的代码和数据加载到进程的地址空间中,供程序调用。相比于静态库,动态库的优点时程序体积较小,多个程序可以共享同一个动态库,但缺点时需要额外的运行时支持库,链接比较麻烦。静态库的扩展名通常为.so
动态库的创建可以使用-shared
选项,下面是一个创建动态库的示例:
$ g++ -c add.cpp sub.cpp
$ g++ -shared -o libmath.so add.o sub.o
动态库的使用可以在编译命令中加入-l
和-L
选项。和静态库不同的是,**动态库需要在运行时才能找到并加载,因此需要使用LD_LIBRARY_PATH
环境变量指定库文件的搜索路径。**下面是一个使用动态库的示例:
$ export LD_LIBRARY_PATH=.
$ g++ main.cpp -lmath -L./ -Wl,-rpath=./
其中-lmath
表示要链接的库名为libmath.so
(注意在链接时的库名是去除lib和.so之后剩余的部分),-L./
表示库文件在当前目录下。export LD_LIBRARY_PATH=.
表示将当前目录添加到库文件的搜索路径中。
-Wl
选项用于将后面的选项传递给链接器,-rpath
选项用于指定运行时动态库的搜索路径。
3.2 创建动态库
示例如下:
add_src := src/add.cpp
add_obj := objs/add.o
compile_options := -g -O3 -w
$(add_obj) : $(add_src)
@mkdir -p $(dir $@)
@g++ -c $^ -o $@ $(compile_options) -fPIC
libadd.so : $(add_obj)
@g++ -shared -fPIC -o $@ $^
sharedlib : libadd.so
@echo "share_library created successfully!"
debug :
@echo $(compile_options)
clean :
@rm -rf objs libadd.so
.PHONY : debug clean sharedlib
具体而言,该Makefile文件的各个部分及其含义如下:
add_src := src/add.cpp
:将要编译成动态库的源文件add_obj := objs/add.o
:将要编译成动态库的对象文件compile_options := -g -O3 -w
:编译选项,包括调试信息、优化等级和不显示警告$(add_obj) : $(add_src)
:生成对象文件的规则,表示将add_src
编译成add_obj
@mkdir -p $(dir $@)
:如果不存在对象文件所在目录,则创建该目录@g++ -c $^ -o $@ $(compile_options) -fPIC
:编译源文件生成对象文件,其中-fPIC
选项表示编译为位置无关代码,是动态库所必需的选项
libadd.so : $(add_obj)
:生成动态库的规则,表示将add_obj
链接为动态库libadd.so
@g++ -shared -fPIC -o $@ $^
:链接对象文件生成动态库,其中-shared
表示生成动态库,-fPIC
选项同上
sharedlib : libadd.so
:生成动态库的目标,表示生成动态库成功debug :
:输出编译选项@echo $(compile_options)
:输出compile_options
的值
clean :
:清理目标文件和动态库@rm -rf objs libadd.so
:删除objs
目录和libadd.so
文件
.PHONY : debug clean sharedlib
:声明debug
、clean
、sharedlib
为伪目标,防止与同名文件或目录冲突
执行make sharedlib
运行效果如下:
share_library created successfully!
3.3 使用动态库
示例如下:
add_src := src/add.cpp
add_obj := objs/add.o
include_paths := include
include_paths := $(patsubst %,-I%,$(include_paths))
compile_options := -g -O3 -w $(include_paths)
$(add_obj) : $(add_src)
@mkdir -p $(dir $@)
@g++ -c $^ -o $@ $(compile_options) -fPIC
libadd.so : $(add_obj)
@g++ -shared -fPIC -o $@ $^
sharedlib : libadd.so
@echo "share_library created successfully!"
workspace/pro : src/main.cpp libadd.so
@mkdir -p $(dir $@)
@g++ $< -o $@ -L./ -Wl,-rpath=./ -ladd $(compile_options)
run : workspace/pro
@export LD_LIBRARY_PATH=./;
@./$<
debug :
@echo $(compile_options)
clean :
@rm -rf objs libadd.so workspace/pro
.PHONY : debug clean sharedlib run
在此代码中,workspace/pro
为生成的可执行文件,依赖于src/main.cpp
和libadd.so
。使用-L
选项指定动态库的搜索路径,使用-l
选项指定动态库的名称。-Wl,-rpath=./
选项指定动态库的搜索路径。另外,在运行时,还需要设置环境变量LD_LIBRARY_PATH
,将动态库路径添加到搜索路径中。
执行make run
运行效果如下:
a + b = 15
4.静态库 vs. 动态库
4.1 静态库
静态库(Static Library)是指在程序编译时被链接进可执行程序的库,与动态库不同的是,静态库在程序编译时就已经被链接,因此程序的运行时不需要进行库文件的加载。静态库的文件格式为.a(Linux)
或.lib(Windows)
。
优点:
- 程序运行时速度快:由于库文件已经被链接到程序中,程序运行时无需进行库文件的加载;
- 程序依赖性低:静态库在程序编译时已经被链接,因此程序的运行时不需要依赖相应的库文件;
- 程序更加安全:由于静态库已经被编译进程序中,因此不容易被黑客攻击。
缺点:
- 可执行程序体积大:由于静态库已经被编译进程序中,因此可执行程序的体积比较大;
- 程序更新麻烦:当静态库更新时,需要编译整个项目文件。
适用场景:静态库通常用于小型程序或嵌入式系统,因为它们可以减少程序的依赖,避免了动态库加载时的开销。
4.2 动态库
动态库(Dynamic Link Library,简称DLL)是一种在程序运行时才会被加载的库,程序需要调用其中的函数或者变量时才会进行加载。动态库的文件格式为.so(Linux)
或.dll(Windows)
,可以被多个程序共享使用,因此可以减小可执行程序的体积。
优点:
- 程序体积小:多个程序可以共享一个库文件,减少了可执行程序的体积(即多个应用程序可以同时加载使用同一个动态库,而对于静态库,每个程序都有一份自己的静态库副本,无法共享)
- 程序更灵活:程序在运行时才进行库的加载,便于程序的动态更新
- 便于维护:在动态更新时秩序替换相应的库文件,不需要重新编译整个程序。(即当你改动动态库文件的内容时,比如你增加了某项功能,只需要重新生成对应的动态库文件,去替换旧的就行,不再需要将整个项目程序编译)
- 节省内存:由于多个程序可以共享一个库文件,因此可以节省内存资源。
缺点:
- 链接时间长:由于库文件在程序运行时才会进行加载,因此会增加程序的启动时间;
- 运行时可能会出现找不到库文件的错误
- 对系统依赖:由于动态库在运行时才会进行加载,因此需要依赖相应的系统库,若系统库版本不兼容可能会出现错误
- 程序安全性较差:由于动态库是公开的的,因此容易被黑客攻击
适用场景:动态库通常用于大型程序中,因为它们可以减少内存占用,允许多个程序共享同一份库代码。此外,动态库还允许在运行时加载插件和扩展,从而增强程序的灵活性和可扩展性。
总结
本次从ChatGPT中学习了动态库和静态库的使用,以及它们之间的区别和优缺点。