gcc

gcc 编译四步骤

  • 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

gcc常用的参数

  • -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文件
# 获取 .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
  • -l 指定库名
  • -L 指定库的路径
# 编译指令
$ 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时的这一个文件生效

gdb

  • 调试作用
  • 注意需要加上 -g 用于生成调试信息,从而可以使用 gdb进行检测
  • 使用方式:
    • gdb 可执行程序名称 进入调试状态
    • list 或者l 显示源码
    • list n 或者 l n 表示从第 n 行开始显示源码
    • b n 表示在第n行设置断点
    • run 或者 r 表示执行程序,一般用于执行到断点的位置
    • n 表示 next 越过这一行直接进入下一行(在调试的时候使用)
    • s 表示进入这一行(step)
    • p value 可以用于查看变量的值,这就是表示查看 value 的值
    • continue 表示继续执行下面的函数,相当于放行
    • quit 退出

gdb进阶命令

  • 如果出现了段错误,那么直接使用 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 查看局部变量
  • 常见错误:
    • 没有符号表读取: 没有 -g 选项

Makefile

  • 相当于脚本,相当于一组命令的集合
  • Makefile中的注意点:
    1. 一个规则
    2. 两个函数
    3. 三个自动变量

规则

  • 一个规则:
    • 目标: 依赖条件 # 表示格式 (一个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

函数

wildcard函数

  • src = $(wildcard *.c) 用于找到当前目录下的所有的 .c 文件,赋值给 src

patsubst

  • 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
  • 总结一下: 如果使用模式匹配的方式,首先会利用依赖的匹配字符串在依赖的目录位置找到名称,确定%的值,从而确定目标的名称,确定目标的名称之后,就可以通过命令生成对应的目标了
  • 静态模式匹配使用的情况就是可以使用多种方式生成依赖,指定生成的方式,所以一般情况下不要使用

CMake

介绍

  • 一种项目构建工具,如果使用编写makefile的方式构建项目,由于不同的平台使用的构建构建工具不同(VS中的nmakeQtCreatorqmake)所以 makefile依赖于当前的平台,利用cmake构建项目就可以自动生成makefile达到了跨平台的效果,同时由于makefile编写起来工作量太大,使用cmake可以更加便捷的帮助我们管理大型项目

CMake使用

注释

  • 单行注释:
# 这是单行注释
  • 多行注释:
#[[
  这是多行注释
]]

入门案例

  • 程序结构如下:
./Cmake-demo1/
├── add.c
├── app.c
├── div1.c
├── head.h
└── sub.c
  • 在这一个目录中编写 CMakeLists.txt
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使用
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函数的使用

  1. 可以用于设置变量的值(变量之间使用 ; 或者空格进行分割)
# 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})
  1. 指定使用的 C++ 标准
  • 如果使用 g++ 指定方式如下:
$ 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
  1. 指定输出路径
  • 直接修改 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})

搜索文件

  1. 使用 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所在的目录
  1. 使用 file 函数
  • 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函数即可
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函数
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})
指定输出路径
  1. 由于动态库具有可执行权限,所以被当成可执行程序处理,可以利用 set 设置 EXECUTABLE_OUTPUT_PATH即可
  • 好像没有用
  • 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)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(cacl SHARED ${SRC})
  1. 设置 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(静态库路径名称)
  • 使用静态库的CMakeLists.txt如下:
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也会链接BC
  • 三种访问权限的作用如下:
    • 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_librarieslink_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 进行变量的拼接
set(变量名1 ${变量名1} ${变量名2} ...)
利用 list 进行各种列表的操作
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_DIRtarget 编译目录
EXECUTABLE_OUTPUT_PATH重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH重新定义目标链接库文件的存放位置
PROJECT_NAME返回通过PROJECT指令定义的项目名称
CMAKE_BINARY_DIR项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径

其他的高级操作

  • 包含 CMakeLists.txt的嵌套(大型项目中使用到),以及循环控制语句的使用,可以参考: https://subingwen.cn/cmake/CMake-advanced/