往期本博主的 C++ 精讲优质博文可通过这篇导航进行查找:
《Lemo 的C++精华博文导航:进阶、精讲、设计模式文章全收录》
前言
在C++二次开发的过程中,理解各种编译模式并能灵活切换,对于提升软件性能和调试效率至关重要。
本文将深入讨论 Debug
与 Release
模式的区别、默认编译模式、如何确保编译模式的一致性、编译模式不一致时可能遇到的问题及在依赖项为 Release
模式时,如何支持自身为 Debug
模式。
文章目录
什么是 Debug 模式和 Release 模式
在 C++ 项目开发中,Debug
模式和 Release
模式是两种最常见的编译配置。
Debug
模式旨在增加调试信息,优化调试过程,而不注重运行效率和生成文件的大小。Release
模式会应用优化以提高程序的运行速度和效率,通常会移除调试信息,减少可执行文件的体积。
通常而言:
Debug
模式会保留更多的调试信息,包括变量名、函数栈信息等,使得开发者可以使用调试器跟踪执行过程,检查变量值等,便于查找和修复错误。
而Release
模式通过优化代码,去除不必要的调试信息,以期望达到更快的执行速度和更高的运行效率,是向最终用户发布的首选模式。
默认编译模式介绍
大多数CMake
项目支持四种默认编译模式:Debug
、Release
、RelWithDebInfo
、MinSizeRel
。
下面是它们各自的区别:
- Debug模式: 保留详细的调试信息,不进行优化,适合开发和调试阶段使用。
- Release模式: 启用优化,不保留调试信息,适用于最终的产品发布。
- RelWithDebInfo模式: 既保留一定的调试信息,又进行优化。它提供了一种中间的选择,既可以获得较好的性能又便于调试。
- MinSizeRel模式: 优化设置旨在减少最终产品的体积,适用于对可执行文件大小有严格要求的情况。
CMake对应地提供了变量 CMAKE_BUILD_TYPE
来管理这些编译模式。
如下例所示:
cmake -B ./build -G "Visual Studio 17 2022" -T v141 -DCMAKE_BUILD_TYPE=Debug -DProject_INSTALL_PATH="project_root_dir"
就是显式的指定是用Debug
模式来对 .sln
工程进行生成。
如果开发者不想看到这么多编译模式,可以在 CMakeLists.txt 中显式的指定能看到的编译模式:
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "Configurations" FORCE)
如上例,就显式的指定了,只显式 Debug
Release
两种模式在工程中。
如何保持编译模式一致
在实际开发过程中,保持整个项目及其依赖项的编译模式一致是非常重要的。不一致的编译模式不仅会引起性能问题,甚至可能导致程序错误。要确保编译模式一致,可以在CMakeLists.txt中全局设置CMAKE_BUILD_TYPE,并确保所有子项目和相关依赖采用相同的设置。
set(CMAKE_BUILD_TYPE Release)
将上述代码添加到项目的根CMakeLists.txt文件中,可以确保整个项目使用Release模式编译。
当然,开发者也可以在生成的 .sln
工程中进行调整编译模式,不过不建议这么做,这样可能会导致编译选项和链接选项混乱,因为,默认的编译选项,链接选项都是在 CMakeLists.txt 中写好了,在生成 .sln
工程时就设置在了工程中,贸然的改动会导致异常的发生。
下面给一段通用的 cmake 编译模式设置代码:
# 进行编选选项类型的设置
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "Configurations" FORCE)
# 默认设置编译选项
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
endif()
if (CMAKE_BUILD_TYPE AND (CMAKE_BUILD_TYPE STREQUAL "Debug"))
# 设置独属于 Debug 模式时的一些宏定义、设置、编译选项、链接选项,etc
add_definitions(...)
string(...)
add_compile_options(...)
add_link_options(...)
set(...)
...
elseif (CMAKE_BUILD_TYPE AND (CMAKE_BUILD_TYPE STREQUAL "Release"))
# 设置独属于 Release 模式时的一些宏定义、设置、编译选项、链接选项,etc
string(...)
add_compile_options(...)
add_link_options(...)
set(...)
...
endif()
# 设置通用的编译选项、链接选项
add_compile_options(...)
add_link_options(...)
...
编译模式不一致时可能出现的问题
编译模式的不一致可能会导致许多问题,包括但不限于:
- 性能不一致:Debug模式下的低效率可能会使整个项目或系统的性能降低。
- 程序错误:不同编译模式下的内存布局可能不同,Debug模式可能添加了额外的检查,这可能会在Release模式下暴露出隐藏的错误。
- 链接错误:尤其是在使用静态库或动态库时,如果编译模式不一致,可能会遇到链接错误或运行时错误。
最常见的现象是,程序能正常启动,但运行过程中出现因析构错误导致的程序崩溃问题,这类问题也不好定位。
如何在依赖项为 Release 模式时,自身为 Release 模式也可以进行调试
如果依赖项为 Release 模式,自身为 Release 模式时,如果也想进行调试,可以采用如下办法:
elseif (CMAKE_BUILD_TYPE AND (CMAKE_BUILD_TYPE STREQUAL "Release"))
string(REPLACE "/O2" "/Od" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
string(REPLACE "/O2" "/Od" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
add_compile_options(/Zi)
add_link_options(/DEBUG)
endif()
我们来一句句看上述代码:
- 第一句,当设置当模式为 Release 模式时
- 第二句,将 CMAKE_CXX_FLAGS_RELEASE 变量中的调试信息优化级别从/O2(最大优化)更改为/Od(不优化),即 CXX 编译时生成调试信息。
- 第三句,将 CMAKE_C_FLAGS_RELEASE 变量中的调试信息优化级别从/O2(最大优化)更改为/Od(不优化),即 C 编译时生成调试信息。
- 第四句,添加编译选项/Zi,表示使用 Standard Edit & Continue 调试信息格式。
- 第五句,添加链接选项/DEBUG,请求链接器生成调试信息。
通过上述方法,就能保证 Release 模式也可以进行调试了。
如何在依赖项为Release模式时,支持自身为Debug模式
在某些情况下,可能需要在依赖项为Release模式时,使自身项目保持在Debug模式,特别是在进行调试且依赖项不方便改变时。要实现这一点,可以采用如下的方法:
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "Configurations" FORCE)
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
endif()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
if (CMAKE_BUILD_TYPE AND (CMAKE_BUILD_TYPE STREQUAL "Debug"))
string(REPLACE "/O2" "/Od" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
string(REPLACE "/O2" "/Od" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Zi")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Zi")
add_link_options(/DEBUG)
elseif (CMAKE_BUILD_TYPE AND (CMAKE_BUILD_TYPE STREQUAL "Release"))
add_definitions(
-DNDEBUG
)
# 编译器优化
add_compile_options(/Ob1)
add_compile_options(/Oi)
add_compile_options(/Ot)
add_compile_options(/GF)
add_compile_options(/Gy)
endif()
这是一套通用的做法。先设置编译选项的类型只有 Debug, Release 两种模式,然后对它们进行约束。
将 Release 模式的编译链接选项给 Debug 模式,覆盖它选项的设置。然后将 Debug 模式中新的不能产生调试信息和链接信息的选项全部替换掉,使其能正常调试,然后优化掉 Release 模式中的相关编译选项,使其不保留调试相关的信息。
通过这些方法,可以灵活地控制项目及其依赖项的编译方式,以适应不同的开发和部署需求。
总结
CMake的灵活性为C++项目的编译配置提供了强大支持。理解和正确使用Debug与Release模式,能有效促进项目的开发效率和产品的性能,是每个C++开发者都应掌握的重要技能。
希望本文的内容能够成为你在C++项目中更有效地使用CMake编译策略带来些帮助。
如果有不解的,欢迎私信交流!