官方网站
GCC(GNU Compiler Collection,GNU编译程序集合)是最重要的开放源码软件。其他所有开放源码软件都在某种层次上依赖于它。甚至其他语言,例如 Python,都是由 C 语言开发的,由 GNU 编译程序编译的。
源文件.c文件 -> 预编译成.i文件 -> 编译成汇编语言.s -> 汇编成.o文件 -> 链接成可执行文件
.c
文件.i
文件。.s
文件.o
。举个例子
以hello.c
为例
可执行程序
预处理
# 不会生成 .i 文件
# -E 选项告诉编译器只进行预处理操作
# -o 选项把预处理的结果输出到指定文件
gcc -E main.c
gcc -E main.c -o helloworld.i
生成汇编语言
# -S 选项告诉编译器,进行预处理和编译成汇编语言操作
gcc -S main.c
gcc -S main.c -o xxx.s
生成目标对象文件
gcc -c main.c
gcc -c main.c -o xxx.o
# 编译多个 .c 文件,同样输出多个 .o 文件
gcc -c main.c add.c minus.c
单个文件编译为可执行程序
gcc main.c
gcc main.c -o xxx
单个xxx
就是可以执行文件了,可以通过./xxx
运行
多个文件编译为可执行程序
gcc main.c add.c minus.c -o exec
./exec
静态库和动态库,封装功能函数
编译为可链接的目标对象文件**.o**
gcc -c [.c] -o [功能函数]
gcc -c [.c] [.c] ...
假设有三个文件 main.c
, add.c
,minus.c
,其中后两个为功能函数。
gcc -c main.c add.c minus.c
那么编译后得到三个对象文件main.o
, add.o
,minus.o
编译为静态库
ar -r [lib自定义库名.a] [.o] [.o] ...
将两个功能函数编为静态库
ar -r liboperation.a add.o minus.o
得到静态库liboperation.a
链接为可执行文件
gcc [.c] [.a] -o [自定义输出文件名]
gcc [.c] -o [自定义输出文件名] -l[库名] -L[库所在路径]
链接 main 函数和静态库得到可执行程序
gcc main.c liboperation.a -o exec
也可以链接三个对象文件变为可执行程序
gcc main.o add.o minus.o -o exec
二进制对象文件
gcc -c -fpic [.c/.cpp][.c/.cpp]...
库
gcc -shared [.o][.o]... -o [lib自定义库名.so]
也可以直接编成库文件
gcc -shared [.o文件] [.o文件] [...] -o lib[库名].so
链接为可执行文件
gcc [.c/.cpp] -o [自定义可执行文件名] -l[库名] -L[库路径] -Wl,-rpath=[库路径]
预处理
和 c 类似,不过用的是 g++
#-E 选项告诉编译器只进行预处理操作
#-o 选项把预处理的结果输出到指定文件
g++ -E helloworld.c
g++ -E helloworld.c -o helloworld.i
生成汇编
#-S 选项告诉编译器,进行预处理和编译成汇编语言操作
g++ -S helloworld.c
g++ -S helloworld.c -o helloworld.s
生成二进制对象文件
g++ -c helloworld.c
g++ -c helloworld.c -o harumph.o
# 编译多个 .c 文件
g++ -c helloworld.c helloworld1.c helloworld2.c
编译单个源文件为可执行文件
g++ helloworld.c
g++ helloworld.c -o howdy
编译多个源文件为可执行文件
g++ hellomain.c sayhello.c -o hello
静态库和动态库,封装功能函数
编译为可链接的目标对象文件**.o**
g++ -c [.c] -o [功能函数]
g++ -c [.c] [.c] ...
假设有三个文件 main.c
, add.c
,minus.c
,其中后两个为功能函数。
g++ -c main.c add.c minus.c
那么编译后得到三个对象文件main.o
, add.o
,minus.o
编译为静态库
ar -r [lib自定义库名.a] [.o] [.o] ...
将两个功能函数编为静态库
ar -r liboperation.a add.o minus.o
得到静态库liboperation.a
链接为可执行文件
g++ [.c] [.a] -o [自定义输出文件名]
gcc [.c] -o [自定义输出文件名] -l[库名] -L[库所在路径]
链接 main 函数和静态库得到可执行程序
g++ main.c liboperation.a -o exec
也可以链接三个对象文件变为可执行程序
g++ main.o add.o minus.o -o exec
二进制对象文件
g++ -c -fpic [.c/.cpp][.c/.cpp]...
库
g++ -shared [.o][.o]... -o [lib自定义库名.so]
也可以直接编成库文件
g++ -shared [.o文件] [.o文件] [...] -o lib[库名].so
链接为可执行文件
g++ [.c/.cpp] -o [自定义可执行文件名] -l[库名] -L[库路径] -Wl,-rpath=[库路径]
make && makefile:BTW,如果源文件非常之多,一个个编译会很麻烦,所以设计出了类似批处理的程序来批量编译源文件,这就是自动化的编译工具 make,它需要使用到一个规则文件 Makefile 来辅助自己按照规则编译。make 工具没有编译和链接功能,而是通过批处理的方式调用 Makefile 文件里存储的用户指定命令来进行编译和链接。
cmake && CMakeLists.txt:那么假设遇到很大的工程,编写 Makefile 文件也会很吃力,于是又诞生了能够自动生成makefile或者project(如Visual studio下的vcproj文件,被隐藏)的工具 cmake ,它可以读入所有源文件然后输出各种各样的makefile文件。同样需要遵守一定的规则,这就是 CMakeLists.txt 文件。值得注意的是,cmake,是可以跨平台生成对应平台的 Makefile 的。
流程如下:
老牌的自动化构建和编译项目工具,使用名为 Makefile 的文件来定义构建规则。根据文件的依赖关系和时间戳来确定哪些文件需要重新构建,以确保构建是有效的。灵活且功能丰富,适用于各种项目,特别是在Unix/Linux环境下。
大多数IDE都集成了make,比如:VS 的 nmake、linux 下的 GNU make、Qt 的 qmake等。
常用的命令
make
:运行 make 命令时,它将查找当前目录中的 Makefile 文件并执行默认的构建任务。通常,make 命令不需要参数。make
:运行 make 命令时,你可以指定要构建的目标。例如,make all
或 make myprogram
将构建名为 “all” 或 “myprogram” 的目标。这些目标需要在 Makefile 中定义。make -f
:使用 -f
选项可以指定一个不同的 Makefile 文件的名称,而不使用默认的 Makefile。make clean
:通常,项目的 Makefile 中会包含一个 clean 目标,用于清除生成的中间文件和可执行文件。运行 make clean
将执行清理操作。make install
:如果 Makefile 中包含一个 install 目标,运行 make install 可以安装项目的可执行文件或其他文件到系统中的指定位置。make uninstall
:类似于 install,如果 Makefile 包含一个 uninstall 目标,运行 make uninstall
可以卸载先前安装的文件。make =
:你可以通过命令行覆盖 Makefile 中的变量的值。例如,make CFLAGS="-O2"
将覆盖 CFLAGS 变量的值为 “-O2”。make -n
:使用-n
选项可以进行模拟构建,而不实际执行命令。这可以帮助你查看 make 将要执行的命令,但不会真正运行它们基本结构
每个Makefile包含一个或者多个目标
targets : prerequisties
[tab键]command # 必须是tab键
#或者
targets : prerequisites ; command
target
:目标文件,可以是 OjectFile,也可以是执行文件,还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。prerequisite
:要生成那个 target 所需要的依赖文件。command
:是 make 需要执行的命令运行规则
Makefile
或 makefile
的文件 .o
文件(prerequities)的文件修改时间要比 target 这个文件新,就会执行后面所定义的命令 command 来生成 target 这个文件.o
文件(prerequisties)也存在,make 会在当前文件中找到 target 为 .o
文件的依赖性,如果找到,再根据那个规则生成 .o
文件伪目标
有时候我们设置一个目标,并不是真正生成这个文件,通常用于执行特殊操作,如clean、install等。它们的目的是告诉 make 哪些操作不会产生与文件相关的输出。
避免 target 和 Makefile 同级目录下 文件/文件夹 重名的这种情况,可以使用一个特殊的标记 .PHONY
来显式地指明一个目标是 “伪目标”,向 make 说明,不管是否有这个文件/文件夹,这个目标就是 “伪目标”.PHONY : clean
只要有这个声明,不管是否有 “clean” 文件/文件夹,要运行 “clean” 这个目标。
如,下面的clean 就是删除 myprogram,而不是真的生成clean 介个文件。
.PHONY: clean
clean:
rm -f *.o myprogram
# 如果不想显示shell指令,可以加@进行掩盖
即 rm -f *.o myprogram
声明时需要赋初值,使用时需要加$
然后用()
包起来
定义
cpp := src/main.cpp
obj := objs/main.o
引用
使用$
+()
或者{}
cpp := src/main.cpp
obj := objs/main.o
$(obj) : ${cpp}
@g++ -c $(cpp) -o $(obj)
compile : $(obj)
预定义变量
$@
: 目标(target)的完整名称$<
: 第一个依赖文件(prerequisties)的名称$^
: 所有的依赖文件(prerequisties),以空格分开,不包含重复的依赖文件cpp := src/main.cpp
obj := objs/main.o
$(obj) : ${cpp}
@g++ -c $< -o $@
@echo $^
compile : $(obj)
.PHONY : compile
=
:类似一般编程语言的赋值符号
:=
:定义变量的时候立即求值,然后值不可更改
?=
:如果变量已经定义就不进行任何操作,否则就进行赋值
+=
:就是一般编程语言里面的,右边的加到左边。
\
:一行写不完,可以用这个继续写
*
:匹配任意字符串,用于目录名或者文件名%
:匹配任意字符串,并将字符串作为变量使用
函数调用和变量使用类似,用$
标识,语法如下,$(fn, arguments) or ${fn, arguments}
fn
:函数名arguments
:函数参数
usage:$(shell
返回 shell 命令的执行结果。
usage:$(subst
返回替换后的新字符串,text 中的 old 替换为 new
usage:$(patsubst
从 text 中取出 pattern 替换为 replacement。可用任意长度字符串通配符 %
示例:
cpp_objs := $(patsubst %.cpp,%.o,$(cpp_srcs))
#cpp_srcs变量下cpp文件替换成 .o文件
usage:$(foreach ,
,
把 list 的每个元素取出来 执行 text 的表达式,返回值是每次表达式返回结果的整个字符串,以空格分开。
usage:$(dir
返回文件名序列的目录部分
usage:$(notdir
usage:$(filter
# 找到所有lib开头的文件,返回文件名
libs := $(notdir $(shell find /usr/lib -name lib*))
# 取出后缀为.a的静态库
a_libs := $(filter %.a,$(libs))
# 取出后缀为.so的动态库
so_libs := $(filter %.so,$(libs))
usage:$(basename
不能使用Tab 缩进,只能用空格
ifeq ($(a),$(b))
else ifeq ()
else
endif
ifneq ($(a),$(b))
else ifneq ()
else
endif
ifdef
endif
编译选项
-m64
: 指定编译为 64 位应用程序-std=
: 指定编译标准,例如:-std=c++11、-std=c++14-g
: 包含调试信息-w
: 不显示警告-O
: 优化等级,通常使用:-O3-I
: 加在头文件路径前fPIC
: (Position-Independent Code), 产生的没有绝对地址,全部使用相对地址,代码可以被加载到内存的任意位置,且可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的链接选项
-l
: 加在库名前面-L
: 加在库路径前面-Wl,<选项>
: 将逗号分隔的 <选项> 传递给链接器-rpath=
: “运行” 的时候,去找的目录。运行的时候,要找 .so 文件,会从这个选项里指定的地方去找比较新的构建工具和 make 类似, 使用声明式的构建规则,它不需要像 **Makefile **那样冗长的文本文件,而是使用类似于 JSON 的格式。
它可以快速并行构建和最小化构建系统开销,某些情况下比 Make 快。适用于需要快速构建的大型项目。
在某些情况下,还可以将两者结合使用,使用 Make 作为高级构建系统的前端,而底层的构建任务则由 Ninja 执行,以兼顾灵活性和性能。
对比Makefile ,Ninja的规则文件名为xx.ninja
。
目标语法:
build :
target
:要构建的目标文件名。rule
:构建规则名称。dependencies
: 目标文件依赖列表default # 可以用default关键字指定默认构建目标
规则语法
Rules:定义如何构建目标,由名称,命令和可选属性组成
rule
command =
description =
variable = value # 等号赋值
调用变量使用@
一个综合例子
CFLAGS = -Wall -O2
rule cc
command = gcc $CFLAGS -o $out $in
description = CC $out
build main.o: cc main.c
跨平台的项目构建工具,可以根据配置生成不同构建系统的构建规则包括 **Makefile **和 ninja 等等。配置文件为CMakeLists.txt
默认使用的是 **make **工具,要使用 **Ninja **构建 ninja 规则可以使用-G
选项
cmake -G Ninja /path/to/source
CMakeLists.txt
文件cmake_minimum_required(VERSION 3.0)
project(test)
add_executable(app add.cpp main.cpp minus.cpp)
#也可以 add_executable(app add.cpp;main.cpp;minus.cpp)
cmake_minimum_required
:Cmake 最低版本声明,可选但不加可能会有警告。project
:工程名,可指定工程的版本、工程描述、web主页地址、支持的语言(默认情况支持所有语言)# PROJECT 指令的语法是:
project( [...])
project(
[VERSION [.[.[.]]]]
[DESCRIPTION ]
[HOMEPAGE_URL ]
[LANGUAGES ...])
add_executable
:定义生成一个可执行程序add_executable(可执行程序名 源文件名称)
cmake
命令格式为cmake
+CMakeLists.txt
所在的路径。如果就在当前目录的话就可以执行cmake .
,执行后会产生很多文件,其中就有Makefile
make
即可# 行注释
#[[
块注释
]]
定义
# [] 中的参数为可选项, 如不需要可以不写
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
使用:${}
set(src add.c;div.c;main.c;mult.c;sub.c)
add_executable(app ${src})
#增加-std=c++11
set(CMAKE_CXX_STANDARD 11)
#增加-std=c++14
set(CMAKE_CXX_STANDARD 14)
#增加-std=c++17
set(CMAKE_CXX_STANDARD 17)
使用宏EXECUTABLE_OUTPUT_PATH
set(HOME /home/xxx/test)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin)
#也可以 set(EXECUTABLE_OUTPUT_PATH ./bin)
如果目录不存在会自动创建。
And,如果这里指定的是相对路径,那么./
对应的就是生成的Makefile
的目录。
include_directories(${PROJECT_SOURCE_DIR}/include)
PROJECT_SOURCE_DIR
:项目的根目录方法一
aux_source_directory(< dir > < variable >)
查找dir
下的所有文件并存储到variable
变量中
# 搜索 src 目录下的源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src src)
CMAKE_CURRENT_SOURCE_DIR
: 宏表示当前访问的CMakeLists.txt 文件所在的路径。方法二
当目录很多的时候,就可以使用搜索文件的指令
file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
GLOB
: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。GLOB_RECURSE
:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中举例
file(GLOB/GLOB_RECURSE src "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
项目结构
~/test$ tree
.
├── build
├── CMakeLists.txt
├── include
│ └── head.h
└── src
├── add.cpp
├── main.cpp
└── minus.cpp
CMakeLists.txt文件
# 设定cmake版本(可选)
cmake_minimum_required(VERSION 3.0)
# 设定项目名称
project(test)
# 设定编译器选项
set(CMAKE_CXX_STANDARD 11)
# 设定可行行文件输出路径
set(EXECUTABLE_OUTPUT_PATH ./bin)
# 包含头文件
include_directories(${PROJECT_SOURCE_DIR}/include)
# 设定源文件
file(GLOB src "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 添加可执行文件,生成一个可执行文件
add_executable(test ${src})
add_library(库名称 STATIC 源文件1 [源文件2] ...)
前面提到静态库的命名lib自定义库名.a
。这里的库名称只需要提供中间的自定义库名就行。其他会自动填充。
add_library(库名称 SHARED 源文件1 [源文件2] ...)
和静态库一样,只需要指定名字。
方法一
由于linux下有执行权限问题,动态库和可执行文件一样具有执行权限。所以可以使用一样的指定方法
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(test SHARED ${src})
方法二
静态库不具有执行权限,不能用同一个宏,得用LIBRARY_OUTPUT_PATH
。
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
#add_library(calc SHARED ${src})
# 生成静态库
add_library(calc STATIC ${src})
link_libraries( [...])
可以全名也可以取中间的名称部分(去lib
和.a
)
非系统提供的库需要指定路径。
link_directories()
综合例子
# 设定cmake版本(可选)
cmake_minimum_required(VERSION 3.0)
# 设定项目名称
project(test)
# 设定编译器选项
set(CMAKE_CXX_STANDARD 11)
# 包含头文件
include_directories(${PROJECT_SOURCE_DIR}/include)
# 设定库文件输出路径
set(LIBRARY_OUTPUT_PATH ./lib)
# 设定可行行文件输出路径
set(EXECUTABLE_OUTPUT_PATH ./bin)
# 设定静态库路径
link_directories(${LIBRARY_OUTPUT_PATH})
# 设定源文件
file(GLOB lib "${CMAKE_CURRENT_SOURCE_DIR}/func/*.cpp")
file(GLOB exec "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 添加可执行文件,生成一个可执行文件
add_library(calc STATIC ${lib})
link_libraries(calc)
add_executable(test ${exec})
target_link_libraries(
- ...
[
- ...]...)
target
:要加载动态库的目标文件,可以是动态库,源文件或者可执行文件。PRIVATE|PUBLIC|INTERFACE
:文件的访问权限。
PUBLIC
:public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。PRIVATE
:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知调了啥库INTERFACE
:在interface后面引入的库不会被链接到前面的target中,只会导出符号。默认PUBLIC。
item
:动态库名,一般指定中间名字就行。另外,动态库链接具有传递性,下面的代码中 D库也可以调用 B和C 库。
target_link_libraries(A B C)
target_link_libraries(D A)
AND,由于动态库链接阶段是不会被打包到可执行文件中,而是等文件执行的时候才开始调用,所以调用动态库的语句需要写到可执行文件生成之后。也就是
add_executable(test ${src})
target_link_libraries(test calc)
综合例子
# 设定cmake版本(可选)
cmake_minimum_required(VERSION 3.0)
# 设定项目名称
project(test)
# 设定编译器选项
set(CMAKE_CXX_STANDARD 11)
# 包含头文件
include_directories(${PROJECT_SOURCE_DIR}/include)
# 设定库文件输出路径
set(LIBRARY_OUTPUT_PATH ./lib)
# 设定可行行文件输出路径
set(EXECUTABLE_OUTPUT_PATH ./bin)
# 设定静态库路径
link_directories(${LIBRARY_OUTPUT_PATH})
# 设定源文件
file(GLOB lib "${CMAKE_CURRENT_SOURCE_DIR}/func/*.cpp")
file(GLOB exec "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc SHARED ${lib})
# 添加可执行文件,生成一个可执行文件
add_executable(test ${exec})
# 链接动态库
target_link_libraries(test calc)
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 错误, 终止所有处理过程CMake的命令行工具会在 stdout 上显示 STATUS
消息,在 stderr上显示其他所有消息。CMake的GUI会在它的 log 区域显示所有消息。
如果源文件不在同一个目录里,可能需要用到拼接
set
拼接set(变量名1 ${变量名1} ${变量名2} ...)
list
拼接list(APPEND [ ...])
list
功能很多,通过第一个功能参数来设置,如APPEND
就
是指字符串拼接。
list(REMOVE_ITEM [ ...])
还是list
,把功能参数换为REMOVE_ITEM
即可
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
会使得文件很复杂庞大,不易维护。那么可以对每个源文件目录都写上一个CMakeLists.txt
,来便于管理。
嵌套的CMake和Linux目录一样都是树状结构,有根节点,父节点,子节点。其中的变量关系满足,
CMakeLists.txt
中的变量全局有效CMakeLists.txt
中的变量可以在子节点中使用CMakeLists.txt
中的变量只能在当前节点中使用CMakeLists.txt
之间建立联系通过
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
source_dir
:子节点的目录。binary_dir
:指定输出文件的路径,一般无需。EXCLUDE_FROM_ALL
:不需要被包含的文件基础逻辑
NOT
:AND
:OR
:数值比较逻辑
LESS
:GREATER
:EQUAL
:LESS_EQUAL
:GREATER_EQUAL
:字符串比较逻辑
STRLESS
:STRGREATER
:STREQUAL
:STRLESS_EQUAL
:STRGREATER_EQUAL
:文件判断
EXISTS
:文件是否存在,后面跟绝对路径IS_DIRECTORY
:是否是目录,后面跟绝对路径IS_SYMLINK
:是否软连接IS_ABSOLUTE
:是否绝对路径其他判定逻辑
IN_LIST
:判定某个元素是否存在于列表中PATH_EQUAL
:判定路径是否相同if()
elseif()
else()
endif() # 必须要有
条件判定的时候,
1
, ON
, YES
, TRUE
, Y
, 非零值
,非空字符串
时,条件判断返回 True
0
, OFF
, NO
, FALSE
, N
, IGNORE
, NOTFOUND
,空字符串
时,条件判断返回 False
**foreach**
foreach( RANGE )
foreach( RANGE [])
loop_var
:存储每次循环取出的值stop
:表示从0开始,最大值为 stop,左右闭区间。start,stop
:也可以指定范围,左右闭区间。step
:可以指定跨度。foreach( IN [LISTS []] [ITEMS []])
# 遍历多个列表
foreach(... IN ZIP_LISTS )
LIST
:对应列表list
,可以从set
,list
命令中获得**while**
while()
endwhile()
5分钟理解make/makefile/cmake/nmake
GNC-Tutorial
CMake 保姆级教程