C++开发(三)
C++开发(三)
最近的确学习到开发的知识。
Cmake串
可以在一个项目中使用多个CMake文件(CMakeLists.txt
),在模块化的项目中非常常见。在项目的最顶层编写一个CMakeLists.txt
,在其中可以通过add_subdirectory()
命令去添加包含了子CMakeLists.txt
的子模块目录。这样,在主目录执行cmake
时,cmake
会自动再跑到子目录接着执行对应目录的CMakeLists.txt
。子目录如果还有子目录,则递归执行。
在递归的过程中,Cmake
的环境得到了保持,比如上层增加的头文件目录include_directories
在下层中还是会同样include
。主CMakeLists.txt
也可以不设置具体目标文件,而只是最一些全局配置,比如C++标准等。
1 | project_root/ |
Homebrew安装
Python由于运行完全由解释器决定,包就是一个个.py
文件,所以它的包管理容易一点。可以一个Python解释器版本对应一组.py
文件,然后把这个解释器和包都放到一起就行了。其实这就是Anaconda做环境管理的思路。当然,现在还有项目驱动的包管理方法, 类似于Node管理库文件的方式,不再是一个解释器配一套库,而是一个项目配一套自己的库,Julia应该也是这样做的。
对于C/C++这种编译型语言,全局(只)有一套工具链。而且包不是Python那种动态型的,导入.py
文件就可以运行了。而是需要提前编译成库文件,否则要不每次调用包写自己的代码,还需要把大包也一起编译了才能运行,这个繁琐程度是受不了的,而且也是没必要的。因为这些包的代码都是不变的,他们只是给我们自己的程序提供了些方便的函数或者类接口。这个时候,我们需要提前把他们编译成库文件,到时候我们自己的项目可以直接链接这些库文件,不需要重新编译这些大包。另外,我们在写程序的时候,因为编译器一次只能编译一个源文件,因此我们还需要这些大包中头文件的接口声明,方便编译器知道怎么分配空间一类的。因此安装一个C/C++的包,实际上就是两块,一块是头文件,这使得编译器知道怎么编译我们调用了大包的源文件。另一个就是我们要把大包的源文件编译成库文件,这样链接器能把库文件串到我们自己的项目上。
在Win里,没有一个自带的包管理工具。对于MacOS,也没有一个系统自带的包管理工具,但是开源的Homebrew已经成为了MacOS的标准了。Intel芯片的Mac和M芯片的Mac下Homebrew的默认目录是不同的,但是Apple逐渐就全是M芯片了,这里就只看看M芯片吧,反正也差不多。
Homebrew所谓的包管理,实际上和Anaconda也差不多,只不过它并不是解释器依赖的envs目录,而是在一个全局目录做包管理。Homebrew默认将包安装到/opt/homebrew
目录下,这个文件包含了Homebrew的所有文件和软件包。Homebrew使用所谓的Formula(配方)来描述如何安装软件包。每个Formula是一个Ruby脚本,包含了从下载源代码、编译到安装软件包的完整过程。Formula存储在Github上的Homebrew仓库中,当运行brew install
时,Homebrew会从这些仓库中获取相应的Formula。什么是包?就是一堆文件。
/opt/homebrew/Cellar
(地下室、地窖)是Homebrew存储包的主要目录。每个软件包都会有一个子目录,并且按照版本号进一步分类。比如,安装的GoogleTest可能位于/opt/homebrew/Cellar/googletest/1.11.0
。每个版本的目录包含了该软件包的所有文件,包括可执行文件、库文件、头文件等。/opt/homebrew/opt
提供用于指向Cellar
中某个软件包版本的符号链接。这使得我们总能够通过opt
路径访问到当前的“激活”版本呢,而无需关心具体的版本号。
我们在安装Homebrew时,会把Homebrew的可执行文件路径添加到我们的$PATH
路径中。Homebrew通过brew link
命令,将其所安装的各大包的可执行文件都连接到这个路径,这样我们就可以通过命令行直接使用这些大包的可执行文件,而不需要手动把他们每个的bin路径都粘到$PATH
中。此外,当安装某些库时,Homebrew可能会设置LDFLAGS
和CPPFLAGS
环境变量,前一个用于指定连接时选项,后一个用于指定编译时选项。
另外呢,当通过Homebrew安装某些软件包时,Homebrew还可能会从源码编译这些软件,这也是根据Formula的指示编译安装的。对于常用的软件包,Homebrew也会提供预编译的二进制包,称为bottles
。这些包下载后直接解压即可使用,无须机器再编译了。Homebrew源码编译具体来说,工具链的选择以及编译出的库文件的位置取决于Homebrew的配置和包的具体Formula。
Homebrew默认使用MacOS提供的编译器工具链,即Xcode中的clang
、clang++
和ld
,以及make
和其他相关工具。Homebrew也允许自定义工具链,可以通过环境变量如CC
、CXX
、LD
等来指定不同的编译器或链接器。某些Formula中可能还指定了特定的工具链或者编译器选项,以确保兼容性或性能。例如某些包可能强制使用clang
或gcc
,以确保与其他库或工具的一致性。
编译出来的库文件通常会放在Homebrew的地窖目录下,对应于具体包的子目录中。同时,为了方便使用,Homebrew会在/opt/homebrew/opt
目录下创建相应的符号链接,指向Cellar
目录中的实际文件。通常,编译出来的库文件会以.a
(静态库)或.dylib
(动态库)形式存放在于lib
子目录下。而头文件会放置在include
子目录下,供其他项目引用。Homebrew也会把大包的头文件、编译出来的可执行文件(如果有)、以及库文件,分别放到Homebrew下的include
、bin
、以及lib
目录中。我们也可以指定Homebrew的安装选项,比如在brew install
安装的时候,--build-from-source
会强制Homebrew从源码编译,而不是使用预编译的bottle
二进制包;--cc=gcc-11
会强制Homebrew使用指定的编译器。不同编译器生成的代码可能会存在不兼容,如果用clang
编译一个库文件,然后用gcc
编译自己的项目并链接到这个库,就可能会遇到API不兼容的问题导致报错。因此,依赖库的编译和项目的编译最好都采用相同的工具链。
另外,sbin
目录,在各种情景下,一般都是系统管理员的二进制文件目录,通常包含那些普通用户不需要直接执行的系统管理工具或守护进程(daemon)。这些工具可能需要管理员权限才能运行,因此通常位于sbin
而非bin
目录中。Homebrew安装的软件包如果包含系统管理工具,可能会把可执行文件放到sbin
目录下而不是bin
目录。例如,网络服务管理工具或系统配置工具等,通常会安装在sbin
目录中,以避免普通用户无意中执行这些命令。
Anaconda路径
Anaconda的环境中也有很多长得很像的库文件目录,其中
Lib
目录,这是Windows平台的特有目录。主要用于存放Python的标准库和由conda
或pip
安装的纯Python包。标准库模块直接放到Lib
目录中,site-packages
子目录放置通过pip
或conda
安装的第三方Python包。Unix系统下存在lib/pythonX.y
目录下。
Library
目录,这是Windows平台特有的目录,主要用于存放非Python的库和工具。它类似于Unix系统中的/usr/local
目录。这个目录主要用于在Anaconda环境中处理非Python的依赖,特别是那些需要与C/C++代码交互的库。Library
目录下有三个子目录:
include
存放C/C++头文件。
lib
存放C/C++静态库和动态库。
bin
存放可执行文件和动态库。这个目录在Unix系统上有,在Windows平台变成了Scripts
目录。
libs
也是Windows平台特有的目录,用于存放Python解释器的库文件,如python38.lib
。它主要用于链接Python解释器的库文件,以及如果需要在C/C++代码中嵌入Python解释器或者使用Python/C API,这个库会被用到。
各平台工具链
Linux:GCC(GNU Compiler Collection),Linux系统最常用的编译器,其中gcc
(C编译器)、g++
(C++)编译器、gfortran
(Fortran)编译器;Clang:基于LLVM的编译器,clang
(C/C++编译器)、clang++
(C++编译器);GDB(GNU Debugger)调试器gdb
。
Mac:Clang基于LLVM的编译器,LLDB
基于LLVM的调试器。
Win:MSVC(Microsoft Visual C++),cl
编译器,link
连接器;MinGW-w64,GCC的Windows版本;Clang,基于LLVM的编译器。
总结:
Linux:主要使用 GCC 或 Clang 作为编译器,搭配 Make、CMake 等构建工具,GDB 作为调试器。APT 和 Yum 是常见的包管理器。
macOS:默认使用 Clang 编译器,配合 Xcode 提供的工具链。Homebrew 是常用的包管理器,LLDB 是默认的调试器。
Windows:MSVC 是最常用的编译器和工具链,Visual Studio 提供全面的开发环境。MinGW 和 Clang 也被广泛使用。Chocolatey 和 vcpkg 是主要的包管理器。
另外,大公司Intel为它的硬件提供了一套Intel编译器套件(Intel one API Toolkits),这些编译器以高性能和对 Intel 硬件的优化著称,广泛应用于科学计算、工程仿真、高性能计算(HPC)等领域。
头文件引用
在C++中,""
和<>
是用于包含头文件的两种不同的方式,它们之间的主要区别是编译器搜索头文件的路径顺序。#include "header.h"
通常用于包含项目中自定义的头文件。编译器首先在包含当前源文件的目录中搜索该头文件,如果在当前目录中未找到,则继续在标准库的包含路径中搜索。#include <header.h>
用于包含标准库或第三方库的头文件,编译器直接在标准库的包含路径中搜索该头文件,如果未找到,继续在其他指定的包含路径中查找。
多语言项目配置
现在手头的项目,既有Python、又有Cython、还有C++/Pybind11,需要规划出合理的目录结构:
1 | 3DPhononMC/ |
清晰的分离:通过将Python、Cython、C++/Pybind11代码放在各自独立的目录中,确保每个部分的代码和构建配置都能独立维护,减少耦合。
Cython目录独立:将Cython代码单独放在phononmc/cython/
目录中,可以更明确地表明哪些部分是使用Cython实现的,有助于开发和调试。
头文件与实现分离:在cpp
目录中,将头文件放在include/
目录中,可以保持代码结构的清晰,并遵循良好的C++代码实践。
模块化测试:在cpp/tests/
目录中管理所有C++测试代码,可以确保测试代码与实现代码分离,并简化测试的构建和执行流程。
逐步转换为 C++:随着项目的发展,你可以逐步将 phononmc/
中的逻辑迁移到 cpp/
,只保留必要的 Python 接口。
测试与文档:保持测试与文档的良好组织,使得项目易于维护和扩展。
CMake配置:在顶层 CMakeLists.txt
中配置整个项目的构建逻辑,并通过 add_subdirectory
包含 cpp
目录中的 CMake 配置。Cython部分的构建可以通过 setup.py
进行配置。
setup.py配置:在 setup.py
中管理 Python 和 Cython 的安装和打包。如果需要,将 C++/Pybind11 的构建与 Python 包打包集成,可以在 setup.py
中调用 CMake 进行构建。后面再具体总结setup.py
的配置。
VSCode与CMake
文件夹
- 3DPhononMC:这是你当前打开的 CMake 项目的根目录名称。VS Code 识别这是一个 CMake 项目,并在这个文件夹下组织所有与 CMake 相关的内容。
配置
- Visual Studio 生成工具 2022 Release - x64 - Debug:这个部分显示了当前选择的 CMake 配置,包括编译器和构建类型。在这个例子中,使用的是 Visual Studio 2022 发行版生成工具,当前是 Debug 构建类型。
- 你可以通过点击这个部分来选择其他配置(不同的编译器或不同的构建类型)。
生成
- test_hello:这里列出了你 CMake 项目中可构建的目标。
test_hello
是一个可执行目标,表示这个项目会生成一个名为test_hello
的可执行文件。 - 点击生成目标可以触发构建。你也可以编辑目标配置(如重命名或修改构建选项)。
测试
- [All tests]:这部分显示项目中定义的测试目标。如果你使用了 GoogleTest 或其他测试框架,并在 CMake 中定义了测试,CMake Tools 会在这里列出这些测试,并允许你运行它们。
[All tests]
表示你可以一次运行项目中所有的测试。
调试
- all:这一部分列出了可以调试的构建目标。在这个例子中,
all
可能是一个自动生成的目标,表示可以调试整个项目。 - 点击调试目标后,VS Code 会启动调试器,并附加到选定的可执行文件或测试上。
启动
- all:这一部分通常与调试目标类似,但它只是启动目标而不附加调试器。
- 你可以点击这里来启动项目的主可执行文件或其他指定的启动目标。
项目大纲
- PHONONMC:这是你的 CMake 项目在项目大纲中的显示名称。VS Code 通过 CMakeLists.txt 文件识别并组织项目结构。
- hello_cpp (静态库) 和 hello_py (静态库):这些是项目中的库目标(静态库)。这些目标是由 CMakeLists.txt 文件定义的,并且会生成
.lib
或.a
文件。 - test_hello (可执行):这是一个可执行文件目标,表示项目将生成一个名为
test_hello
的可执行文件。
总结
- 文件夹 部分显示项目的根目录。
- 配置 部分允许你选择和切换构建配置(如编译器和生成器)。
- 生成 部分显示可以生成的目标文件(可执行文件、静态库等)。
- 测试 部分显示项目中定义的单元测试,可以直接运行这些测试。
- 调试 和 启动 部分列出可以调试或启动的目标。
- 项目大纲 显示项目的整体结构和构建目标。
这些部分相互配合,使你能够方便地配置、构建、测试和调试 CMake 项目。
可以在CMake插件的默认选项中设置默认生成器,比如Ninja。此外,在VSCODE工具栏左侧的测试部分,可以看到项目的详细测试目标。
在包含子模块的CMake测试中,每个层级的CMakeLists.txt都需要enable_testing()
,否则在生成目录的顶层运行ctest
是找不到子目录的测试目标的。