NOTE:此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-04/recipe-05 中找到,包含一个C++的示例。该示例在CMake 3.5版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。

内存缺陷:写入或读取越界,或者内存泄漏(已分配但从未释放的内存),会产生难以跟踪的bug,最好尽早将它们检查出来。Valgrind( http://valgrind.org )是一个通用的工具,用来检测内存缺陷和内存泄漏。本节中,我们将在使用CMake/CTest测试时使用Valgrind对内存问题进行警告。

准备工作

对于这个配置,需要三个文件。第一个是测试的实现(我们可以调用文件leaky_implementation.cpp):

  1. #include "leaky_implementation.hpp"
  2. int do_some_work() {
  3. // we allocate an array
  4. double *my_array = new double[1000];
  5. // do some work
  6. // ...
  7. // we forget to deallocate it
  8. // delete[] my_array;
  9. return 0;
  10. }

还需要相应的头文件(leaky_implementation.hpp):

  1. #pragma once
  2. int do_some_work();

并且,需要测试文件(test.cpp):

  1. #include "leaky_implementation.hpp"
  2. int main() {
  3. int return_code = do_some_work();
  4. return return_code;
  5. }

我们希望测试通过,因为return_code硬编码为0。这里我们也期望检测到内存泄漏,因为my_array没有释放。

具体实施

下面展示了如何设置CMakeLists.txt来执行代码动态分析:

  1. 我们首先定义CMake最低版本、项目名称、语言、目标和依赖关系: ```cmake cmake_minimum_required(VERSION 3.5 FATAL_ERROR)

project(recipe-05 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_library(example_library leaky_implementation.cpp)

add_executable(cpp_test test.cpp) target_link_libraries(cpp_test example_library)

  1. 2.
  2. 然后,定义测试目标,还定义了`MEMORYCHECK_COMMAND`:
  3. ```cmake
  4. find_program(MEMORYCHECK_COMMAND NAMES valgrind)
  5. set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes --leak-check=full")
  6. # add memcheck test action
  7. include(CTest)
  8. enable_testing()
  9. add_test(
  10. NAME cpp_test
  11. COMMAND $<TARGET_FILE:cpp_test>
  12. )
  1. 运行测试集,报告测试通过情况,如下所示: ```shell $ ctest

Test project /home/user/cmake-recipes/chapter-04/recipe-05/cxx-example/build Start 1: cpp_test 1/1 Test #1: cpp_test ……………………. Passed 0.00 sec 100% tests passed, 0 tests failed out of 1 Total Test time (real) = 0.00 sec



4. 
现在,我们希望检查内存缺陷,可以观察到被检测到的内存泄漏:
```shell
$ ctest -T memcheck

Site: myhost
Build name: Linux-c++
Create new tag: 20171127-1717 - Experimental
Memory check project /home/user/cmake-recipes/chapter-04/recipe-05/cxx-example/build
Start 1: cpp_test
1/1 MemCheck #1: cpp_test ......................... Passed 0.40 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.40 sec
-- Processing memory checking output:
1/1 MemCheck: #1: cpp_test ......................... Defects: 1
MemCheck log files can be found here: ( * corresponds to test number)
/home/user/cmake-recipes/chapter-04/recipe-05/cxx-example/build/Testing/Temporary/MemoryChecker.*.log
Memory checking results:
Memory Leak - 1
  1. 最后一步,应该尝试修复内存泄漏,并验证ctest -T memcheck没有报告错误。

工作原理

使用find_program(MEMORYCHECK_COMMAND NAMES valgrind)查找valgrind,并将MEMORYCHECK_COMMAND设置为其绝对路径。我们显式地包含CTest模块来启用memcheck测试操作,可以使用CTest -T memcheck来启用这个操作。此外,使用set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes --leak-check=full"),将相关参数传递给Valgrind。内存检查会创建一个日志文件,该文件可用于详细记录内存缺陷信息。

NOTE:一些工具,如代码覆盖率和静态分析工具,可以进行类似地设置。然而,其中一些工具的使用更加复杂,因为需要专门的构建和工具链。Sanitizers就是这样一个例子。有关更多信息,请参见https://github.com/arsenm/sanitizers-cmake 。另外,请参阅第14章,其中讨论了AddressSanitizer和ThreadSanitizer。

更多信息

该方法可向测试面板报告内存缺陷,这里演示的功能也可以独立于测试面板使用。我们将在第14章中重新讨论,与CDash一起使用的情况。

有关Valgrind及其特性和选项的文档,请参见http://valgrind.org