gcc
编译四步骤:
- 预处理(展开宏定义,头文件,替换条件编译,删除注释,空行空白)
- 编译(检查语法规范,得到汇编代码)
- 汇编(将汇编指令翻译成机器指令)
- 链接(数据段合并,地址回填)
- 注意
-o
的作用就是起名字,而不是链接
- 其中编译阶段消耗时间和系统资源最多
g++
使用方式一样:
$ g++ -E hello1.cpp -o hello1.i
$ g++ -S hello1.i -o hello1.s
$ g++ -c hello1.s -o hello1.o
$ g++ hello1.o -o hello1
-I 目录
用于指定头文件,头文件和源代码不再同一个目录下就需要指定头文件
- 制定了头文件的位置,或者指定了位置
# 指定头文件所在的目录
$ gcc hello.c -I ../inc -o hello
-c
预处理编译和汇编,相当于进行了后面的步骤,前面的步骤执行了,所以最后使用的编译命令:
# 这里没有参数,已经完成了链接和之前的操作
$ gcc hello.c -o hello
-g
编译时添加调试语句(可以调试)
-On n=0~3
编译优化,n
的值越大优化越多(嵌入式编程中需要使用放置优化)
-Wall
显示所有的警告信息
$ gcc hello.c -o hello -Wall
-D
表示向当前程序中注册一个宏,一般可以配合 #ifdef
使用,注意#ifndef
表示没有定义
gcc hello.c -o hello -D HELLO
#include "../inc/hello.h"
#ifdef HELLO
#define HI 20
#endif
void say_hello()
{
printf("hello world");
}
int main()
{
say_hello();
printf("%d\n",HI);
return 0;
}
- 静态库: 任何一个程序需要使用必须编译这一个静态库,静态库被编译到程序中,相当于库就相当于自己的函数
- 动态库: 只需要链接就可以了,不用编译到程序中
- 静态库优点:
- 动态库优点:
- 静态库: 对于空间要求比较低,时间要求比较高,比如操作系统的启动文件中
- 动态库: 对于时间要求比较低,对于空间要求比较高
# 获取 .o
$ gcc -c hello.c -o hello.o
$ ar rcs libmylib.a file1.o
linux
中静态库的后缀: .a
,windows
中后缀: .lib
$ gcc -c add.c -o add.o
$ gcc -c sub.c -o sub.o
$ ar rsc mymathlib.a add.o sub.o
- 使用静态库
- 编译阶段出错会有行号
ld
表示连接器,所以如果有 ld
就是链接出错
# 直接把静态库编译进去就可以了
$ gcc test.c mymathlib.a -o test
- 加上
-Wall
会有如下警告信息:
- 意思就是隐式声明,原因就是当函数没有定义并且没有声明的时候,编译器就会隐式声明
- 但是隐式声明必须需要推导出返回值和参数类型
test.c:6:28: warning: implicit declaration of function ‘add’ [-Wimplicit-function-declaration]
6 | printf("a + b = %d\n", add(a , b));
#ifndef _MYMATH_H_
#define _MYMATH_H_
int add(int a , int b);
int sub(int a , int b);
#endif
- 总结一下问题:
- 如果没有在头文件中定义需要使用的函数,那么就会导致使用的时候如果使用
-Wall
参数就会报错implicit declaration of ...
- 所以在制作静态库的时候需要提供一个头文件用于声明好需要使用的函数
- 编译时利用
-I
参数连接好头文件就可以了,当然需要在代码中 include
头文件
- 常用的
C
项目结构如下:
inc
: 存放头文件
lib
: 存放库文件
src
: 存放源代码
$ gcc test.c ../lib/mymathlib.a -o ../output/test -I ../inc
- 只有当调用到动态库中的函数的时候,才会加载这一个文件
- 地址回填: 汇编阶段之后,生成了
Xxx.o
二进制代码,但是这一个代码中 main
函数的地址还没有确定(运行时才会确定),但是main
函数中自己定义的函数的地址和main
发生了绑定关系,比如main
函数中定义了 func
那么汇编阶段之后,func
的地址虽然没有确定,但是与 main
函数地址之间的关系确定了,当运行阶段main
地址分配了,就叫做地址回填,此时 func
的地址才可以确定
- 只有在运行的时候,动态库的函数才可以被
main
调用,所以动态库的函数晚绑定,所以需要生成地址固定的代码才可以制作动态库
- 制作过程:
- 利用
-c
制作 .o
文件,制作位置无关的代码,这是由于调用的过程中需要让地址固定(-fPIC
参数可以做到)
- 使用
gcc -shared 制作动态库
# 生成代码与位置无关的 .o 文件
gcc -c add.c -o add.o -fPIC
# 利用生成的代码制作动态库
gcc -shared -o lib库名.so add.o sub.o div.o
# 使用动态库.编译可执行程序的时候,指定使用的动态库 -l 指定库名 -L: 指定库的路径
gcc test.c -o test -l mymath -L ./lib
# 编译指令
$ gcc dtest.c -o ../output/dtest -l mymath -L ../lib -I ../inc
- 注意上面会报错,找不到文件:
- 原因:
- 链接器: 工作处于链接阶段,工作时需要
-l
和-L
参数(完成数据段合并和地址回填)
- 动态链接器: 工作处于程序运行阶段,工作时需要提供动态库所在的目录位置,会到特定的位置去找动态库(
LD_LIBRARY_PATH=./lib
)
- 注意动态库的名称一定需要需要使用 lib库名.so 的名称,链接时需要使用 库名(去掉 lib 和 so)
- 但是直接改变
LD_LIBRARY_PATH
的方法不可取,最好把动态库安装到 /usr/lib
目录下便于寻找
- 为什么我的可以运行?
- 可以使用
ldd
查看加载动态库的路径
- 解决方式:
- 改变
LD_LIBRARY_PATH
的值
- 把自己的动态库放在
/lib
下,这是由于gcc
编译器会自动在 /lib
下寻找动态库(利用 ldd
可以查看)
- 配置文件法:
- 编辑配置文件
/etc/ld.so.conf
,include
自己的动态库路径即可
- 之后让这一个配置文件生效
ldconfig /etc/ld.so.conf
即可
- 注意动态库和静态库都需要指定头文件
- 数据段合并的原理(之后学习了操作系统的内存管理在看):
- 其实底层就是链接时期,完成
.bss
和 .data
的合并和.rodata
和 .text
的合并
- 总结:
# 首先生成 .o 文件
$ gcc -c add.c -o add.o
$ gcc -c sub.c -o sub.o
# 利用 .o 文件生成静态库
$ ar rsc 静态库名称.a add.o sub.o
# 使用静态库
$ gcc test.c 静态库名.a -I 头文件路径 -o a.out
# 首先生成 .o 文件(地址和代码位置无关)
$ gcc -c add.c -o add.o -fPIC
$ gcc -c sub.c -o sub.o -fPIC
# 利用 .o 文件生成静态库
$ gcc -shared -o lib动态库名.so add.o sub.o
# 使用静态库
$ gcc test.c -o a.out -l 动态库名 -I 头文件路径 -L 动态库路径
- 解决动态链接器找不到动态库的问题:
- 改变(
LD_LIBRART_PATH
)
- 把
.so
放在 /lib
下
- 配置
/etc/ld.so.conf
,并且利用 ldconfig
时的这一个文件生效
- 调试作用
- 注意需要加上
-g
用于生成调试信息,从而可以使用 gdb
进行检测
- 使用方式:
gdb 可执行程序名称
进入调试状态
list
或者l
显示源码
list n
或者 l n
表示从第 n
行开始显示源码
b n
表示在第n
行设置断点
run
或者 r
表示执行程序,一般用于执行到断点的位置
n
表示 next
越过这一行直接进入下一行(在调试的时候使用)
s
表示进入这一行(step
)
p value
可以用于查看变量的值,这就是表示查看 value
的值
continue
表示继续执行下面的函数,相当于放行
quit
退出
- 如果出现了段错误,那么直接使用
run
命令就可以,直接使用 run
命令,停止的位置就是段错误的位置
start
命令表示从当前行开始执行(相当于当前行打了一个断点),之后可以使用 s
或者 n
进行下一步
finish
表示退出当前函数,回到进入函数的位置(结束当前函数调用,返回进入点)
- 注意设置断点之后一定需要 `run
set args
表示设置程序运行的参数,set args "aa" "bb" "cc" "dd"
info b
表示设置断点信息
b 20 if i = 5
表示只有 i = 5
的时候才可以设置断点,相当于循环中间才会让断点生效
ptype 变量
表示查看变量的类型
- 栈帧: 表示随着函数的调用而在
stack
开辟的一片内存空间,用于存放函数调用时产生的局部变量和临时值,当函数执行完毕的时候,栈帧就会消失,并且栈帧中存储着函数执行过程中的局部变量
backtrace(bt)
查看函数的调用的栈帧和层级关系
frame(f)
切换函数的栈帧,作用就是可以在执行一个函数的同时切换到另外一个函数的栈帧的位置查看另外一个函数中的局部变量,但是前提就是这些函数没有执行完毕(注意使用 run
执行到断点的位置)
display
设置跟踪变量,对于一个变量使用了 display
之后,这一个变量就会被纳入到跟踪列表,之后使用 display
就可以跟踪变量
undisplay 号码
就可以取消对应号码的跟踪,下一次使用 display
就不会展示这一个变量了
delete
删除断点
info locals
查看局部变量
- 常见错误:
- 相当于脚本,相当于一组命令的集合
Makefile
中的注意点:
- 一个规则
- 两个函数
- 三个自动变量
- 一个规则:
- 目标: 依赖条件 # 表示格式
(一个
tab
缩进)命令
- 命名方式(
makefile
或者 Makefile
)
- 一个最简单的
makefile
如下:
hello:hello.c # 目标:依赖
gcc hello.c -o hello # 生成目标的命令
# hello:hello.c
# gcc hello.c -o hello
hello:hello.o
gcc hello.o -o hello
hello.o:hello.c
gcc -c hello.c -o hello.o
- 规则: 如果想要生成目标(比如
hello.o
),检查规则中的依赖条件是否存在,如果不存在,就可以寻找是否存在规则用来生成该依赖文件(可以使用 hello.c
生成规则)
- 考虑如下的
makefile
文件:
test: add.c sub.c div1.c mul.c test.c
gcc test.c add.c sub.c div1.c mul.c -o test
- 如果这样写的话,如果其中的某一个文件发生了改变,如果还是利用这一个指令,那么没有改变的文件救护重复编译,造成时间的浪费
- 所以可以提前生成
.o
文件
- 改写之后的
makefile
如下:
# test: add.c sub.c div1.c mul.c test.c
# gcc test.c add.c sub.c div1.c mul.c -o test
test:add.o sub.o div1.o mul.o test.o
gcc test.o add.o sub.o div1.o mul.o -o test
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc -c sub.c -o sub.o
div1.o:div1.c
gcc -c div1.c -o div1.o
mul.o:mul.c
gcc -c mul.c -o mul.o
test.o:test.c
gcc -c test.c -o test.o
- 此时指挥编译被修改过的文件
- 规则2: 检测规则中的目标是否需要更新,必须首先检测他的所有依赖,依赖中有任何一个被更新,则目标都会被更新
- 总结规则:
- 目标的时间必须晚于依赖条件的时间,否则就会更新目录
- 依赖条件如果不存在,找寻新的规则去产生依赖
- 注意
makefile
会默认把第一个遇到的目标作为终极目标,如果这一个目标的依赖关系已经满足,那么就不会继续执行下面的目标了,所以生成可执行文件需要写在前面
- 但是可以指定
ALL 目标文件
就可以解决这一个文件
# test: add.c sub.c div1.c mul.c test.c
# gcc test.c add.c sub.c div1.c mul.c -o test
# 表示指定终极目标
ALL: test
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc -c sub.c -o sub.o
div1.o:div1.c
gcc -c div1.c -o div1.o
mul.o:mul.c
gcc -c mul.c -o mul.o
test.o:test.c
gcc -c test.c -o test.o
test:add.o sub.o div1.o mul.o test.o
gcc test.o add.o sub.o div1.o mul.o -o test
src = $(wildcard *.c)
用于找到当前目录下的所有的 .c
文件,赋值给 src
obj = $(patsubst %.c , %.o , $(src))
把src
变量里面所有后缀为 .c
的文件替换成 .o
- 作用: 将包含参数
1
的部分,替换成参数 2
- 同时可以使用
clean
删除所有的 .o
文件和原来生成的可执行文件
- 首先使用
make clean -n
查看清除语句
- 之后利用
make clean
删除语句
src = $(wildcard *.c) # src = add.c sub.c div1.c mul.c test.c
obj = $(patsubst %.c , %.o , $(src)) # obj = add.o sub.o div1.o mul.o test.o
test:$(obj)
gcc $(obj) -o test
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc -c sub.c -o sub.o
div1.o:div1.c
gcc -c div1.c -o div1.o
mul.o:mul.c
gcc -c mul.c -o mul.o
test.o:test.c
gcc -c test.c -o test.o
clean:
-rm -rf $(obj) test
- 注意
clean
没有依赖,并且 -rm
中 -
的作用就是删除不存在的文件的时候不会报错
$@
表示规则中的目标,在规则的命令中表示目标,只可以出现在命令的位置
$<
表示规则中的第一个条件,在规则的命令中表示第一个依赖条件如果引用到模式规则中,他可以把依赖条件列表中的依赖一次取出,套用于模式规则(相当于 一次循环)
$^
表示规则中的所有条件,表示所有依赖条件
src = $(wildcard *.c) # src = add.c sub.c div1.c mul.c test.c
obj = $(patsubst %.c , %.o , $(src)) # obj = add.o sub.o div1.o mul.o test.o
test:$(obj)
gcc $^ -o $@
add.o:add.c
gcc -c $< -o $@
sub.o:sub.c
gcc -c $< -o $@
div1.o:div1.c
gcc -c $< -o $@
mul.o:mul.c
gcc -c $< -o $@
test.o:test.c
gcc -c $< -o $@
clean:
-rm -rf $(obj) test
- 好处就是当程序中多了一个模块的时候,不用修改
makefile
就可以了利用 make
编译得到可执行程序
- 模式规则: 可以使用模式匹配模拟规则:
%.o:%.c
命令也是通用的
- 模式规则如下:
src = $(wildcard *.c) # src = add.c sub.c div1.c mul.c test.c
obj = $(patsubst %.c , %.o , $(src)) # obj = add.o sub.o div1.o mul.o test.o
test:$(obj)
gcc $^ -o $@
%.o:%.c
gcc -c $< -o $@
clean:
-rm -rf $(obj) test
- 静态模式规则:
$(obj):%.o:%.c gcc -c $< -o $@
- 表示对于某一个条件套用某一个模式规则,当模式规则不只有一个的时候,那么就会导致不知道需要找到那一个依赖才可以满足依赖关系
src = $(wildcard *.c) # src = add.c sub.c div1.c mul.c test.c
obj = $(patsubst %.c , %.o , $(src)) # obj = add.o sub.o div1.o mul.o test.o
test:$(obj)
gcc $^ -o $@
$(obj):%.o:%.c
gcc -c $< -o $@
%.s:%.c
gcc -S $< -o $@
clean:
-rm -rf $(obj) test
- 如果定义一个
clean
文件,使用 make clean
的时候,就会被错误解析成需要制作一个 clean
文件
- 所以此时需要使用伪目标(不论是否符合条件都需要生成目标)
- 就是无论符合条件都会执行(
PHONY
)
src = $(wildcard *.c) # src = add.c sub.c div1.c mul.c test.c
obj = $(patsubst %.c , %.o , $(src)) # obj = add.o sub.o div1.o mul.o test.o
test:$(obj)
gcc $^ -o $@
$(obj):%.o:%.c
gcc -c $< -o $@
%.s:%.c
gcc -S $< -o $@
clean:
-rm -rf $(obj) test
.PHONY: clean ALL
- 同时也可以链接静态库和动态库(
-l
-L
-Wall
)
- 注意
makefile
中 *
的作用就是通配符号,但是 %
表示占位符号,用于指定特殊为位置的字符,一定需要注意二者的区别,在进行模式匹配的时候,一定需要注意使用 %
指定目标和依赖的位置关系
src = $(wildcard ./src/*.c) # src = *.c
obj = $(patsubst ./src/%.c , ./obj/%.o , $(src)) # obj = ./obj/add.o ./obj/sub.o ./sub/mul.c
ALL: test
include_path = ./inc
my_args = -Wall -g
test:$(obj)
gcc $^ -o $@ $(my_args) -I $(include_path)
$(obj):./obj/%.o:./src/%.c # 此时 注意 % 匹配的就是 add sub mul 就可以了,注意一定需要找到依赖的位置
gcc -c $< -o $@ $(my_args) -I $(include_path)
clean:
-rm -rf $(obj) test
.PHONY: clean ALL
# 注意头文件链接发生在预处理阶段,这里只需要链接所以不用 -I ./inc
- 注意预处理阶段就完成了头文件的展开,所以这里的链接阶段应该不需要头文件,但是为什么没有头文件也会报错 ????
- 或者可以使用如下参数:
-n
模拟执行 make
命令
-f
指定文件执行 make
命令 xxx.mk
- 注意
$(obj):%.o:%.c
的含义,表示如果依赖是 $(obj)
需要依靠这一条规则生成,如果没有如上依赖,那么就不会作任何事情
- 以下
makefile
用于生成该目录下所有可执行程序
src=$(wildcard *.c)
out=$(patsubst %.c,%,$(src))
ALL: $(out)
%:%.c
gcc $< -o $@
clean:
-rm -rf $(out)
.PYHONY: clean ALL
- 注意静态模式匹配的含义: 确定了需要匹配的字符串,对于这一个字符串,利用
%
进行模式匹配,同时依赖中的 %
依赖于 target
中的 %
- 如果在同一个目录下
%
表示匹配同一个名称
- 还可以指定文件:
src=$(wildcard ./src/*.c)
out=$(patsubst ./src/%.c,./out/%,$(src))
ALL: $(out)
./out/%:./src/%.c
gcc $< -o $@
clean:
-rm -rf $(out)
.PYHONY: clean ALL
- 总结一下: 如果使用模式匹配的方式,首先会利用依赖的匹配字符串在依赖的目录位置找到名称,确定
%
的值,从而确定目标的名称,确定目标的名称之后,就可以通过命令生成对应的目标了
- 静态模式匹配使用的情况就是可以使用多种方式生成依赖,指定生成的方式,所以一般情况下不要使用
- 一种项目构建工具,如果使用编写
makefile
的方式构建项目,由于不同的平台使用的构建构建工具不同(VS
中的nmake
和QtCreator
中qmake
)所以 makefile
依赖于当前的平台,利用cmake
构建项目就可以自动生成makefile
达到了跨平台的效果,同时由于makefile
编写起来工作量太大,使用cmake
可以更加便捷的帮助我们管理大型项目
# 这是单行注释
#[[
这是多行注释
]]
./Cmake-demo1/
├── add.c
├── app.c
├── div1.c
├── head.h
└── sub.c
cmake_minimum_required(VERSION 3.25.0)
project(calc)
add_executable(app app.c div1.c add.c sub.c)
- 程序解释如下:
cmake_minimum_required
表示使用的cmake
的最低版本要求,可选
project
指定项目的基本信息,包含名称,语言等信息
add_excutable
定义工程生成一个可执行程序,这里可以指定多个可执行程序
project
可选参数如下:
# PROJECT 指令的语法是:
project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[HOMEPAGE_URL <url-string>]
[LANGUAGES <language-name>...])
add_excutable(可执行程序名称,源文件名称)
- 执行构建操作:
cmake CMakeLists.txt所在的路径
用于执行CMakeLists.txt
- 自动生成了
makefile
执行 make
命令即可得到可执行程序
- 执行
cmake 目录
命令之后的文件夹内容如下:
.
├── add.c
├── app
├── app.c
├── CMakeCache.txt
├── CMakeFiles
│ ├── 3.28.3
│ │ ├── CMakeCCompiler.cmake
│ │ ├── CMakeCXXCompiler.cmake
│ │ ├── CMakeDetermineCompilerABI_C.bin
│ │ ├── CMakeDetermineCompilerABI_CXX.bin
│ │ ├── CMakeSystem.cmake
│ │ ├── CompilerIdC
│ │ │ ├── a.out
│ │ │ ├── CMakeCCompilerId.c
│ │ │ └── tmp
│ │ └── CompilerIdCXX
│ │ ├── a.out
│ │ ├── CMakeCXXCompilerId.cpp
│ │ └── tmp
│ ├── app.dir
│ │ ├── add.c.o
│ │ ├── add.c.o.d
│ │ ├── app.c.o
│ │ ├── app.c.o.d
│ │ ├── build.make
│ │ ├── cmake_clean.cmake
│ │ ├── compiler_depend.make
│ │ ├── compiler_depend.ts
│ │ ├── DependInfo.cmake
│ │ ├── depend.make
│ │ ├── div1.c.o
│ │ ├── div1.c.o.d
│ │ ├── flags.make
│ │ ├── link.txt
│ │ ├── progress.make
│ │ ├── sub.c.o
│ │ └── sub.c.o.d
│ ├── cmake.check_cache
│ ├── CMakeConfigureLog.yaml
│ ├── CMakeDirectoryInformation.cmake
│ ├── CMakeScratch
│ ├── Makefile2
│ ├── Makefile.cmake
│ ├── pkgRedirects
│ ├── progress.marks
│ └── TargetDirectories.txt
├── cmake_install.cmake
├── CMakeLists.txt
├── div1.c
├── head.h
├── Makefile
└── sub.c
- 可见如果在当前目录中执行
CMake
就会导致的当前目录结构十分混乱,所以一般都需要新建一个build
目录,在build
目录中执行构建操作
build
目录中的构建结果如下:
.
├── app
├── CMakeCache.txt
├── CMakeFiles
├── cmake_install.cmake
└── Makefile
- 可以用于设置变量的值(变量之间使用
;
或者空格进行分割)
# SET 指令的语法是:
# [] 中的参数为可选项, 如不需要可以不写
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
cmake_minimum_required(VERSION 3.25.0)
project(calc)
set(SRC app.c div1.c add.c sub.c)
add_executable(app ${SRC})
- 指定使用的
C++
标准
$ g++ test.c -std=c++11 -o test
- 使用
set
设置 CMAKE_CXX_STANDARD
变量的值
set(CMAKE_CXX_STANDARD 11)
- 在使用
cmake
命令进行构建的时候,指定 DCMAKE_CXX_STANDARD
这一个宏定义的值
$ cmake CMakeLists.txt所在的目录 -DCMAKE_CXX_STANDARD 11
- 指定输出路径
- 直接修改
EXECUTABLE_OUTPUT_PATH
即可
set(EXECUTABLE_OUTPUT_PATH, 目录名)
- 使用
set
之后的 CMakeLists.txt
cmake_minimum_required(VERSION 3.25.0)
project(calc)
set(SRC app.c div1.c add.c sub.c)
# set(CMAKE_CXX_STANDARD 11)
set(HOME /home/loser/公共/C++后端/Linux系统编程/code/Cmake/Cmake-demo1)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/out)
add_executable(app ${SRC})
- 使用
aux_source_directory
搜索路径
aux_source_directory(需要搜索的路径,存储搜索结果的变量)
.
├── build
├── CMakeFiles
│ ├── 3.28.3
│ │ ├── CMakeCCompiler.cmake
│ │ ├── CMakeCXXCompiler.cmake
│ │ ├── CMakeDetermineCompilerABI_C.bin
│ │ ├── CMakeDetermineCompilerABI_CXX.bin
│ │ ├── CMakeSystem.cmake
│ │ ├── CompilerIdC
│ │ │ ├── a.out
│ │ │ ├── CMakeCCompilerId.c
│ │ │ └── tmp
│ │ └── CompilerIdCXX
│ │ ├── a.out
│ │ ├── CMakeCXXCompilerId.cpp
│ │ └── tmp
│ ├── app.dir
│ │ ├── add.c.o
│ │ ├── add.c.o.d
│ │ ├── app.c.o
│ │ ├── app.c.o.d
│ │ ├── build.make
│ │ ├── cmake_clean.cmake
│ │ ├── compiler_depend.make
│ │ ├── compiler_depend.ts
│ │ ├── DependInfo.cmake
│ │ ├── depend.make
│ │ ├── div1.c.o
│ │ ├── div1.c.o.d
│ │ ├── flags.make
│ │ ├── link.txt
│ │ ├── progress.make
│ │ ├── sub.c.o
│ │ └── sub.c.o.d
│ ├── cmake.check_cache
│ ├── CMakeConfigureLog.yaml
│ ├── CMakeDirectoryInformation.cmake
│ ├── CMakeScratch
│ ├── Makefile2
│ ├── Makefile.cmake
│ ├── pkgRedirects
│ ├── progress.marks
│ └── TargetDirectories.txt
├── CMakeLists.txt
├── inc
│ └── head.h
├── out
│ └── app
└── src
├── add.c
├── app.c
├── div1.c
└── sub.c
cmake_minimum_required(VERSION 3.25.0)
project(calc)
# set(SRC app.c div1.c add.c sub.c)
# 搜索头文件
include_directories(${PROJECT_SOURCE_DIR}/inc)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC)
# set(CMAKE_CXX_STANDARD 11)
set(HOME /home/loser/公共/C++后端/Linux系统编程/code/Cmake/Cmake-demo1)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/out)
add_executable(app ${SRC})
- 注意
PROJECT_SOURCE_DIR
表示项目所在的目录,就是当前CMakeLists.txt
所在的目录
CMAKE_CURRENT_SOURCE_DIR
也是当前CMakeLists.txt
所在的目录
- 使用
file
函数
file(GLOB/GLOB_RECURSE 变量名 需要搜索的目录)
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
- 使用
file
之后的 CMakeLists.txt
cmake_minimum_required(VERSION 3.25.0)
project(calc)
# set(SRC app.c div1.c add.c sub.c)
# 搜索头文件
include_directories(${PROJECT_SOURCE_DIR}/inc)
# aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
# set(CMAKE_CXX_STANDARD 11)
set(HOME ${CMAKE_CURRENT_SOURCE_DIR})
set(EXECUTABLE_OUTPUT_PATH ${HOME}/out)
add_executable(app ${SRC})
- 注意
aux_source_directory
指定的是文件夹
file
指定的是具体的文件
- 上面使用过的,使用
include_directories
函数
include_directories(头文件所在的目录名称)
cmake_minimum_required(VERSION 3.25.0)
project(calc)
# set(SRC app.c div1.c add.c sub.c)
# 搜索头文件
include_directories(${PROJECT_SOURCE_DIR}/inc)
# aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
# set(CMAKE_CXX_STANDARD 11)
set(HOME ${CMAKE_CURRENT_SOURCE_DIR})
set(EXECUTABLE_OUTPUT_PATH ${HOME}/out)
add_executable(app ${SRC})
PROJECT_SOURCE_DIR
一般就是cmake
后面跟的路径
add_library(库名称 STATIC 源文件 ...)
- 注意
linux
下的库名称 lib + 库名称 + .a
这里只用指定库名称,其他的自动生成
CMakeLists.txt
如下:
cmake_minimum_required(VERSION 3.25.0)
project(cacl)
include_directories(${PROJECT_SOURCE_DIR}/inc)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
add_library(mymath STATIC ${SRC})
add_library(库名称 SHARED 源文件...)
- 注意
linux
下动态库的库名: lib + 库名 + .so
,这里只用指定库名称就可以了
- 具体编写的
CMakeLists.txt
如下
cmake_minimum_required(VERSION 3.25.0)
project(cacl)
include_directories(${PROJECT_SOURCE_DIR}/inc)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
add_library(cacl SHARED ${SRC})
- 由于动态库具有可执行权限,所以被当成可执行程序处理,可以利用
set
设置 EXECUTABLE_OUTPUT_PATH
即可
cmake_minimum_required(VERSION 3.25.0)
project(cacl)
include_directories(${PROJECT_SOURCE_DIR}/inc)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(cacl SHARED ${SRC})
- 设置
LIBRAYR_OUTPUT_PATH
的值
cmake_minimum_required(VERSION 3.25.0)
project(cacl)
include_directories(${PROJECT_SOURCE_DIR}/inc)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(cacl STATIC ${SRC})
.
├── build
├── CMakeLists.txt
├── inc
│ └── head.h
├── lib
│ ├── libcacl.a
│ └── libcacl.so
├── out
└── src
└── app.c
- 使用
link_libraries
命令,使用方式如下:
link_libraries(静态库名称1 静态库名称2 ...)
- 静态库名称可以时全名
lib + 库名 + .a
,也可以就是库名
- 如果不是系统提供的库,而是第三方库的,还需要指定路径,使用
link_directories
函数
link_directories(静态库路径名称)
cmake_minimum_required(VERSION 3.25.0)
project(test)
file(GLOB SRC ${PROJECT_SOURCE_DIR}/src/*.c)
include_directories(${PROJECT_SOURCE_DIR}/inc)
link_directories(${PROJECT_SOURCE_DIR}/lib)
link_libraries(libcacl.a) # 或者指定库名就可以
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/out)
add_executable(app ${SRC})
- 链接动态库可以使用
target_link_libraries
函数,函数的使用方法如下:
target_link_libraries(target_name [item1 [item2 [...]]]
[<debug|optimized|general> <lib1> [<lib2> [...]]])
target
可以是源文件,可执行程序或者库文件
PRIVATE|PUBLIC|INTERFACE
表示动态库的访问权限
- 如果动态库之间没有依赖关系,随便如何填写
- 链接具有传递性,类似于
maven
的依赖传递原则,比如 A
链接B
C
D
链接A
,那么D
也会链接B
和C
- 三种访问权限的作用如下:
PUBLIC
:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。
PRIVATE
:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库
INTERFACE
:在interface后面引入的库不会被链接到前面的target中,只会导出符号。
- 这里我的理解就是动态库也是一个可执行程序,所以可以链接到另外的可执行程序上面,相当于链式的链接
- 比如:
target_link_directories(app calc pthread)
- 相当于把
pthread
连接到 calc
在把 calc
连接到 app
上
- 动态库和静态库之间的区别:
- 静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。
- 动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存
- 所以链接需要放在生成可执行程序之后
- 如果是调用第三方库,还需要指定库的路径,还是可以利用
link_directories
函数指定库的路径
- 所以使用动态库的
CMakeLists.txt
如下:
cmake_minimum_required(VERSION 3.25.0)
project(test)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/out)
include_directories(${PROJECT_SOURCE_DIR}/inc)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
link_directories(${PROJECT_SOURCE_DIR}/lib)
add_executable(app ${SRC})
target_link_libraries(app cacl pthread)
- 注意
target_link_libraries
和link_libraries
的区别:
- 后者用于全局链接库,之后生成的目标都会受到影响
- 前者用于对于目标链接库,控制更为精确
- 可以使用
message
控制 CMake
日志输出:
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
- 各种状态如下:
- (无) :重要消息
STATUS
:非重要消息
WARNING
:CMake 警告, 会继续执行
AUTHOR_WARNING
:CMake 警告 (dev), 会继续执行
SEND_ERROR
:CMake 错误, 继续执行,但是会跳过生成的步骤
FATAL_ERROR
:CMake 错误, 终止所有处理过程
set(变量名1 ${变量名1} ${变量名2} ...)
list([OPTIONS] <list> [<element> ...])
- 各种选项可以见: https://subingwen.cn/cmake/CMake-primer/#%E4%BD%BF%E7%94%A8set%E6%8B%BC%E6%8E%A5
CMake
中可以自定义宏定义,作用就是昨天说的,可以作为开关控制程序的运行,使用 add_definitions
函数即可
add_definitions(-D宏定义)
宏 | 功能 |
PROJECT_SOURCE_DIR | 使用cmake命令后紧跟的目录,一般是工程的根目录 |
PROJECT_BINARY_DIR | 执行cmake命令的目录 |
CMAKE_CURRENT_SOURCE_DIR | 当前处理的CMakeLists.txt所在的路径 |
CMAKE_CURRENT_BINARY_DIR | target 编译目录 |
EXECUTABLE_OUTPUT_PATH | 重新定义目标二进制可执行文件的存放位置 |
LIBRARY_OUTPUT_PATH | 重新定义目标链接库文件的存放位置 |
PROJECT_NAME | 返回通过PROJECT指令定义的项目名称 |
CMAKE_BINARY_DIR | 项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径 |
- 包含
CMakeLists.txt
的嵌套(大型项目中使用到),以及循环控制语句的使用,可以参考: https://subingwen.cn/cmake/CMake-advanced/