写在前面
- 这里主要记录一下如何使用GCC、make和CMake编译代码;
一、GCC
-
g++是GCC下专门用于编译C++项目的编译器;
-
假设目录结构如下:
include
:包含分离的.h
和.cpp
文件;src
:包含主函数入口main.cpp
;
- 参考:
- http://c.biancheng.net/view/7936.html;
- gcc/g++常用编译选项和gdb常用调试命令;
1. gcc和g++的区别
-
GCC(GNU Compiler Collection,GNU编译器集合)中,
- 包含了许多编译器,可以编译包括C、C++、Java等语言;
- gcc是GCC中的C编译器;
- g++是GCC中的C++编译器;
-
区别:
- (1) 编译语言:
- gcc只能编译C语言;
- g++可以编译C语言和C++语言;
- (2) 对待
.c
和.cpp
文件的处理:- gcc会分别处理两种文件,如果
.cpp
中只有C语法(最好是仅有用extern "C"
包含的代码),则gcc也是可以编译的,但不建议; - g++会都当作
.cpp
文件处理;
- gcc会分别处理两种文件,如果
- (3) C++链接库:
- gcc不会自动链接C++标准库,需要手动加
-libstdc++
; - g++会自动链接C++标准库;
- gcc不会自动链接C++标准库,需要手动加
- (4) 对C++特有功能的支持:
- gcc不支持编译类、多态等C++特性;
- g++支持C++的所有功能;
- (1) 编译语言:
2. 使用
2.1 直接编译
- 主函数如下:
- 引用的头文件需要显示给出完整的相对路径;
#include <iostream>
#include "../include/add.h"
#include "../include/minus.h"
int main()
{
std::cout << "Hello world!" << std::endl;
std::cout << add(2, 3) << std::endl;
std::cout << minus(2, 3) << std::endl;
return 0;
}
- 则使用G++编译代码如下:
- 所有使用的
.cpp
都需要显式给出; - 所有的
.cpp
都需要给出完整的查找路径;
- 所有使用的
# 进入main.cpp所在的src目录
cd xxx/src
# 指明所有的.cpp的路径
g++ main.cpp ../include/add.cpp ../include/minus.cpp -o main
-
编译的过程如下:
- 将所有的
.cpp
编译成.o
文件; - 通过在系统的头文件搜索路径中搜索和
main.cpp
中的#include
说明,将对应的同名的.o
链接到main
中; - 最后生成输出
main
;
- 将所有的
-
不足:
- (1) 代码
main.cpp
中需要显式给出所有使用库的相对路径;- 一旦修改了库的位置,则源代码也需要对应修改;
- 可以在编译时添加头文件搜索路径来避免这个问题;
- (2) 编译命令中需要显式给出所有使用的
.cpp
文件的相对路径;- 如果使用了许多头文件,则所有涉及到的
.cpp
都需要写,而且相对路径必须给出,所以写法上很繁琐; - 可以使用的
.cpp
文件打包成静态库来避免这个问题;
- 如果使用了许多头文件,则所有涉及到的
- (1) 代码
2.2 使用头文件搜索编译
- 主函数如下:
- 引用的头文件不需要显式给出完整的相对路径;
#include <iostream>
#include "add.h"
#include "minus.h"
int main()
{
std::cout << "Hello world!" << std::endl;
std::cout << add(2, 3) << std::endl;
std::cout << minus(2, 3) << std::endl;
return 0;
}
- 则使用G++编译代码如下:
- 所有使用的
.cpp
都需要显式给出; - 所有的
.cpp
都需要给出完整的查找路径;
- 所有使用的
# 进入main.cpp所在的src目录
cd xxx/src
# -I用于将头文件所在的目录添加到编译器的搜索路径
# -I后面没有空格,直接跟需要添加的头文件搜索路径
g++ -I../include main.cpp ../include/add.cpp ../include/minus.cpp -o main
- 编译的过程如下:
- 将所有的
.cpp
编译成.o
文件; - 通过在系统的头文件搜索路径和添加的头文件搜索目录中搜索,将对应的同名的
.o
链接到main
中; - 最后生成输出
main
;
- 将所有的
2.3 使用静态库编译
- 首先在
include
文件夹中将.cpp
文件编译成一个静态库;
# 进入include文件夹
cd xxx/include
# 为每个.cpp文件编译.o文件
g++ -c add.cpp minus.cpp
# 打包成静态库libmath.a
ar rcs libmath.a add.o minus.o
- 然后就可以在编译时使用静态库了:
# 进入main.cpp所在的src目录
cd xxx/src
# 用静态库代替.cpp文件
g++ -I../include main.cpp ../include/libmath.a -o main
- 注意:
- 静态库中仅包含
.cpp
文件; - 也就是说,如果要令整个库可以迁移的话,还是要加上头文件一起打包的;
- 头文件是做指引,静态库(或者说
.o
文件)是做实际的链接,它们的功能是互斥的;
- 静态库中仅包含
2.4 设置构建级别编译
- 默认使用
RELEASE
级别构建; - 添加
-g
参数可以使用DEBUG
级别构建;- 如果需要使用gdb调试则需要用该级别构建;
- 此外,还有代码的优化级别,可以搭配
RELEASE
级别或者DEBUG
级别一起使用,说明如下:
# 进入main.cpp所在的src目录
cd xxx/src
# 指明所有的.cpp的路径
g++ -g main.cpp ../include/add.cpp ../include/minus.cpp -o main
二、make
三、CMake
- 参考:
- CMake 入门实战;
- CMake 输出路径的设置 ,当前目录,安装拷贝 集合;
- cmake-examples-Chinese;
1. 安装
sudo apt install cmake
2. 使用
- 一个
CMakeList.txt
的例子如下:- 一些内置变量的说明可以参考官方文档:https://cmake.org/cmake/help/latest/manual/cmake-variables.7.html;
# CMake 最低版本号要求
cmake_minimum_required(VERSION 2.8)
# 项目信息
project(项目名)
# 设置静态库文件目录
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib)
file(MAKE_DIRECTORY ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY})
# 设置动态库文件目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib)
file(MAKE_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
# 设置可执行文件目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin)
file(MAKE_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
# 设置中间文件目录
set(CMAKE_CURRENT_BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/build)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
# 设置构建为调试模式
set(CMAKE_BUILD_TYPE Debug)
# 根据编译器信息设置调试信息
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
endif()
# 查找当前目录下的所有源文件,并把名称保存在DIR_SRCS变量
aux_source_directory(. DIR_SRCS)
# 指定main源文件和可执行文件名,需要放在最后,否则前面的设置不会生效
# add_executable(可执行文件名 cpp文件1 cpp文件2 ...)
add_executable(可执行文件名 ${DIR_SRCS})
- 使用的过程如下:
# 在CMakeList.txt目录下新建一个build文件夹
mkdir build
# 进入该文件夹
cd build
# 调用CMakeList.txt在当前目录下生成makefile及其他中间文件
cmake ..
# 执行make生成可执行文件
make
2.1 使用头文件搜索编译
- 项目结构如下:
- 主函数如下:
- 引用的头文件不需要显式给出完整的相对路径;
#include <iostream>
#include "add.h"
#include "minus.h"
int main()
{
std::cout << "Hello world!" << std::endl;
std::cout << add(2, 3) << std::endl;
std::cout << minus(2, 3) << std::endl;
return 0;
}
- CMakeLists.txt如下:
cmake_minimum_required(VERSION 3.5)
# 项目名称
project(cmake_project)
# 创建一个变量,包含所有的.cpp文件
set(SOURCES
src/main.cpp
include/add.cpp
include/minus.cpp
)
# 生成可执行文件
add_executable(${PROJECT_NAME} ${SOURCES})
# 设置包含库路径
target_include_directories(${PROJECT_NAME}
PRIVATE
${PROJECT_SOURCE_DIR}/include
)
- 一些说明:
project(cmake_project)
表明当前项目名,即设置了${PROJECT_NAME}=cmake_project
;target_include_directories
为当前项目设置包含库路径,所以后面跟的第一个参数是项目的名字;- 一些可用的CMake变量说明如下:
2.2 创建静态库并使用静态库编译
- 创建静态库的CMakeLists.txt如下:
cmake_minimum_required(VERSION 3.5)
# 项目名称
project(cmake_project)
# 创建一个变量,包含静态库的所有.cpp文件
set(SOURCES
include/add.cpp
include/minus.cpp
)
# 生成静态库
add_library(${PROJECT_NAME} ${SOURCES})
# 设置包含库路径
target_include_directories(${PROJECT_NAME}
PUBLIC
${PROJECT_SOURCE_DIR}/include
)
-
说明:
- 其实和创建可执行文件非常像,仅修改为使用
add_library
,就可以不创建目标文件而改为创建静态库; - 调用
cmake
的方式也和创建可执行文件相同; - 创建后的静态库也是在build文件夹中,以
.a
为后缀;
- 其实和创建可执行文件非常像,仅修改为使用
-
使用静态库进行编译的CMakeLists.txt如下:
cmake_minimum_required(VERSION 3.5)
# 项目名称
project(cmake_project)
# 创建一个变量,包含静态库的所有.cpp文件
set(SOURCES
src/main.cpp
)
# 生成静态库
add_executable(${PROJECT_NAME} ${SOURCES})
# 设置包含库路径
target_include_directories(${PROJECT_NAME}
PUBLIC
${PROJECT_SOURCE_DIR}/include
)
# 设置包含静态库路径
target_link_libraries(${PROJECT_NAME}
PUBLIC
${PROJECT_SOURCE_DIR}/include/libcmake_project.a
)
- 说明:
- 即使是使用静态库,头文件还是要加上的;
- 因为静态库只是
.cpp
文件的集合,这个在GCC一章中也有说到; - 另外,关于
PUBLIC
、PRIVATE
和INTERFACE
的说明如下:
2.3 设置构建级别编译
-
这里的构建模式是指
DEBUG
和RELEASE
之类的模式,如下:- (1)
Debug
模式:- 用于开发和调试目的的构建级别;
- 包含调试符号和较少优化;
- 如果需要使用gdb调试的话,则需要使用该级别构建;
- (2)
RelWithDebInfo
:- 包含优化和调试信息的构建级别;
- 适合进行性能分析和调试;
- (3)
Release
模式(默认):- 用于发布和部署目的的构建级别;
- 几乎不包含调试符号和较多的优化;
- 使用该级别不能进行gdb调试;
- (4)
MinSizeRel
:- 最小体积和最大程度优化的构建级别;
- (1)
-
设置构建级别的CMakeLists.txt如下:
cmake_minimum_required(VERSION 3.5)
# 项目名称
project(cmake_project)
# 创建一个变量,包含静态库的所有.cpp文件
set(SOURCES
src/main.cpp
)
# 设置构建为调试模式
set(CMAKE_BUILD_TYPE Debug)
# 生成静态库
add_executable(${PROJECT_NAME} ${SOURCES})
# 设置包含库路径
target_include_directories(${PROJECT_NAME}
PUBLIC
${PROJECT_SOURCE_DIR}/include
)
# 设置包含静态库路径
target_link_libraries(${PROJECT_NAME}
PUBLIC
${PROJECT_SOURCE_DIR}/include/libcmake_project.a
)
- 说明:
- 通过设置
CMAKE_BUILD_TYPE
变量即可更改调试模式;
- 通过设置
3. 错误
3.1 CMake Error: The source “/home/jeremy_wsl/projects/CMakeProject/CMakeLists.txt” does not match the source “/home/jeremy_wsl/projects/CMakeProject/src/CMakeLists.txt” used to generate cache. Re-run cmake with a different source directory.
-
原因:
- 当前的源码目录与之前生成缓存时使用的源码目录不匹配;
- 更改项目目录结构或者移动 CMakeLists.txt 文件后继续使用之前的缓存文件;
-
解决方法:
- 删除bulid文件夹下的所有文件;
- 重新
cmake
即可;