cmake和宏开关
# cmake和宏开关
Owner: -QVQ-
# cmake
# 基本
aux_source_directory(dir var)
cmake中提供把指定目录下所有源文件储存在一个变量中的函数
add_subdirectory
可以向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制的存放位置
include_directories()
函数包裹头文件路径,要么通过如下方式给出头文件位置
add_executable(生成的exe名 源文件)
指定生成可执行文件名和相关源文件
set(变量名 文件名……)
新建变量来存放需要的源文件
EXECUTABLE_OUTPUT_PATH
:目标二进制可执行文件的存放位置
PROJECT_SOURCE_DIR
:工程的根目录
add_library
: 生成动态库或静态库(第1个参数指定库的名字;第2个参数决定是动态还是静态,如果没有就默认静态;第3个参数指定生成库的源文件)
set_target_properties
: 设置最终生成的库的名称,还有其它功能,如设置库的版本号等等
LIBRARY_OUTPUT_PATH
: 库文件的默认输出路径,这里设置为工程目录下的lib目录
find_library
: 在指定目录下查找指定库,并把库的绝对路径存放到变量里,其第一个参数是变量名称,第二个参数是库名称,第三个参数是HINTS,第4个参数是路径,其它用法可以参考cmake文档
find_library函数默认查看动态库,如下方式可以指定使用动态库还是静态库find_library(TESTFUNC_LIB libtestFunc.so)
target_link_libraries
: 把目标文件与库文件进行链接
# 对于同一目录下的文件
目录下创建CMakeLists.txt,cmake无视大小写
cmake_minimum_required(VERSION 3.15)# 指定使用CMake的最低版本号
project(Tutorial)# 项目名字
add_executable(Tutorial tutorial.cpp 多个文件...)# 指定生成可执行文件名和相关源文件
2
3
4
5
执行 cmake .
生成makefile和其他文件
执行make
开始编译生成可执行文件
cmake中提供把指定目录下所有源文件储存在一个变量中的函数
aux_source_directory(dir var)
第一个是指定目录,第二个是存放源文件列表的变量
cmake_minimum_required (VERSION 2.8)
project (demo)
aux_source_directory(. SRC_LIST)
add_executable(main ${SRC_LIST})
2
3
4
5
6
7
如果只要部分文件,可以用set
命令去新建变量来存放需要的源文件
cmake_minimum_required (VERSION 2.8)
project (demo)
set( SRC_LIST
./main.c
./testFunc1.c
./testFunc.c)
add_executable(main ${SRC_LIST})
2
3
4
5
6
7
8
9
10
# 对于不同目录下的文件
用include_directories()
向工程加入多个头文件的搜索路径,路径间用空格分隔
用多个变量将不同路径下的文件加入进去
cmake_minimum_required (VERSION 2.8)
project (demo)
# 加入路径
include_directories (test_func test_func1)
# 加入文件
aux_source_directory (test_func SRC_LIST)
aux_source_directory (test_func1 SRC_LIST1)
add_executable (main main.c ${SRC_LIST} ${SRC_LIST1})
# 目录结构:
# CMakeLists.txt
# main.c
# **test_func**
# --1.c
# --1.h
# **test_func1**
# --2.c
# --2.h
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
对于main.c ,如果引用了1.h和2.h,要么通过include_directories()
函数包裹头文件路径,要么通过如下方式给出头文件位置
#include "test_func1/2.h"
# 对于正规的组织结构
正规一点来说,一般会把源文件放到src目录下,把头文件放入到include文件下,生成的对象文件放入到build目录下,最终输出的elf文件会放到bin目录下
# 目录结构:
# **bin**
# **build**
# **include**
# --1.h
# --2.h
# **src**
# --1.c
# --2.c
# --main.c
2
3
4
5
6
7
8
9
10
通常在最外层建一个CMakeLists.txt
cmake_minimum_required (VERSION 2.8)
project (demo)
add_subdirectory (src)
2
3
4
5
add_subdirectory
可以向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制的存放位置
此时执行cmake 会进入src目录下去找CMakeLists.txt,所以在src目录下也建立一个CMakeLists.txt,内容如下,
cmake自带的预定义变量
# 添加当前路径下的所有文件
aux_source_directory (. SRC_LIST)
# 添加头文件
include_directories (../include)
# 指定生成的可执行文件名和源文件
add_executable (main ${SRC_LIST})
# 这里的set指把存放elf文件的位置设置为工程根目录下的bin目录
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
2
3
4
5
6
7
8
- EXECUTABLE_OUTPUT_PATH :目标二进制可执行文件的存放位置
- PROJECT_SOURCE_DIR:工程的根目录
此时切换到build路径下,输入cmake ..
在build下生成中间文件,然后在build下运行make
在这个例子下写了两个CMakeLists.txt,如果只想要一个CMakeLists.txt,只需把根目录下的CMakeLists.txt改为
cmake_minimum_required (VERSION 2.8)
project (demo)
# 相当于不设置src文件
# 后面的四步等同里层的CMakeLists.txt文件
aux_source_directory (src SRC_LIST)
include_directories (include)
add_executable (main ${SRC_LIST})
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
2
3
4
5
6
7
8
9
10
11
12
# 动态库和静态库的编译控制
只编译出动态库和静态库,让其他程序去使用
# 对于如下结构目录
# build
# CMakeLists.txt
# lib
# testFunc
# --testFunc.c
# --testFunc.h
2
3
4
5
6
7
在build下运行cmake,并把生成的库文件放到lib下
cmake_minimum_required (VERSION 3.5)
project (demo)
# 指定源文件
set (SRC_LIST ${PROJECT_SOURCE_DIR}/testFunc/testFunc.c)
# 指定动态库的名字
add_library (testFunc_shared SHARED ${SRC_LIST})
# 指定静态库的名字
add_library (testFunc_static STATIC ${SRC_LIST})
# 设定最终生成库的名称
set_target_properties (testFunc_shared PROPERTIES OUTPUT_NAME "testFunc")
set_target_properties (testFunc_static PROPERTIES OUTPUT_NAME "testFunc")
set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
2
3
4
5
6
7
8
9
10
11
12
13
14
add_library
: 生成动态库或静态库(第1个参数指定库的名字;第2个参数决定是动态还是静态,如果没有就默认静态;第3个参数指定生成库的源文件)
set_target_properties
: 设置最终生成的库的名称,还有其它功能,如设置库的版本号等等
LIBRARY_OUTPUT_PATH
: 库文件的默认输出路径,这里设置为工程目录下的lib目录
# 对库进行链接
# 对于如下目录结构
# bin
# build
# CMakeLists.txt
# src
# --main.c
# testFunc
# --inc
# ------testFunc.h
# --lib
# ------libtestFunc.a
# ------libtestFunc.so
2
3
4
5
6
7
8
9
10
11
12
工作目录下的CMakeLists.txt
cmake_minimum_required (VERSION 3.5)
project (demo)
# 设置可执行文件的输出路径
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 设置源文件路径
set (SRC_LIST ${PROJECT_SOURCE_DIR}/src/main.c)
# find testFunc.h
include_directories (${PROJECT_SOURCE_DIR}/testFunc/inc)
# 查找指定库
find_library(TESTFUNC_LIB testFunc HINTS ${PROJECT_SOURCE_DIR}/testFunc/lib)
# 指定源文件和生成的可执行文件名
add_executable (main ${SRC_LIST})
# 目标文件和库文件
target_link_libraries (main ${TESTFUNC_LIB})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
find_library
: 在指定目录下查找指定库,并把库的绝对路径存放到变量里,其第一个参数是变量名称,第二个参数是库名称,第三个参数是HINTS,第4个参数是路径,其它用法可以参考cmake文档target_link_libraries
: 把目标文件与库文件进行链接
linux下通过
readelf -d ./xx
可以查看源文件的库依赖
# 添加编译或控制选项
# 编译选项添加
add_compile_options()
指定编译选项
cmake_minimum_required (VERSION 2.8)
project (demo)
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 指定编译选项
add_compile_options(-std=c++11 -Wall)
add_executable(main main.cpp)
2
3
4
5
6
7
8
9
# 添加控制选项选择文件编译
option()第一个参数是这个option的名字,第二个参数是字符串,用来描述这个option是来干嘛的,第三个是option的值,ON或OFF,不写就是默认OFF。
# 目录结构为
# bin
# build
# CMakeLists.txt
# src
# ---CMakeLists.txt
# ---main1.c
# ---main2.c
2
3
4
5
6
7
8
外层的CMakeList.txt
cmake_minimum_required(VERSION 3.5)
project(demo)
option(MYDEBUG "enable debug compilation" OFF)
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
add_subdirectory(src)
2
3
4
5
6
7
8
src目录下的CMakeList.txt
cmake_minimum_required (VERSION 3.5)
add_executable(main1 main1.c)
# 依据前面的开关决定这里是否编译main.c
if (MYDEBUG)
add_executable(main2 main2.c)
else()
message(STATUS "Currently is not in debug mode")
endif()
2
3
4
5
6
7
8
9
编译时在build目录下输入cmake .. -DMYDEBUG=ON
,这样就可以编译出main1和main2
# 添加控制选项选择宏开关
mian.c内容如下
#include <stdio.h>
int main(void)
{
#ifdef WWW1
printf("hello world1\n");
#endif
#ifdef WWW2
printf("hello world2\n");
#endif
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
CMakeList.txt中的内容如下
cmake_minimum_required(VERSION 3.5)
project(demo)
# 设置可执行文件的路径
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 设置宏开关
option(WWW1 "print one message" OFF)
option(WWW2 "print another message" OFF)
# 根据宏开关在make 的时候决定是否加上宏定义
if (WWW1)
add_definitions(-DWWW1)
endif()
if (WWW2)
add_definitions(-DWWW2)
endif()
# 决定可执行文件名称和源文件位置
add_executable(main main.c)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
分别安装如下命令能打印出不同的内容
cmake .. -DWWW1=ON -DWWW2=OFF && make
cmake .. -DWWW1=OFF -DWWW2=ON && make
cmake .. -DWWW1=ON -DWWW2=ON && make
注意:如果有多个options选项,先用cmake设置了A,下次再调用cmake设置B,这个A的状态会沿用上次的
所以如果option有变化,要么删除上次执行cmake时产生的缓存文件,要么把所有的option都显式的指定其值。
# 控制语句
IF (CMAKE_BUILD_TYPE STREQUAL DEBUG)
ADD_DEFINITIONS(-DDEBUG)
ENDIF()
//如果CMAKE_BUILD_TYPE的值等于DEBUG执行语句
2
3
4
cmake中if语句除了STREQUAL关键字,还有以下关键字可以使用:
- STREQUAL: 判断字符串是否相等
- STRLESS: 判断字符串是否小于
- STRGREATER: 判断字符串是否大于
- STRLESS_EQUAL: 判断字符串是否小于等于
- STRGREATER_EQUAL: 判断字符串是否大于等于
- EQUAL: 判断数字是否相等
- LESS: 判断数字是否小于
- GREATER: 判断数字是否大于
- LESS_EQUAL: 判断数字是否小于等于
- GREATER_EQUAL: 判断数字是否大于等于
- AND: 逻辑与
- OR: 逻辑或
- NOT: 逻辑非
这些关键字可以用来进行条件判断,根据判断结果执行相应的操作。
# cmake中的宏开关
为了调试程序,经常需要加一些输出语句,可以定义一个debug宏作为调试输出的开关。
实例代码:
#include <stdio.h>
int main(void)
{
int i, sum;
for (i = 1, sum = 0; i <= 5; i++)
{
sum += i;
#ifdef DEBUG
printf("sum += %d is %d\n", i, sum);
#endif
}
printf("total sum is %d\n", sum);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
使用gcc编译时用-D打开这个宏
gcc -D DEBUG test.c
更进一步的,自己写输出宏函数,使用时用自己的宏函数,避免了每次都写#ifdef DEBUG
#include <stdio.h>
#ifdef DEBUG
#define DBGprint(...) printf(__VA_ARGS__)
#else
#define DBGprint(...)
#endif
int main(void)
{
int i, sum;
for (i = 1, sum = 0; i <= 5; i++)
{
sum += i;
DBGprint("sum += %d is %d\n", i, sum);
}
printf("total sum is %d\n", sum);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在cmake中打开宏
在CMakeLists文件中添加DEBUG的定义:
IF (CMAKE_BUILD_TYPE STREQUAL DEBUG)
ADD_DEFINITIONS(-DDEBUG)
ENDIF()
2
3
在cmake的时候设置参数DCMAKE_BUILD_TYPE 为 DEBUG:
$ cmake .. -DCMAKE_BUILD_TYPE=DEBUG $ make -j4
cmake -D<variable>=<value>
定义 CMake 变量(-D后面没有空格)。<variable>
为变量名,<value>
为变量的值。可以使用多个 -D
选项来定义多个变量。
cmake CFLAG=-std
为gdb添加选项
cmake -DCMAKE_CXX_STANDARD=11
指定g++的版本为c++11
cmake .. -DCMAKE_BUILD_TYPE=Debug
指定编译出来的文件为可调式的不做优化的,release是发行版本
# cmake中set
set(CMAKE_CXX_STANDARD_REQUIRED ON)
设置指定的C++编译器版本是必须的,如果不设置,或者为OFF,则指定版本不可用时,会使用上一版本。
set(CMAKE_CXX_STANDARD 11)
指定g++的编译选项
set(CMAKE_C_FLAGS_RELEASE "-O0 -ggdb")
指定gcc的编译选项
set(CMAKE_BUILD_TYPE DEBUG)
set(CMAKE_C_FLAGS "-O0 -ggdb")
set(CMAKE_C_FLAGS_DEBUG "-O0 -ggdb")
set(CMAKE_C_FLAGS_RELEASE "-O0 -ggdb")
set(CMAKE_CXX_FLAGS "-O0 -ggdb")
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "-O0 -ggdb")
2
3
4
5
6
7
# 一些配置选项
cmake_minimum_required(VERSION 3.10)
设置cmake的最低版本
# 理论
# debug和release模式
两者并没有本质界限,只是编译选项的不同,可以自定义编译选项以设置不同的版本
主要编译选项
Debug版本:
/Od 关闭优化开关
/D ”_DEBUG“ 打开编译调试代码的宏定义(主要是assert函数)
/ZI 创建Edit and continue编辑继续数据库,这样在调试的过程中修改了源代码不需重新编译
/GZ 帮助捕获内存错误{
主要功能:
1 初始化内存和变量,用一些特殊的值初始化变量,堆区、内存等,这些值一般不会使用,也容易辨认,利于在debug版本中发现release版才会遇到的错误
2 检查函数指针调用函数时是否匹配
3 函数返回当前检查栈指针,确认未被修改
}
/MDd /MLd 或/MTd 使用Debug、runtime、library(调试版本运行时刻函数库)
Release版本:
/MD /MT 或ML 使用发布版本的运行时刻函数库
/O1 /O2优化开关,是程序最小或最快
/D ”NDEBUG“关闭条件编译调试代码开关(不编译assert函数)
/GF 合并重复的字符串,将字符串常量放到只读内存
如果Debug版本有错,Release正常,程序肯定有问题,只是某次运行没有表现出来
# 哪些情况下release版本会出错
优化问题是造成错误的主要原因
一、帧指针(Frame Pointer即FPO)
函数的声明和实现不同(数据类型不同),Debug模式下可以通过EBP寄存器保存的地址实现,release会省略EBP栈基址指针,通过全局指针访问栈。
C++的强类型能检查出,但强制类型转换就不行
二、volatile型变量
有时一些变量放在寄存器中,其他进程直接对变量内存进行修改,多线程下,如果发现某个值和预期不符,可以把认为可以的变量加上volatile
三、变量优化
void abc(void) {
int i = 1;
int a[4];
{
int j;
j = 1; //j的空间还没有回收
}
a[-1] = 1; //此时虽然j已经离开了作用域,当其空间并未回收,从而掩盖了越界
std::cout<<a[-1];
//a[3] = 1;
}
int main()
{
abc();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
函数有未使用的变量在debug版本中它可能掩盖数组越界
而release版本由于j未使用则可能会被优化掉,从而使栈被破坏,从而程序终止
四、 /GZ选项
会造成Debug版本错误,而Release版本正常,因为Debug版本变量随机分配,可能指向应该有效地址而掩盖了非法访问
# 调试方法
修改编译选项来缩小错误范围
debug版中使用/W4警告级别,获得最多的错误信息
#progma warning(disable: 4702) //禁止某个错误
#progma warning(default: 4702)
#progma warning(push, 3) //设置警告级别为/W3
#progma warning(pop) //重设警告级别
2
3
4
# debug模式下正常运行,release模式下运行出错
问题:调试时发现代码进入了第三方库的时候,构建了函数栈,还没执行第一条函数的时候发生段错误
分析:编译时开启了release模式时运行错误,使用debug模式运行正确