CMake学习篇[2]---CMake进阶+非同级目录构建+静态库/动态库链接
文章目录
- 前言
- 1.多层目录
- 1.1 项目结构
- 1.2 CMakeLists
- 1.3 命令说明
- 1.3.1 set
- 1.3.2 include_directories
- 1.3.3 aux_source_directory
- 1.3.4 flie
- 1.4 一些常用宏说明
- 2.创建自定义库与库链接
- 2.1 生成静态库并链接
- 2.1.1 CMakeLists流程梳理
- 2.1.2 关于CMake中的设置于VS中的对应关系
- 2.2 生成动态库并链接
- 2.2.1 CMakeLists流程梳理
- 2.2.2 head.h文件修改
- 2.2.3 关于CMake中的设置于VS中的对应关系
- 2.2.4 一些自己实现时遇到的坑
- 2.3 命令说明
- 2.3.1 add_library
- 2.3.2 link_libraries
- 总结
前言
上一篇博客搭建了一个最最最基础的cmake入门demo,让大家对CMake的构建过程有了一个基本的认识,这篇博客更进一层,愈发贴近实战。
这篇博客主要讲我们的源文件包含多层目录如何找文件,如何生成静态库动态库并连接库的方式。这篇文章有点干巴,我自己写这篇博客之前,琢磨了好一阵子,总算是理顺了,下面且听我娓娓道来。
1.多层目录
1.1 项目结构
head.h
#ifndef HEAD_H
#define HEAD_Hint SumInt(int a,int b);
double SumDouble(double a,double b); #endif
myalgorithm.h
#ifndef MYALGORITHM_H
#define MYALGORITHM_Htemplate<typename T>
T sum(T a,T b)
{return a+b;
} template<typename T>
T sub(T a,T b)
{return a-b;
} #endif
sumDouble.cpp
#include<iostream>
#include"head.h" double SumDouble(double a,double b)
{return a+b;
}
sumInt.cpp
#include<iostream>
#include"head.h" int SumInt(int a,int b)
{return a+b;
}
demo2.cpp
#include<iostream>
#include"myalgorithm.h"
#include"head.h" int main()
{int a=6,b=2;std::cout<<"sum(a,b)="<<sum(a,b)<<std::endl;std::cout<<"sub(a,b)="<<sub(a,b)<<std::endl;std::cout<<"sumInt(a,b)="<< SumInt(a,b)<<std::endl;std::cout<<"sumDouble(a,b)="<< SumInt(a,b)<<std::endl;return 0;
}
上面的代码不用考虑太多,主要还是看CMakeLists的编写。
1.2 CMakeLists
#CMake最小要求的版本
cmake_minimum_required(VERSION 3.11.0)#工程名称
project(Demo2)#增加-std=c++11 指定C++版本为C++11
set(CMAKE_CXX_STANDARD 11)#将几个源文件用一个字符串存储
set(SRC_LIST${CMAKE_CURRENT_SOURCE_DIR}/include/head.h${CMAKE_CURRENT_SOURCE_DIR}/include/myalgorithm.h${CMAKE_CURRENT_SOURCE_DIR}/src/sumDouble.cpp${CMAKE_CURRENT_SOURCE_DIR}/src/sumInt.cpp${CMAKE_CURRENT_SOURCE_DIR}/demo2.cpp)#包含头文件
include_directories(${PROJECT_SOURCE_DIR}/include)#设置输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build_exe_dir)#生成可执行项目
add_executable(demo2exe ${SRC_LIST})
CMakeLists流程梳理
我们且不论具体的命令的用法,先梳理一下大体的流程,并提及一些注意点。
首先设定好最小的CMake版本要求,工程名称,以及C++的标准(其实这里也没用上),再将我们的所需要的源文件全部用一个字符串存储,这样可以在后面复用这些源文件时,不用再全部输入一遍,直接用我们定义的字符串即可。再将头文件的查找路径包含进去,并设置输出路径,最后再生成我们需要的可执行文件的项目。
这里我们的SRC_LIST
中包含了所需要的.h
文件, 在这里包含的意思是指,我最终生成的项目中是否包含指定的.h
文件,而如果不指定头文件查找路径,除了在自身同级目录,环境变量路径找,他是不会去.h
文件实际存在的位置找的,所以还是需要使用include_directories
进行包含
设置输出路径,是指我们运行时,可执行文件输出的位置在哪,一般根据配置不同,会自动生成Debug,Release等子目录,子目录中才会含有可执行文件.exe
在上面的set命令设置SRC_LIST的代码中,其实还有两种方式进行获取,通过aux_source_directory
和flie
命令。
使用代码如下:
#将几个源文件用一个字符串存储
#方式一:通过手动set进行设置
set(SRC_LIST${CMAKE_CURRENT_SOURCE_DIR}/include/head.h${CMAKE_CURRENT_SOURCE_DIR}/include/myalgorithm.h${CMAKE_CURRENT_SOURCE_DIR}/src/sumDouble.cpp${CMAKE_CURRENT_SOURCE_DIR}/src/sumInt.cpp${CMAKE_CURRENT_SOURCE_DIR}/demo2.cpp)
#方式二:通过搜索的方式获取,针对的是指定目录下所有的文件
# aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
# aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SRC_LIST)#方式三:通过搜索的方式获取,针对的是指定目录下,指定文件类型的文件,可加引号也可不加
#file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
#file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
#GLOB_RECURSE递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。
#file(GLOB_RECURSE SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
1.3 命令说明
第一篇中出现的命令这里不做赘述,这里只说明一下新出现的命令
1.3.1 set
在 CMake 中,set() 命令是最基础且常用的命令之一,主要用于设置变量的值,包括普通变量、缓存变量、环境变量等。它的功能非常灵活,是构建 CMake 脚本逻辑的核心工具之一。
set() 命令的基本格式如下:
set(<变量名> <值>)
其中:
<变量名>
:自定义的变量名称(通常使用大写字母,如 MY_VAR,遵循 CMake 约定)。
<值>
:变量的取值,可以是字符串、路径、列表(多个值用空格分隔)等。
下面是一个常用法
#单值变量
set(SOURCE_FILES main.cpp) # 变量 SOURCE_FILES 被赋值为 "main.cpp"#列表变量(多个值用空格分隔,本质是字符串,CMake 会按空格解析为列表)
set(SOURCE_FILES main.cpp utils.cpp) # 列表形式,包含两个文件
#后续可通过 ${SOURCE_FILES} 引用变量值,例如
add_executable(myapp ${SOURCE_FILES}) # 等价于 add_executable(myapp main.cpp utils.cpp)
其他的也不多说了,只要记住,set第一个参数取名字,第二个参数给值,支持多个参数给到一个名字上
1.3.2 include_directories
在 CMake 中,include_directories() 是用于指定头文件搜索路径的命令,它告诉编译器在编译源代码时到哪些目录中查找 .h(或 .hpp 等)头文件。
基本用法
include_directories([AFTER|BEFORE] [SYSTEM] <目录路径1> <目录路径2> ...)
<目录路径>
:必填参数,指定一个或多个头文件所在的目录(可以是相对路径或绝对路径)。
可选参数:
AFTER/BEFORE
:控制新路径在搜索路径列表中的位置(AFTER 追加到现有列表后,BEFORE 插入到现有列表前,默认是 AFTER)。
SYSTEM
:将目录标记为 “系统目录”,编译器会对该目录下的头文件减少警告(例如忽略一些语法检查警告)。
当源代码中使用 #include “xxx.h” 或 #include <xxx.h> 时,编译器会默认在系统标准路径(如 /usr/include、/usr/local/include 等)中查找头文件。如果头文件不在这些路径中(例如项目自己的 include 目录),就需要通过 include_directories() 告诉编译器额外的搜索路径,否则会出现 “头文件找不到” 的编译错误。
与 target_include_directories 的区别
CMake 3.0+ 推荐使用更现代的 target_include_directories() 替代 include_directories(),两者的核心区别在于:
include_directories() 是全局生效
的,会将路径添加到当前 CMakeLists.txt 及所有子目录的目标(target,如可执行文件、库)的头文件搜索路径中。
target_include_directories() 是针对特定目标
生效的,仅为指定的目标(如 myapp 或 mylib)添加头文件路径,更符合 “目标导向” 的现代 CMake 理念。
所以上面示例中的include_directories也可以用target_include_directories替代,只不过后者要指定项目,我这里由于是个demo所以用include_xxx更加省事。
为了更好的理解Windows在VS2017上开发CMakeLists命令和具体项目的配置关系,这里再贴个图说明一下include_directories的作用。
这里如果是用include,那就是解决方案下所有
lib,exe,dll
项目的包含目录中均含有你指定的路径,如果是实用工具,那可没有这个包含选项。
如果是target_include_xxx,那就是你指定的项目的包含目录,附加上你设定的路径
1.3.3 aux_source_directory
1.3.4 flie
1.4 一些常用宏说明
宏名称 | 含义 | 备注 |
---|---|---|
CMAKE_CXX_STANDARD | C++标准 | 11,14,17等等 |
CMAKE_CURRENT_SOURCE_DIR | 当前处理的CMakeLists.txt所在的路径 | |
CMAKE_CURRENT_BINARY_DIR | target 编译目录 | |
PROJECT_SOURCE_DIR | 使用cmake命令后紧跟的目录,一般是工程的根目录 | |
PROJECT_BINARY_DIR | 执行cmake命令的目录 | |
EXECUTABLE_OUTPUT_PATH | 重新定义目标二进制可执行文件的存放位置 | |
LIBRARY_OUTPUT_PATH | 重新定义目标链接库文件的存放位置 | |
PROJECT_NAME | 返回通过PROJECT指令定义的项目名称 | |
CMAKE_BINARY_DIR | 项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径 |
2.创建自定义库与库链接
2.1 生成静态库并链接
先看一段CMakeLists的代码
#CMake最小要求的版本
cmake_minimum_required(VERSION 3.11.0)#工程名称
project(Demo3)#增加-std=c++11 指定C++版本为C++11
set(CMAKE_CXX_STANDARD 11)set(SRC_LIST${CMAKE_CURRENT_SOURCE_DIR}/src/sumDouble.cpp${CMAKE_CURRENT_SOURCE_DIR}/src/sumInt.cpp)set(HEAD_LIST${CMAKE_CURRENT_SOURCE_DIR}/include/head.h${CMAKE_CURRENT_SOURCE_DIR}/include/myalgorithm.h)#包含头文件
include_directories(${PROJECT_SOURCE_DIR}/include)#设置输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build_exe_dir)
#设置库的输出存放路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)#制作静态库
add_library(demo3static STATIC ${SRC_LIST} ${HEAD_LIST})#链接静态库,先指定静态库的位置,以防查找不到
link_directories(${PROJECT_SOURCE_DIR}/lib)
link_libraries(demo3static)add_executable(demo3exe demo3.cpp)
2.1.1 CMakeLists流程梳理
相比于上一节,我们对于SRC_LIST
进行了优化,将SRC_LIST
中的.cpp
和.h
文件进行了分隔,分别放到SRC_LIST
和HEAD_LIST
。
这里我们先设置好库的输出存放路径,接着使用add_library命令制作一个静态,同时由于我们手动设置了库的存放路径,所以这里需要再手动link一下库的目录,以防找不到库的情况,最后再链接库即可。
设置库输出路径,通过set命令进行设置,使用到了上面介绍的宏LIBRARY_OUTPUT_PATH
,这里也会根据不同配置Debug/Release等自动生成子文件夹
这里制作静态库的命令add_library
,平常使用就三个参数,自定义库名
,静态动态(STATIC/SHARED)
,源文件
。
倒数第二步链接库,这里有点学问,我们只需要指定库名即可,即使这个文件可能是demo3static.lib
,后缀.lib
我们不用管,只需要指定库名即可。
在Linux
中,静态库名字分为三部分:lib
+库名字
+.a
,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。
最后效果大体如下:
2.1.2 关于CMake中的设置于VS中的对应关系
- 关于添加连接库,实际上相当于就是VS里面,链接器中的附加依赖项
- 我们创建自定义库的时候,库的名称以及输出位置的设定,相当于VS中下面的配置
- 而我们设置的输出目录以及add_executeable命令里面指定的exe的名称,其实相当于VS这里的配置
2.2 生成动态库并链接
先看一段CMakeLists的代码
#CMake最小要求的版本
cmake_minimum_required(VERSION 3.11.0)#工程名称
project(Demo4)#增加-std=c++11 指定C++版本为C++11
set(CMAKE_CXX_STANDARD 11)set(SRC_LIST${CMAKE_CURRENT_SOURCE_DIR}/src/sumDouble.cpp${CMAKE_CURRENT_SOURCE_DIR}/src/sumInt.cpp)set(HEAD_LIST${CMAKE_CURRENT_SOURCE_DIR}/include/head.h${CMAKE_CURRENT_SOURCE_DIR}/include/myalgorithm.h)#包含头文件
include_directories(${PROJECT_SOURCE_DIR}/include)#设置输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build_exe_dir)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build_exe_dir)#制作动态库
add_library(demo4shared SHARED ${SRC_LIST} ${HEAD_LIST} )
link_directories(LIBRARY_OUTPUT_PATH)
#如果不指定名称会自动生成"项目名_EXPORTS"的宏
target_compile_definitions(demo4shared PUBLIC MY_DEMO4SHARED_EXPORTS)add_executable(demo4exe demo4.cpp)#链接动态库
target_link_libraries(demo4exe demo4shared)
2.2.1 CMakeLists流程梳理
这里和静态库的很像,基本保持一致,部分行有些不同
- 23行中让库的输出目录和生成的可执行文件在同一目录下,这里先不说为什么后面再讲。
- 26行
add_library
中,由静态库的STATIC
改为了SHARED
- 29行多了个编译定义命令
target_complie_definitions
,这条命令的意思是给demo4shared这个动态库定义了一个自定义宏,MY_DEMO4SHARED_EXPORTS
,具体用法我们看代码
动态库和静态库其实还是有很大区别的,我不是指它们和内存的关系,我是指在使用上。
在Windows端,需要使用__declspec(dllexport)
和__declspec(dllimport)
来说明是导出还是导入动态库dll,既然是生成动态库,所以我们需要对head.h做一个修改。
2.2.2 head.h文件修改
#ifndef HEAD_H
#define HEAD_H// 导出修饰符,用于动态库项目
#ifdef MY_DEMO4SHARED_EXPORTS
#define DEMO4SHARED_API __declspec(dllexport)
#else
#define DEMO4SHARED_API __declspec(dllimport)
#endif
DEMO4SHARED_API int SumInt(int a,int b);
DEMO4SHARED_API double SumDouble(double a,double b);#endif
这里我们多了一个宏的判断操作,用来判断head.h文件中的函数是否需要导出到库。这里的宏MY_DEMO4SHARED_EXPORTS
,必须得和cmake中的target_complie_definitions
定义的宏一致,因为我们用的就是cmake的宏是否存在来判定导出还是导入到动态库。
改完head.h后,构建编译通过,应该就可以正常运行。
2.2.3 关于CMake中的设置于VS中的对应关系
- 我们添加的宏,实际上就是动态库的预处理器定义
2.2.4 一些自己实现时遇到的坑
1. 生成的可执行文件必须和应用的外部dll库放在一起吗?不能通过link_directories找到后直接调用吗?能否把动态库的位置和可执行文件的位置分开?
生成的可执行文件不必须和外部 DLL 放在一起,但link_directories只能解决编译链接阶段的 “找库” 问题,无法解决运行阶段的 “找 DLL” 问题 —— 这两个阶段的 “找库逻辑” 完全不同,是 Windows 系统的底层机制决定的,需要明确区分。
一、先理清两个核心阶段的区别
Windows 程序的构建和运行分为两个独立阶段,“找库” 的逻辑和路径完全不同:
二、为什么link_directories无法解决运行阶段找 DLL 的问题?
link_directories
的作用是告诉编译器 / 链接器
:在编译链接时,去哪里找 “导入库(.lib)
”(导入库是链接阶段的 “桥梁”,仅包含 DLL 的符号信息,不包含实际代码)。
但运行阶段时,操作系统(Windows)加载 DLL 的逻辑是独立于 CMake 的link_directories
配置的 ——Windows 会按照固定的优先级顺序搜索 DLL,而link_directories
指定的路径不在这个搜索列表里,所以即使link_directories
能找到导入库,运行时仍可能找不到 DLL。
三、Windows 运行时搜索 DLL 的优先级顺序(关键)
可执行文件运行时,Windows 会按以下顺序查找需要的 DLL(优先级从高到低):
可执行文件所在目录(这就是 “把 DLL 和 exe 放一起能运行” 的原因);
当前工作目录(即运行 exe 时的命令行所在目录,不是 exe 的目录);
系统目录(C:\Windows\System32,32 位 DLL;C:\Windows\SysWOW64,64 位 DLL);
Windows 目录(C:\Windows);
环境变量PATH中指定的目录(这是 “不把 DLL 和 exe 放一起” 的核心方案)。
四、不把 DLL 和 exe 放一起的 3 种可行方案
如果不想将 DLL 和可执行文件放在同一目录,可以通过以下方式让 Windows 在运行时找到 DLL:
方案 1
:将 DLL 路径添加到系统环境变量PATH
这是最通用的方案,适合多个程序共用同一个 DLL 的场景:
找到你的 DLL 所在目录(例如${PROJECT_SOURCE_DIR}/lib/Debug);
右键 “此电脑”→“属性”→“高级系统设置”→“环境变量”;
在 “系统变量” 中找到PATH,点击 “编辑”→“新建”,粘贴 DLL 目录路径;
重启命令行 / IDE(环境变量修改后需要重启生效),再运行 exe。
注意:如果是 Debug 和 Release 模式的 DLL,需要分别将lib/Debug和lib/Release添加到PATH,或根据当前使用的模式切换PATH。
方案 2
:通过 CMake 自动将 DLL 复制到PATH目录(适合开发阶段)
如果不想手动修改环境变量,可以在 CMake 中添加逻辑,将生成的 DLL 自动复制到系统PATH包含的目录(例如C:\Windows\System32,但不推荐修改系统目录,更安全的是复制到用户目录下的bin目录并添加到PATH):
# 示例:将DLL复制到用户目录的 bin 目录(需先将该目录添加到 PATH)
set(USER_BIN_DIR $ENV{USERPROFILE}/bin)
file(MAKE_DIRECTORY ${USER_BIN_DIR}) # 确保目录存在# 编译动态库后,自动复制DLL到 USER_BIN_DIR
add_custom_command(TARGET demo2shared POST_BUILDCOMMAND ${CMAKE_COMMAND} -E copy$<TARGET_FILE:demo2shared> # 动态库文件路径(自动包含 Debug/Release)${USER_BIN_DIR}/$<TARGET_FILE_NAME:demo2shared>COMMENT "Copy DLL to user bin directory: ${USER_BIN_DIR}"
)
方案 3
:在代码中指定 DLL 加载路径(进阶,适合特定场景)
如果是 C/C++ 代码,可以通过LoadLibraryA函数手动指定 DLL 的完整路径加载,绕过 Windows 的默认搜索规则(适合需要灵活控制 DLL 路径的场景):
#include <windows.h>
#include <iostream>// 定义DLL中的函数指针类型
typedef int (*SumIntFunc)(int, int);int main() {// 手动指定DLL的完整路径(例如放在 D:\my_dlls 目录)HMODULE hDll = LoadLibraryA("D:\\my_dlls\\demo2shared.dll");if (hDll == NULL) {std::cout << "找不到DLL!错误码:" << GetLastError() << std::endl;return 1;}// 获取DLL中的函数地址SumIntFunc sumInt = (SumIntFunc)GetProcAddress(hDll, "SumInt");if (sumInt == NULL) {std::cout << "找不到函数!错误码:" << GetLastError() << std::endl;FreeLibrary(hDll);return 1;}// 调用DLL中的函数std::cout << sumInt(3, 5) << std::endl;// 释放DLLFreeLibrary(hDll);return 0;
}
注意:这种方式需要用 “显式加载”(LoadLibrary)替代 “隐式加载”(编译时链接导入库),适合对 DLL 路径有特殊要求的场景(如插件化架构)。
而我在cmake中将他们放到一起的方式,其实是避免了找不到的情况,当然了,我们还可以用自定义命令来将生成在别的地方的lib库拷贝一份到exe文件处,这样也不会出现找不到问题。
add_custom_command(TARGET demo4exe POST_BUILDCOMMAND ${CMAKE_COMMAND} -E copy_directory${LIBRARY_OUTPUT_PATH}/$<IF:$<CONFIG:Debug>,Debug,Release> $<TARGET_FILE_DIR:demo4exe>COMMENT "Copying dynamic library to executable directory"VERBATIM)
2. 为什么只改head.h,而myalgorithm.h不用修改,它们不都是头文件吗?为什么后者不需要dllexport?
myalgorithm.h 文件是纯模板函数(sum、sub)的实现,这类文件不需要添加 MYDLL_EXPORTS
相关的导出 / 导入逻辑,原因如下:
-
模板函数的特殊性:必须在头文件中实现
C++ 模板的实现机制决定了模板函数 / 类的定义(实现)必须放在头文件中(或通过#include
包含实现文件),因为编译器需要在编译时根据具体的模板参数(如 int、double)实例化出具体的函数代码。
这意味着:
模板函数不会像普通函数那样编译成 DLL 中的二进制代码(因为编译时还不知道具体实例化类型)。
其他项目使用你的模板函数时,需要直接包含头文件,由该项目的编译器根据实际使用的类型(如 sum(1,2))实例化代码,而不是从 DLL 中 “导入” 已编译的符号。 -
为什么不需要 dllexport/dllimport?
__declspec(dllexport)
和__declspec(dllimport)
的作用是控制 DLL 中二进制符号的导出和导入,但模板函数:
没有 “预编译的二进制符号”(因为依赖具体实例化类型),无法被 DLL 导出。
其他项目使用时也不需要 “导入”,而是直接通过头文件实例化,因此无需 dllimport。
如果强行在模板函数中添加 MYDLL_API(即 dllexport/dllimport),反而会导致编译错误或无意义的冗余代码。
2.3 命令说明
2.3.1 add_library
在 CMake 中,add_library 是用于创建库文件(静态库、动态库或模块库)的核心命令。它的主要作用是将指定的源文件编译为库,供其他目标(如可执行文件或其他库)链接使用。
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] [source1] [source2 ...])
关键参数说明
<name>
库的名称,CMake 会根据平台自动生成对应的库文件名(例如:在 Linux 上静态库为 lib<name>.a
,动态库为 lib<name>.so
;在 Windows 上静态库为 <name>.lib
,动态库为 <name>.dll
)。
库类型(可选,默认根据政策判断)
STATIC
:生成静态库(.a/.lib),编译时会被直接链接到目标文件中。
SHARED
:生成动态库(.so/.dll),运行时动态加载,可被多个程序共享。
MODULE
:生成模块库(通常用于动态加载插件),不会被链接到其他目标,而是通过 dlopen 等机制加载。
如果不指定类型,CMake 会根据 BUILD_SHARED_LIBS 变量判断:若该变量为 ON,默认生成 SHARED 库,否则生成 STATIC 库。
EXCLUDE_FROM_ALL
(可选)表示该库不会被默认构建(即执行 make 或 cmake --build 时不会自动编译),需显式指定目标(如 make <name>
)才会构建。
源文件列表指定用于编译库的源文件(.c/.cpp 等),也可后续通过 target_sources 补充添加
注意
库名称需唯一,不能与其他目标(如可执行文件)重名。
动态库在不同平台可能需要特殊处理(如 Windows 需导出符号,可通过 __declspec(dllexport) 实现)。
若库被多个目标依赖,CMake 会自动处理链接关系,无需重复编译
2.3.2 link_libraries
在 CMake 中,link_libraries 是用于指定全局链接依赖的命令,它会为后续定义的所有目标(如可执行文件 add_executable 或库 add_library)添加链接依赖项。简单来说,就是 “提前声明” 哪些库需要被之后的目标默认链接。
link_libraries([item1] [item2 ...])
其中 item 可以是:
已通过 add_library 定义的库目标名称(推荐);
系统库名称(如 pthread、m);
库文件的路径(不推荐,可移植性差)。
核心作用
link_libraries 是 “全局生效”的命令:它会为在它之后出现的所有目标 (add_executable 或 add_library)自动添加链接依赖。例如:
# 声明后续目标都需要链接 math_lib 和 pthread
link_libraries(math_lib pthread)# 生成可执行文件 app,会自动链接 math_lib 和 pthread
add_executable(app main.cpp)# 生成库 utils,也会自动链接 math_lib 和 pthread
add_library(utils STATIC utils.cpp)
与 target_link_libraries 的区别
link_libraries 和 target_link_libraries 都用于链接库,但核心区别在于作用范围:
link_libraries:全局生效,影响之后所有目标(“批量设置”);
target_link_libraries:只针对指定目标生效(“精准设置”,推荐使用)。
常用场景与注意事项
-
不推荐优先使
由于 link_libraries 是全局生效,项目复杂时容易导致 “不必要的链接”(比如某个目标其实不需要该库,但被强制链接),增加构建冗余或冲突风险。因此,更推荐使用 target_link_libraries 为具体目标精准链接依赖。 -
支持链接选项可以添加链接选项(如 -lpthread 或 PUBLIC/PRIVATE 等关键字,与 target_link_libraries 类似):
# 后续目标链接 math_lib 时,将其依赖传递给依赖该目标的其他目标(PUBLIC 语义)
link_libraries(PUBLIC math_lib)
- 多次调用的叠加效果多次调用 link_libraries 会累积依赖项。例如:
link_libraries(lib1)
link_libraries(lib2)
# 后续目标会同时链接 lib1 和 lib2
- 谨慎使用系统库路径尽量避免直接写库文件路径(如 link_libraries(/usr/lib/libxxx.so)),可移植性差。优先使用库目标名称或系统库名(如 pthread)。
link_libraries 适合简单项目中批量设置链接依赖,但在复杂项目中,更推荐用 target_link_libraries 为每个目标单独指定依赖,以提高清晰度和可维护性。实际开发中,target_link_libraries 是更主流的选择。
总结
这篇博客花了一天时间去验证实现+编写,和前年些W25Q128芯片精读一样,一杯茶配上键鼠,电脑前坐一天。刚好台风桦加沙来了,今天也停工,那就沉淀沉淀吧。
其他知识相关的内容,我只能说,无需多言,尽在文字中。相关代码和文件我同步上传至gitee,需要自取
https://gitee.com/Edwinwzy1/cmake-learn