需要先创建CMakeLists.txt文档和main.cpp或main.c主程序
在文档中编写连接等指令
指令:CMake . (当前文件夹构建)
CMake … (上一层文件夹构建)
系统自动生成了如下的文件:
包括:CMakeCache.txt、CMakeFiles、cmake_install.cmake、Makefile等中间文件。
project (mytest)
这样就指定了当前的工程名称为mytest。
将指定目录添加到编译器的头文件搜索路径之下,指定的目录被解释成当前源码路径的相对路径。
默认情况下,include_directories命令会将目录添加到列表最后,可以通过命令设置CMAKE_INCLUDE_DIRECTORIES_BEFORE变量为ON来改变它默认行为,将目录添加到列表前面。也可以在每次调用include_directories命令时使用AFTER或BEFORE选项来指定是添加到列表的前面或者后面。如果使用SYSTEM选项,会把指定目录当成系统的搜索目录。该命令作用范围只在当前的CMakeLists.txt。
添加名为name的库,库的源文件可指定,也可用target_sources()后续指定。
库的类型是STATIC(静态库)/SHARED(动态库)/MODULE(模块库)之一。
指定链接给定目标和/或其依赖项时要使用的库或标志。将传播链接库目标的使用要求。目标依赖项的使用要求会影响其自身源的编译。
set(CMAKE_BUILD_TYPE “Debug”)
设置调试信息(查找程序错误)
install()命令为项目生成安装规则,通过在源目录中调用install()命令指定的安装规则将在安装过程中按顺序执行。此命令有多种格式,分别对应不同的安装目标,如:二进制文件、动态库、静态库以及文件、目录、脚本等。基本命令格式如下:
上述6种命令格式,有部分参数使用方式和意义相同,在这里统一说明,后续不再单独说明。
参数如下:
DESTINATION
用于指定安装路径,可以是绝对路径,也可以是相对路径。
如果使用的是相对路径,那么需要配合使用CMAKE_INSTALL_PREFIX变量来指定路径前缀,该变量可以在CMakeLists.txt文件中设置,也可以通过cmake命令指定。由于cpack不支持绝对路径,所以cmake官方建议使用相对路径.
PERMISSIONS
指定安装文件的权限,这些权限包括:OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ,GROUP_WRITE, GROUP_EXECUTE, WORLD_READ, WORLD_WRITE, WORLD_EXECUTE,SETUID, 和 SETGID。
CONFIGURATIONS
指定安装规则适用的构建配置列表(DEBUG或RELEASE等).
注意:此参数需要在RUNTIME DESTINATION参数之前使用.
COMPONENT
指定与安装规则相关联的安装组件名称,如"runtime"或"development"。在对应组件的安装过程中,将仅执行与给定组件名称关联的安装规则。除非显示标记为EXCLUDE_FROM_ALL,否则将安装所有组件。如果未指定COMPONENT,则会创建默认组件“未Unspecified”。可以使用CMAKE_INSTALL_DEFAULT_COMPONENT_NAME变量来控制默认组件名称。
EXCLUDE_FROM_ALL
指定该文件从完整安装中排除,并且仅作为特定于组件的安装的一部分进行安装。
RENAME
重命名要安装的文件,此参数只在安装单个文件时有效。
OPTIONAL
声明此步骤是可选的,即如果要安装的文件不存在,不必报错,程序将继续向后执行。
安装目标文件
这里的目标文件包含很多中类型,比如动态库.so, 静态库.a,和执行二进制文件等。
这种方式用于说明为项目安装文件的规则。最常见的比如安装.so对应的头文件。
要安装的文件,由当前目录下的相对路径指定。如果没有显式指定PERMISSIONS参数,默认情况下文件的权限为:OWNER_WRITE,
OWNER_READ, GROUP_READ, 和WORLD_READ。
PROGRAMS
与FILE类型的文件类似,但是多了OWNER_EXECUTE, GROUP_EXECUTE,
和WORLD_EXECUTE权限。注意,虽然多了EXECUTE权限,但是这与TARGETS类型的操作是不一样的,比如shell 脚本。
TYPE
指定安装文件的类型。TYPE和DESTINATION 必须至少有一个被指定。
cmake支持的TYPE类型如下:
message :为用户显示一条消息。
可以用下述可选的关键字指定消息的类型:
(无) = 重要消息;
STATUS = 非重要消息;
WARNING = CMake 警告, 会继续执行;
AUTHOR_WARNING = CMake 警告 (dev), 会继续执行;
SEND_ERROR = CMake 错误, 继续执行,但是会跳过生成的步骤;
FATAL_ERROR = CMake 错误, 终止所有处理过程
例:输出错误 FATAL_ERROR
message(FATAL_ERROR "
FATAL: In-source builds are not allowed.
You should create a separate directory for build files.
")
-w的意思是关闭编译时的警告,也就是编译后不显示任何warning,因为有时在编译之后编译器会显示一些例如数据转换之类的警告,这些警告是我们平时可以忽略的。
-Wall选项意思是编译后显示所有警告。
-W选项类似-Wall,会显示警告,但是只显示编译器认为会出现错误的警告。
在编译一些项目的时候可以-W和-Wall选项一起使用。
举个例子:
#include
void main()
{
int a=1.0*4;
return 0;
}
直接编译
gcc -o test_w_wall testwwall.c
只显示这一个警告,下面使用-w选项。
gcc -w -o test_w_wall testwwall.c 不会显示任何警告,直接编译成功。
gcc -Wall -o test_w_wall testwwall.c
显示了所有的警告,比之前不使用任何选项多出了变量a未使用这个警告,也多出了main函数的返回值不是int型。
gcc -W -o test_w_wall testwwall.c
只显示了没有返回值的main函数不应该有return一个值这个警告。
gcc -W -Wall test_w_wall testwwall.c
比单独使用-W多出了变量为使用这个警告,比-Wall选项少了一个看起来重复的main函数返回值不是int这个警告。
之前看了一篇国外程序员写的博客,说编译时不使用-W -Wall选项的是stupid的,所以编译时还是尽量带上吧。
gcc 提供了为了满足用户不同程度的的优化需要,提供了近百种优化选项,用来对{编译时间,目标文件长度,执行效率}这个三维模型进行不同的取舍和平衡。
优化的方法不一而足,总体上将有以下几类:1)精简操作指令;2)尽量满足cpu的流水操作;3)通过对程序行为地猜测,重新调整代码的执行顺序;4)充分使用寄存器;5)对简单的调用进行展开等等。想全部了解这些编译选项,并在其中挑选适合的选项进行优化,无疑像个噩梦般的过程。单从gnu的官方网站上得到的手册来看,描述依然比较苍白,不足以完全了解选项的使用范围和原理。
-O0: 不做任何优化,这是默认的编译选项。
-O和-O1优化会消耗少多的编译时间,它主要对代码的分支,常量以及表达式等进行优化。
-O2会尝试更多的寄存器级的优化以及指令级的优化,它会在编译期间占用更多的内存和编译时间。
-O3在-O2的基础上进行更多的优化,例如使用伪寄存器网络,普通函数的内联,以及针对循环的更多优化。
-Os主要是对代码大小的优化,我们基本不用做更多的关心。
通常各种优化都会打乱程序的结构,让调试工作变得无从着手。并且会打乱执行顺序,依赖内存操作顺序的程序需要做相关处理才能确保程序的正确性。
优化代码有可能带来的问题
1.调试问题:正如上面所提到的,任何级别的优化都将带来代码结构的改变。例如:对分支的合并和消除,对公用子表达式的消除,对循环内load/store操作的替换和更改等,都将会使目标代码的执行顺序变得面目全非,导致调试信息严重不足。
2.内存操作顺序改变所带来的问题:在O2优化后,编译器会对影响内存操作的执行顺序。例如:-fschedule-insns允许数据处理时先完成其他的指令;-fforce-mem有可能导致内存与寄存器之间的数据产生类似脏数据的不一致等。对于某些依赖内存操作顺序而进行的逻辑,需要做严格的处理后才能进行优化。例如,采用volatile关键字限制变量的操作方式,或者利用barrier迫使cpu严格按照指令序执行的。
CMAKE_C_FLAGS
设置 C 编译选项,也可以通过指令 ADD_DEFINITIONS()添加。
CMAKE_CXX_FLAGS
设置 C++编译选项,也可以通过指令 ADD_DEFINITIONS()添加。
-march=navite navite表示允许编译器自动探测目标架构(即:编译主机)并生成针对目标架构优化的目标代码。
注意:
问题说明:在core-i5主机上编译程序,并打包,放到 core-i3 的设备上出现段错误:
“xxx received signal SIGILL, Illegal instruction”
经查,是因为在编译时指定了 -march=native 选项,该选项使得编译器在生成目标文件时,针对当前编译主机(core-i5)进行了优化,导致在 core-i3 上无法正常执行。(前提:i5 和 i3 的系统版本、编译器版本、依赖库版本均完全相同)
-std=c++11设置为使用C++11标准
作用: 检查 CXX 编译器是否支持给定的标志。
注意:必须先include(CheckCXXCompilerFlag)
例子1:在检查当前编译器是否支持c++11
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
CHECK_CXX_COMPILER_FLAG("-std=c++0x"COMPILER_SUPPORTS_CXX0X)
if(COMPILER_SUPPORTS_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
add_definitions(-DCOMPILEDWITHC11)
message(STATUS "Using flag -std=c++11.")
elseif(COMPILER_SUPPORTS_CXX0X)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
add_definitions(-DCOMPILEDWITHC0X)
message(STATUS "Using flag -std=c++0x.")
else()
message(FATAL_ERROR "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
endif()
include 指令用来载入并运行来自于文件或模块的 CMake 代码
list(LENGTH <list><output variable>)
list(GET <list> <elementindex> [<element index> ...]<output variable>)
list(APPEND <list><element> [<element> ...])
list(FIND <list> <value><output variable>)
list(INSERT <list><element_index> <element> [<element> ...])
list(REMOVE_ITEM <list> <value>[<value> ...])
list(REMOVE_AT <list><index> [<index> ...])
list(REMOVE_DUPLICATES <list>)
list(REVERSE <list>)
list(SORT <list>)
LENGTH 返回list的长度
GET 返回list中index的element到value中
APPEND 添加新element到list中
FIND 返回list中element的index,没有找到返回-1
INSERT 将新element插入到list中index的位置
REMOVE_ITEM 从list中删除某个element
REMOVE_AT 从list中删除指定index的element
REMOVE_DUPLICATES 从list中删除重复的element
REVERSE 将list的内容反转
SORT 将list按字母顺序排序
LIST与SET命令类似,即使列表本身是在父域中定义的,LIST命令也只会在当前域创建新的变量,要想将这些操作的结果向上传递,需要通过SET PARENT_SCOPE, SET CACHE INTERNAL或运用其他值域扩展的方法。
注意:cmake中的list是以分号隔开的一组字符串。可以使用set命令创建一个列表。例如:set(var a b c d e)创建了一个这样的列表:a;b;c;d;e。 set(var “a b c d e”)创建了一个字符串或只有一个元素的列表。
当指定index时,如果为大于或等于0的值,它从列表的开始处索引,0代表列表的第一个元素。如果为小于或等于-1的值,它从列表的结尾处索引,-1代表列表的最后一个元素。
使用find_package引入外部依赖包
1、通过Cmake内置模块引入依赖包
为了方便我们在项目中引入外部依赖包,cmake官方为我们预定义了许多寻找依赖包的Module,他们存储在path_to_your_cmake/share/cmake-/Modules目录下。每个以Find.cmake命名的文件都可以帮我们找到一个包。
我们以curl库为例,假设我们项目需要引入这个库,从网站中请求网页到本地,我们看到官方已经定义好了FindCURL.cmake。所以我们在CMakeLists.txt中可以直接用find_pakcage进行引用。
find_package(CURL)
add_executable(curltest curltest.cc)
if(CURL_FOUND)
target_include_directories(clib PRIVATE ${CURL_INCLUDE_DIR})
target_link_libraries(curltest ${CURL_LIBRARY})
else(CURL_FOUND)
message(FATAL_ERROR ”CURL library not found”)
endif(CURL_FOUND)
对于系统预定义的 Find< LibaryName >.cmake 模块,使用方法一般如上例所示。
每一个模块都会定义以下几个变量:
< LibaryName >_FOUND
< LibaryName >_INCLUDE_DIR or < LibaryName >_INCLUDES < LibaryName >_LIBRARY
or < LibaryName >_LIBRARIES
你可以通过< LibaryName >_FOUND 来判断模块是否被找到,如果没有找到,按照工程的需要关闭 某些特性、给出提醒或者中止编译,上面的例子就是报出致命错误并终止构建。 如果
< LibaryName >_FOUND 为真,则将< LibaryName >_INCLUDE_DIR 加入 INCLUDE_DIRECTORIES
2、通过find_package引入非官方的库(该方式只对支持cmake编译安装的库有效)
假设此时我们需要引入glog库来进行日志的记录,我们在Module目录下并没有找到 FindGlog.cmake。所以我们需要自行安装glog库,再进行引用。
安装
# clone该项目
git clone https://github.com/google/glog.git
# 切换到需要的版本
cd glog
git checkout v0.40
# 根据官网的指南进行安装
cmake -H. -Bbuild -G "Unix Makefiles"
cmake --build build
cmake --build build --target install
此时我们便可以通过与引入curl库一样的方式引入glog库了
find_package(GLOG)
add_executable(glogtest glogtest.cc)
if(GLOG_FOUND)
# 由于glog在连接时将头文件直接链接到了库里面,所以这里不用显示调用target_include_directories
target_link_libraries(glogtest glog::glog)
else(GLOG_FOUND)
message(FATAL_ERROR ”GLOG library not found”)
endif(GLOG_FOUND)
对于原生支持Cmake编译和安装的库通常会安装Config模式的配置文件到对应目录,这个配置文件直接配置了头文件库文件的路径以及各种cmake变量供find_package使用。而对于非由cmake编译的项目,我们通常会编写一个Find< LibraryName >.cmake,通过脚本来获取头文件、库文件等信息。通常,原生支持cmake的项目库安装时会拷贝一份XXXConfig.cmake到系统目录中,因此在没有显式指定搜索路径时也可以顺利找到。
include_directories ([AFTER|BEFORE] [SYSTEM] dir1 [dir2 …])
将指定目录添加到编译器的头文件搜索路径之下,指定的目录被解释成当前源码路径的相对路径。
默认情况下,include_directories命令会将目录添加到列表最后,可以通过命令设置CMAKE_INCLUDE_DIRECTORIES_BEFORE变量为ON来改变它默认行为,将目录添加到列表前面。也可以在每次调用include_directories命令时使用AFTER或BEFORE选项来指定是添加到列表的前面或者后面。如果使用SYSTEM选项,会把指定目录当成系统的搜索目录。该命令作用范围只在当前的CMakeLists.txt。
本文主要分析cmake中4个变量的区别:
CMAKE_ARCHIVE_OUTPUT_DIRECTORY:默认存放静态库的文件夹位置;
CMAKE_LIBRARY_OUTPUT_DIRECTORY:默认存放动态库的文件夹位置;
LIBRARY_OUTPUT_PATH:默认存放库文件的位置,如果产生的是静态库并且没有指定CMAKE_ARCHIVE_OUTPUT_DIRECTORY则存放在该目录下,动态库也类似;
CMAKE_RUNTIME_OUTPUT_DIRECTORY:存放可执行软件的目录;
CMAKE_RUNTIME_OUTPUT_DIRECTORY:存放可执行软件的目录
make -j
既然IO不是瓶颈,那CPU就应该是一个影响编译速度的重要因素了。
用make -j带一个参数,可以把项目在进行并行编译,比如在一台双核的机器上,完全可以用make
-j4,让make最多允许4个编译命令同时执行,这样可以更有效的利用CPU资源。
还是用Kernel来测试:
用make: 40分16秒
用make -j4:23分16秒
用make -j8:22分59秒
由此看来,在多核CPU上,适当的进行并行编译还是可以明显提高编译速度的。但并行的任务不宜太多,一般是以CPU的核心数目的两倍为宜。
不过这个方案不是完全没有cost的,如果项目的Makefile不规范,没有正确的设置好依赖关系,并行编译的结果就是编译不能正常进行。如果依赖关系设置过于保守,则可能本身编译的可并行度就下降了,也不能取得最佳的效果。
在if里面变量名不用加¥符号