02 - new关键字及内存泄漏
new 关键字
内存是计算机的底层组件,扎实理解C++动态内存的工作原理对成为一名专业的C++程序员至关重要。
程序被执行后就被称为一个进程
,一个进程可以被划分为很多区域,主要为:
代码区&常量区:进程按照代码区的代码执行,真正的常量也存储在这里。
PS:const
关键字只是让编译器将变量视为常量,和真正的常量有本质上的区别。
栈区:执行函数所需要的空间。当函数执行完毕,对应的栈内存会被销毁。
堆区:进程用来灵活分配内存的地方,只有手动释放时才会销毁内存
静态变量区:用来存储静态变量和全局变量的区域。
假设我们有个代码文件main.cpp
,单个文件可以通过以下指令进行编译和运行:
# 编译
g++ main.cpp -o a.out
# 运行
./a.out
单文件编译虽然很方便,但也有以下缺点:
因此,提出多文件编译的概念,文件之间通过 符号声明 相互引用:
# 分为m1模块和main主模块
# -c 编译生成临时的对象文件
g++ -c m1.cpp -o m1.o
g++ -c main.cpp -o main.o
# 对他们进行链接,得到最终可执行文件
g++ m1.o main.o -o a.out
这样做解决了单文件编译的缺点,但随着模块的增长,敲的指令也变多了,很麻烦、
Makefile
便诞生了,只需一个文件和一个指令即可完成上边的操作:
Makefile:
a.out: m1.o main.o
g++ m1.o main.o -o a.out
m1.o: m1.cpp
g++ -c m1.cpp -o m1.o
main.o: main.cpp
g++ -c main.cpp -o main.o
指令:
make
# 加快速度
make -j
但是也有一些不足:
make
指令在Unix上通用,但Windows不是hit-oslab
)会很头疼。make
语法简单,不能做很多判断。有时候会碰到多个可执行文件共用某些功能的情况,可以把这些共用的功能做成一个 库 ,方便共享。
库中的函数可以被可执行文件调用,也能被其它库文件调用。库文件分为 静态库文件 和 动态库文件(dll, so) 两种。静态库相当于直接把代码插到可执行文件中,会导致体积变大;动态库会在可执行文件相应位置生成“插桩”函数,执行时会先读取dll文件,将对应功能加载到内存空闲位置,执行到此处会跳转到加载后的地址(插桩在读取dll后会被替换)。
Windows中,先会在可执行文件所在目录查找dll,其次是环境变量%PATH%
;Linux中,先在elf可执行文件的RPATH
中查找,其次是/usrt/lib
等。
为了解决make
的以上问题,跨平台的CMake
便诞生了。上边Makefile
对应的CMakeLists.txt
如下:
cmake_minimum_required(VERSION 3.12)
project(hellocmake LANGUAGES CXX)
add_executable(a.out main.cpp m1.cpp)
这是CMakeLists.txt
文件的基本格式,其中,第4行的意思是,a.out
为输出的文件,后面的全是输入的文件。
读取当前目录的CMakeLists.txt
,并在build
文件夹下生成build/Makefile
:
cmake -B build
让make
读取build/Makefile
,并开始构建a.out
:
make -C build
# 也可以选择更跨平台的做法
make --build build
最后便可以执行build/a.out
了。
可以使用add_library
生成库文件:
# 生成静态库 libtest.a/lib
add_library(test STATIC s1.cpp s2.cpp)
# 生成动态库 libtest.so/dll
add_library(test SHARED s1.cpp s2.cpp)
初学建议用静态库,动态库有坑,但他人提供的库大多数是作为动态库的。
创建库后,要在某个 可执行文件 中使用该库,需要:
# 为 myexec 链接刚刚制作的库 libtest.a
target_link_libraries(myexec PUBLIC test)
在复杂的工程中,需要划分子模块,通常一个库一个目录。例如以下文件结构:
module-1
|- CMakeLists.txt
|- m1.cpp
|- m1.h
CMakeLists.txt
main.cpp
模块的CMakeLists.txt
如下:
add_library(m1lib STATIC m1.cpp)
要想在根目录中使用子模块,CMakeLists.txt
可以这样写:
cmake_minimum_required(VERSION 3.12)
project(hellocmake LANGUAGES CXX)
# 使用子模块m1lib
add_subdirectory(m1lib)
add_executable(a.out main.cpp)
target_link_libraries(a.out PUBLIC m1lib)
此外,还得修改main.cpp
中m1.h
的路径。如果要避免修改代码,可以通过target_include_directories
指定a.out
的头文件搜索目录:
# line 9
target_include_directories(a.out PUBLIC m1lib)
这样子指定的路径也被视为系统路径(可以用尖括号包裹起来)。
但是,以上做法违反了 不要重复自己(DRY) 原则,即如果还有其他可执行文件要使用m1
模块,还得添加这一行。因此,可以直接将这句话添加到m1
模块的CMakeLists.txt
中:
# line 2
target_include_directories(m1lib PUBLIC .)
PUBLIC
和PRIVATE
决定一个属性要不要在被链接的时候传播。
target_include_directories(myapp PUBLIC /usr/...)
:添加头文件搜索目录
target_link_libraries(myapp PUBLIC hellolib)
:添加要链接的库
target_add_definitions(myapp PUBLIC MY_MACRO=1)
:添加一个宏定义
target_add_definitions(myapp PUBLIC -DMY_MACRO=1)
:添加一个宏定义
target_compile_options(myapp PUBLIC -fopenmp)
:添加编译器命令行选项
target_sources(myapps hello.cpp other.cpp)
:添加要编译的源文件
第三方库的引入主要分为三类:纯头文件,作为子模块,系统预安装。
纯头文件库:操作起来最简单,只需把他们的include
目录或头文件下载下来,然后include_directories(xxxx/include)
即可,例如:
stb_image库
,只需添加一个宏定义STB_IMAGE_IMPLEMENTATION
并引入一个头文件即可。magic_enum
库(实现枚举类的自反射,枚举名以字符串形式输出)glm
库:模仿GLSL语法的数学矢量/矩阵库Tencent/rapidjson
:JSON解析库,无STL内容range-v3
:C++20的range
受到他启发而成,但没他好用fmt
:提供std::format
的替代品,需要宏定义FMT_HEADER_ONLY
.缺点:函数直接实现在头文件里,没有提前编译,编译时间长。
作为子模块:可以作为CMake子模块引入,即通过add_subdirectory
fmt
range-v3
glm
abseil-cpp
:补充标准库没有的常用功能backward-cpp
:实现C++的堆栈回溯,便于调试googletest
:谷歌单元测试框架benchmark
:谷歌性能评估框架glfw
:OpenGL窗口和上下文管理libigl
:各种图形学算法大合集缺点:可能会出现菱形依赖
,即某个模块被两个模块使用,发生重复定义。
系统预安装:可以通过find_package
命令寻找系统中的包/库:
find_package(fmt REQUIRED)
target_link_libraries(myexec PUBLIC fmt::fmt)
现代CMake认为一个包可以提供多个库,又称为组件,为了避免冲突,每个包都享有一个独立的名字空间。例如TBB这个包中有tbb,tbbmalloc,tbbmalloc_proxy这三个组件,可以指定要用那几个组件:
find_package(TBB REQUIRED COMPONENTS tbb tbbmalloc REQUIRED)
target_link_libraries(myexec PUBLIC TBB::tbb TBB::tbbmalloc)
常用package
列表如下:
fmt::fmt
range-v3::range-v3
TBB::tbb
OpenVDB::openvdb
Boost::iostreams
Eigen3::Eigen
OpenMP::OpenMP_CXX
不同的包之间常常有依赖关系,包管理器的作者会给find_package
编写脚本(xxxConfig.cmake),可以自动且正确处理依赖项。
包的引用格式和文档可以参考FindBLAS.
Linux可以用包管理器来安装第三方库(如apt
,pacman
)等,Windows没有自带的包管理器,可以用微软跨平台的vcpkg
包管理器。
使用方法:下载vcpkg
的源码,放到项目根目录,如图:
然后执行以下命令:
cd vcpkg
./bootstrap-vcpkg.bat
./vcpkg intergrate install
./vcpkg install fmt:x64-windows # 安装fmt库(默认最新,无法指定版本)
cd ..
cmake -B build -DCMAKE_TOOL_CHAIN_FILE="%CD%/vcpkg/scripts/buildsystems/vcpkg.cmake"
Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.