> For the complete documentation index, see [llms.txt](https://chenxiaowei.gitbook.io/cmake-cookbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://chenxiaowei.gitbook.io/cmake-cookbook/4.0-chinese/4.5-chinese.md).

# 4.5 使用动态分析来检测内存缺陷

**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`):

```cpp
#include "leaky_implementation.hpp"

int do_some_work() {

  // we allocate an array
  double *my_array = new double[1000];

  // do some work
  // ...

  // we forget to deallocate it
  // delete[] my_array;

  return 0;
}
```

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

```cpp
#pragma once

int do_some_work();
```

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

```cpp
#include "leaky_implementation.hpp"

int main() {
  int return_code = do_some_work();

  return return_code;
}
```

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

## 具体实施

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

1. 我们首先定义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)
   ```
2. 然后，定义测试目标，还定义了`MEMORYCHECK_COMMAND`:

   ```
   find_program(MEMORYCHECK_COMMAND NAMES valgrind)
   set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes --leak-check=full")

   # add memcheck test action
   include(CTest)

   enable_testing()

   add_test(
     NAME cpp_test
     COMMAND $<TARGET_FILE:cpp_test>
     )
   ```
3. 运行测试集，报告测试通过情况，如下所示:

   ```
   $ 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. 现在，我们希望检查内存缺陷，可以观察到被检测到的内存泄漏:

   ```
   $ 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
   ```
5. 最后一步，应该尝试修复内存泄漏，并验证`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> 。
