C++开发环境构建

C++的开发环境,目前常用的IDE是vscode。 macOS安装 开发工具(例如xcode、commondlinetools) 之后,其包含了c++的标准库。
通过环境变量等方法正确的引入标准库以及三方库 即可实现 基于vscode开发 c++的项目。

然而,环境搭建的过程中,重要的是:

  • 对于编译器、构建工具、库及其管理 的理解、对C++版本的理解。
  • 如何使用 vscode、clang++、make、cmake 开发和构建 c++的项目;如何debug调试;以及理解这些工具和过程的原理。
  • 可能会遇到哪些常见的问题? 怎么解决、以及怎么持续深入学习c++的项目。

1.编译器

常见的编译器有:GCC、Clang、Microsoft Visual C++等等。
clang更加现代化、模块化,编译速度更快,适合大型项目

许多编译器即支持c也支持c++,但对于c++的一些新特性和复杂的语法结构,编译器的优化和处理可能有所不同。

1.1 编译和运行

最简单的C程序

1
2
3
4
5
6
# include<stdio.h>
int main()
{
printf("This is a C program.\n");
return 0;
}

编译与运行

1
2
3
4
5
gcc helloworld.c -o helloworld
# 或者 clang example.c -o example
./helloworld
# 输出结果
This is a C program.

2. c++版本/clang版本

不同来源版本的clang可能存在差异
mac系统自带的Clang编译器不支持OpenMP,需要手动安装Clang(基于llvm),即 brew install llvm.
然后配置环境变量,后续所有开发场景都可以使用此版本的clang和基于llvm的工具。

clang++版本

这些编译工具(clang/g++) 路径在xcode或者commondlinetools 路径下.

1.怎么理解c++的版本?

2.clang的版本和c++版本的关系?

3.clang版本的区别?和make有兼容性问题吗?

4.怎么查看c++的库路径和版本信息?
mac系统库路径一般在:/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib

1
2
3
libc++.1.tbd
libc++.tbd
libc++abi.tbd

.tbd 是苹果操作系统的文件格式,用于描述动态库的符号信息。以上3个是C++标准库的实现
linux中呢?

3.库和库管理

c++相关的库 一般在开发工具的环境路径/目录(/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib)、
以及 系统的基础lib路径(/usr/lib、/usr/local/lib/)下。 例如mac中,在xcode、Command Line Tools 工具的路径下。
开发环境、运行时环境项目 会从这些路径下找 需要的库内容。 「c++主要涉及 头文件(include)、库文件(lib)路径」

3.1 库

c++的库.

3.1.1 C++标准库

提供了一些列重要的模块。
(容器、算法、迭代器、字符串、输入输出、智能指针、线程、日期和时间、类型特性、元编程、异常处理等)

3.1.2 STL和Boost

STL 和 Boost 是 C++ 开发中两个重要的库。
STL(标准模板库)是 C++ 标准库的一部分
Boost 是一个大型的开源 C++ 库集合,它提供了许多扩展功能和库,这些库在功能和性能上都经过了严格的测试和验证。Boost 的设计目的是为 C++ 开发者提供可重用的组件,许多 Boost 库的设计理念和接口最终被采纳到 C++ 标准库中。

STL 是 C++ 开发的基础库,而 Boost 则是 C++ 开发的高级库,提供了更多扩展和增强功能。两者在 C++ 编程中各有其重要作用,Boost 库中的许多功能也为 C++ 标准库的演进做出了贡献。

3.2 库管理

介绍下 通过环境变量 正确、方便的 引入开发环境/项目。
另外介绍下 常见的安装的软件的 开发相关的库。

项目是如果找到 这些库的呢?
以c++库和头文件为例:

  1. 当前目录、当前项目
  2. 编译时指定的头文件目录(有 -I -L 参数指定)
  3. 系统环境变量 CPLUS_INCLUDE_PATH 或 C_INCLUDE_PATH 指定的目录
  4. gcc默认目录: /usr/include;/usr/local/include;等等.(各系统平台可能会有不同)

3.2.1 环境变量

和java同理,要用这些 不同路径下的 库和头文件,可能会涉及 环境变量的配置。 或者ide环境的配置。
主要目的是通过环境变量 正确、方便的 将需要的库引入开发环境/项目。
(有些ide中直接配置即可,不用配置环境变量;但通过环境变量配置可能后续使用起来更方便些)

常见的库路径如下:

来源 说明 路径
CommandLineTools CommandLineTools /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include
XCode XCode /Applications/Xcode.app/Contents/Developer/Platforms/ MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib
系统库 通常是系统自带的库文件(属于系统核心部分) /usr/lib/
系统(手动安装) 类Unix系统(如macOS/Linux)中常用的目录,存放本地安装的库文件(手动或第三方软件安装) /usr/local/lib/
本地头文件 类Unix系统(如macOS/Linux)中常用的目录,存放本地安装的库文件(手动或第三方软件安装) /usr/local/include/

当手动安装或者下载的第三方的软件的路在别的指定路径下,需要在某个项目中使用时。 相关的库和头文件 路径可以通过 环境变量的配置引入项目。 例如:

1
2
3
export CPATH=$CPATH:/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include

3.2.2 CommandLineTools和XCode

这两个都是mac上的开发工具。 CommandLineTools 是轻量级的,主要提供了命令行开发工具; XCode是功能强大的集成开发环境(IDE).
如果是iOS、macOS、watchOS和tvOS的开发,可以安装XCode; 其它情况,建议安装CommandLineTools即可(仅几百M)

类型 CommandLineTools XCode
安装包大小 几百M 几个GB,甚至10多GB
用途 主要提供命令行工具,如编译器(Clang、GCC等)、链接器、调试器(LLDB)和其他基本的开发工具 功能强大的集成开发环境(IDE),代码编辑、调试、界面设计、版本控制等;模拟器、性能分析、测试等高级功能
使用场景 常用于 脚本编写、小型项目开发、自动化构建等 适合专业的IOS和macOS应用程序开发,以及需要图形界面设计和高级开发功能的项目
更新方式 通过命令行工具(如xcode-select —install) 通过Mac App Store进行更新,需要较长时间

一些c++的项目,需要的 三方库和头文件,可以通过引入 CommandLineTools 下的

3.2.3 llvm和clang

1.什么是LLVM?
LLVM(Low Level Virtual Machine) 是一个开源的 编译器基础设施项目。

2.包含的工具集?

  • 包括Clang编译器,它是一个基于LLVM的C、C++和Objective-C 编译器。
  • LLDB调试器,用于调试程序。
  • 其他工具如lli(LLVM解释器)、llvm-link(链接器)等

3.有什么特点和优势?

  • 可扩展性、性能优化、跨平台、开源和活跃的社区。

4.能做什么?

  • 编译器开发
  • 代码优化
  • 程序分析和调试
  • 跨平台开发
  • 工具和库(Clang、LLDB、Polly)

安装和查看

1
2
3
4
5
6
7
8
9
10
11
# mac默认的一般是 基于xcode中的。 建议自己安装
brew install llvm

# 查看
brew info llvm

# 配置环境变量
export PATH="/usr/local/opt/llvm/bin:$PATH"
## 这样 clang也是基于llvm的。 而不是基于xcode的clang 15.0.0版本
## 实质的 安装路径在:/usr/local/Cellar/llvm/ /usr/local/opt/llvm/ 是链接过去的
## 不同版本的macOS系统,homebrew 安装这种三方工具的 路径不太一样(新版系统 在/opt/homebrew/...)

3.2.4 GCC和GDB

重点:谈到 llvm和clang, 同样的介绍下 GCC和GDB。

  1. clang是LLVM项目的一部分, 是一个开源的编译器前端。
  2. 而gcc 是GUN Compiler Collection的缩写,是一个成熟的开源编译器集合
  3. 使用 Clang 还是 GCC?如果 Clang 能够完全满足你的编译需求, 并且不依赖GCC特有的功能,那么可以仅使用clang。
    如果老项目基于GCC,可能需要同时使用gcc和clang。

GDB 类似 lldb 主要用于调试(生成调试信息)。

两者的比较:
LLVM 和 Clang 在设计上较为现代,提供了优秀的错误和警告信息、更快的编译速度、更好的 C++ 标准支持和先进的静态分析工具。
GCC 依然是一个成熟、稳定且功能强大的编译器,具有广泛的语言和平台支持,适合很多传统和生产环境。

4.构建

构建工具 类似java的 maven,用于较大型项目的构建。常见的构建工具包括:make、cmake、bazel。

  • make是传统的构建工具,简单高效,适合小到中型项目、在Unix和类Unix系统上使用。
  • cmake是跨平台(win/linux/macOS/android等)的构建工具,广泛用于C和C++项目。 通过「CMakeLists.txt」描述项目构建过程。

4.1 make

make是个构建工具,类似于maven。
Makefile定义了项目的依赖关系和构建规则。

1.发展背景和现状
make起源于AT&T贝尔实验室,当前由FSF维护, GUN项目提供make的GUN版本。(目前大多数开发环境和操作系统中使用的make实际上是指GUN make,简称make)

2.make的基本概念
Makefile、目标、依赖、规则。

4.1.1 Makefile

Makefile的基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 变量定义 「定义了 (CC) 和编译选项 (CFLAGS)」
CC = gcc
CFLAGS = -Wall -g

# 目标规则
myprogram: main.o foo.o
$(CC) -o myprogram main.o foo.o
## myprogram 是最终生成的可执行文件,它依赖于 main.o 和 foo.o。
## 生成 myprogram 的命令是 $(CC) -o myprogram main.o foo.o。

main.o: main.c
$(CC) $(CFLAGS) -c main.c
## main.o 是一个目标文件,它依赖于 main.c 源文件。生成 main.o 的命令是 $(CC) $(CFLAGS) -c main.c。

foo.o: foo.c
$(CC) $(CFLAGS) -c foo.c

# 清理规则
clean:
rm -f myprogram main.o foo.o

xgboost的Makefile解读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 变量定义 「定义了 (CC) 和编译选项 (CFLAGS)」
CC = gcc
CFLAGS = -Wall -g

# 目标规则
myprogram: main.o foo.o
$(CC) -o myprogram main.o foo.o
## myprogram 是最终生成的可执行文件,它依赖于 main.o 和 foo.o。
## 生成 myprogram 的命令是 $(CC) -o myprogram main.o foo.o。


ALL_DEP = $(filter-out build/cli_main.o, $(ALL_OBJ)) $(LIB_DEP)
# 这个函数调用从 $(ALL_OBJ) 中排除 build/cli_main.o 文件,返回剩余的对象文件列表。

CLI_OBJ = build/cli_main.o

build/%.o: src/%.cc
@mkdir -p $(@D)
$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
$(CXX) -c $(CFLAGS) $< -o $@

xgboost: $(CLI_OBJ) $(ALL_DEP)
$(CXX) $(CFLAGS) -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
## 目标:xgboost 是我们要构建的目标,通常是一个可执行文件。
## 依赖:$(CLI_OBJ) 和 $(ALL_DEP) 是构建 xgboost 所需的依赖文件或对象。
## $(CLI_OBJ) 可能是编译生成的对象文件,而 $(ALL_DEP) 可能包括其他需要的文件(如库文件或额外的对象文件)。

## $(CXX):这是编译器变量,通常设置为 C++ 编译器,如 g++ 或 clang++。
## $(CFLAGS):这些是编译标志,通常用于设置编译器选项,如优化级别和调试信息。对于链接阶段,这个变量可能会被用来设置链接选项。
## -o $@:$@ 是自动变量,表示规则中的目标,即 xgboost。-o 选项用于指定输出文件名。
## $(filter %.o %.a, $^):$^ 是自动变量,表示所有依赖文件的列表。$(filter %.o %.a, $^) 是一个函数,用于筛选出扩展名为 .o 和 .a 的文件。
## 这个函数确保只有 .o 对象文件和 .a 库文件被传递给链接器。
## $(LDFLAGS):这些是链接标志,用于设置链接器选项,例如库路径和库文件。


main.o: main.c
$(CC) $(CFLAGS) -c main.c
## main.o 是一个目标文件,它依赖于 main.c 源文件。生成 main.o 的命令是 $(CC) $(CFLAGS) -c main.c。


foo.o: foo.c
$(CC) $(CFLAGS) -c foo.c


# 清理规则
clean:
rm -f myprogram main.o foo.o

构建过程中,是怎么找到依赖的。 比如 每个源文件是单独构建的,构建的时候,如果依赖其它的 构建,这个关系怎么找到的?
也就是说 怎么知道那个需要先构建?

4.2 cmake

CMake是一个 构建系统生成器。 主要作用是 生成特定与平台和构建工具的构建配置文件。
实际的构建过程则依赖生成的这些构建配置文件所对应的构建工具。

4.2.1 什么是cmake

cmake相比较make,有以下优点:

  • 跨平台支持:支持多种操作系统,如Linux、macOS、Windows等。能够生成适用于不同平台的构建系统配置文件。
  • 构建配置:通过一个或多个CMakeLists.txt文件来描述项目的配置。
  • 自动化和简化构建:生成适用于不同构建工具的配置文件, 使得用户可以使用选择喜欢的构建工具(make/MSBuild等)
    如果系统中有多个C编译器,可以明确指定要使用的编译器

4.2.2 cmake使用方法

CMakeLists.txt

1. 安装cmake

1
2
3
4
5
6
7
# macOS
brew install cmake
# linux
sudo apt-get install cmake # 对于 Debian/Ubuntu 系统
sudo yum install cmake # 对于 Red Hat/CentOS 系统
# Window
choco install cmake

2. 创建CMakeLists.txt
CMakeLists.txt 文件是 CMake 的核心配置文件,用于描述构建项目的规则。一个基本的 CMakeLists.txt 文件可能如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cmake_minimum_required(VERSION 3.10)  # 设置 CMake 的最低版本要求
project(MyProject VERSION 1.0) # 定义项目名称和版本

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 添加可执行文件
add_executable(MyExecutable main.cpp foo.cpp)

#cmake_minimum_required:指定所需的 CMake 最低版本。
#project:定义项目的名称和版本。
#set:设置 CMake 变量,这里设置 C++ 标准。
#add_executable:定义要生成的可执行文件及其源文件。

3. 创建构建目录

1
2
mkdir build
cd build

4. 生成构建系统配置文件

1
2
# 在构建目录中运行 CMake,指定源代码目录(通常是 .. 表示上级目录):
cmake ..

5. 执行构建
使用make等构建工具执行

4.2.3 高级用法

1.定义库

1
2
add_library(MyLibrary STATIC lib.cpp)
target_link_libraries(MyExecutable PRIVATE MyLibrary)

2.查找和使用外部库

1
2
3
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
target_link_libraries(MyExecutable ${OpenCV_LIBS})

1
2
3
4
5
6
7
8
9
10
# 设置编译选项:
target_compile_options(MyExecutable PRIVATE -Wall -Wextra)
# 设置构建选项
option(USE_CUSTOM_FEATURE "Enable custom feature" OFF)
if(USE_CUSTOM_FEATURE)
add_definitions(-DCUSTOM_FEATURE)
endif()
# 运行测试
enable_testing()
add_test(NAME MyTest COMMAND MyExecutable)

4.2.1 版本查看

1
2
3
4
5
6
7
8
9
clang --version

# 查看c++版本
g++ --version
# 或者
clang++ --version

# 查看cmake
cmake --version

5.常见库及问题

5.1 xgboost项目构建和调试

xgboost项目构建和调试过程中遇到的问题?
主要就是 mac系统自带的Clang编译器不支持OpenMP,需要手动安装Clang(基于llvm),即 brew install llvm.

(1.)unsupported option ‘-fopenmp’

1.命令

1
2
clang++ -DDMLC_LOG_CUSTOMIZE=1 -std=c++11 -Wall -Wno-unknown-pragmas -Iinclude   -Idmlc-core/include -Irabit/include 
-I/include -O3 -funroll-loops -msse2 -fPIC -fopenmp -MM -MT build/learner.o src/learner.cc >build/learner.d

2.现象
clang: error: unsupported option ‘-fopenmp’
make: * [build/learner.o] Error 1

3.问题分析
OpenMP 是一个用于多处理器编程的应用程序接口(API),专门设计用于在共享内存系统上并行化计算密集型任务。
是一种编写并行程序的标准化工具,可以显著简化多线程编程的复杂性。
OpenMP的执行模式采用fork-join模式

出现以上问题,可能的原因有:

  • Clang版本不支持OpenMP
  • 未安装OpenMP运行时库
  • Clang配置问题:你的 Clang 安装可能没有启用 OpenMP 支持。需要确保使用的 Clang 编译器版本正确地配置了 OpenMP。
    (重点):1. 开头使用 c++ 进行编译. 2. 改为clang++后还是提示。 因此可能是clang编译器的原因
1
2
#macOS 自带的 Clang 编译器可能不支持 OpenMP,因此你可以通过 Homebrew 安装一个支持 OpenMP 的 Clang 版本
brew install llvm

4.解决办法

1
2
3
4
5
6
7
# 1. 修改makefile的 变量配置,使其使用clang++
CXX = clang++
CC = clang
# 2. 通过llvm 安装新的clang编译器
brew install llvm
# 3. 配置环境变量
export PATH="/usr/local/opt/llvm/bin:$PATH"

mac自带的clang(xcode/commondlinetools) 编译器和基于llvm的编译器存在差异。
mac已经有llvm ,有必要再 brew install llvm 吗?需要通过llvm安装新的clang,并且方便管理维护

6. C++环境验证

6.1 当前环境下使用的 c++的标准库路径?

怎么看当前环境下使用的 c++的标准库路径呢。

1
2
3
4
5
6
7
8
9
# GCC
g++ -print-search-dirs
g++ -print-file-name=libstdc++.a
g++ -print-file-name=libstdc++.so

# Clang
clang++ -print-search-dirs
clang++ -print-file-name=libc++.dylib
clang++ -print-file-name=libc++.a

6.2 vscode环境