C/C++ 中的内存分配:如何避免 Intel® Inspector XE for Visual Studio 2015 检测到的内存分配/释放不匹配问题





5.00/5 (10投票s)
本技巧/窍门介绍了如何避免 Intel® Inspector XE for Visual Studio 2015 检测到的内存分配/释放不匹配问题的基本思路。
引言
在使用 Visual Studio 2015 中的Intel® Inspector XE 检测 C/C++ 代码中的内存泄漏和堆栈操作问题时,您可能会遇到这种情况:即使您在每次分配后都正确地释放了每个缓冲区,Intel® Inspector XE 仍然检测到分配/释放不匹配的问题。在本文中,我们将讨论如何正确释放分配的内存缓冲区,以规避 Intel® Inspector XE 检测到的内存分配/释放不匹配问题。
内存分配/释放不匹配问题
假设您的代码执行以下操作:分配一块内存来存储 N 个特定类型 DATA
的对象的数组,并将内存缓冲区的地址分配给指针变量 data
。
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
typedef struct tagData
{
char* string;
} DATA, NEAR *PDATA, FAR* LPDATA;
const size_t N = 10;
int main()
{
int count = 10;
while (--count >= 0)
{
LPDATA data = (LPDATA)malloc(sizeof(DATA) * N);
memset((void*)&data[0], 0x00, sizeof(DATA) * N);
const size_t length = 1024;
for (size_t index = 0; index < N; index++)
{
data[index].string = (char*)malloc(length);
sprintf(data[index].string, "string: %zu", index);
}
// Do some work at here
for (size_t index = 0; index < N; index++)
free(data[index].string);
free(data);
}
return 0;
}
这通常通过调用 C 内存分配函数(如 calloc
、malloc
、realloc
)以及 C++ 中的 new[]
和 delete[]
运算符来完成。然后,我们遍历对象数组 data
,并为每个对象,我们使用这些内存分配函数分配进程内存来存储大小为 length
的字符缓冲区字符串。在这种情况下,我们正在将分配的缓冲区地址迭代地分配给每个特定对象 data[index]
的指针变量 string
。
由于这些内存缓冲区已被分配,我们可以使用这些分配的缓冲区执行一些工作。最后,根据最佳编程实践,为了避免内存泄漏,我们需要在代码执行结束时明智地释放特定的缓冲区。为此,我们遍历对象数组,并为每个特定对象,通过使用 C 函数 free(data[index].string)
或 C++ 中的 delete[] data[index].string
运算符来释放用于存储字符串缓冲区的内存。之后,我们使用相同的 free(data) 函数释放用于存储对象数组 data
的缓冲区。
通常,通过执行上面列出的代码,我们会看到它显然可以成功执行并提供正确的结果,并且在其执行结束时没有内存泄漏或其他问题。然而,通过使用Intel® Inspector XE 来检测和分析代码执行期间可能存在的内存泄漏或其他问题,我们可能会遇到以下代码突然出现一系列分配/释放不匹配的问题。我们特意重复内存分配/释放过程以在 CPU 上创建工作负载,以便Intel® Inspector XE 可以轻松检测到以下问题。这些问题主要发生在 Windows 内核下运行以下代码时。
导致此代码可能遇到这些问题并因此被Intel® Inspector XE 检测到的主要原因是,当在 Windows 内核下运行以下代码时,调用 free(data[index].string)
函数或 free(data)
实际上并没有释放缓冲区。调用这些函数相当于将特定指针变量(如 data = nullptr
)设置为 null
指针。反过来,在代码执行开始时预先分配的内存由 Windows 内核自动重新分配给其他正在运行的进程。这实际上是Intel® Inspector 突然检测到本文讨论的这些问题的原因。
解决方法
作为讨论问题的变通方法,我们必须使用 ::VirtualAllocEx(...)
和 ::VirtualFreeEx(...)
WinAPI 函数来正确分配/释放内存缓冲区。另外,为了确保我们实际释放了之前分配的内存,我们必须检查分配给指针变量的地址值是否不等于 nullptr
。如果是,则执行释放,否则不做任何操作。内存缓冲区的分配/释放通常如下面的代码示例所示。
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <memory.h>
#include <Windows.h>
typedef struct tagData
{
char* string;
} DATA, NEAR *PDATA, FAR* LPDATA;
const size_t N = 10;
int main()
{
int count = 10;
while (--count >= 0)
{
HANDLE hProcHandle = ::GetCurrentProcess();
LPDATA data = (LPDATA)::VirtualAllocEx(hProcHandle, NULL, \
sizeof(DATA) * N, MEM_COMMIT, PAGE_READWRITE);
::ZeroMemory((void*)&data[0], sizeof(DATA) * N);
const size_t length = 1024;
for (size_t index = 0; index < N; index++)
{
data[index].string = (char*)::VirtualAllocEx(hProcHandle, NULL, \
length, MEM_COMMIT, PAGE_READWRITE);
sprintf(data[index].string, "string: %zu", index);
}
// Do some work here
for (size_t index = 0; index < N; index++)
if (data[index].string != NULL)
::VirtualFreeEx(hProcHandle, data[index].string, length, MEM_RELEASE);
if (data != NULL)
::VirtualFreeEx(hProcHandle, data, sizeof(DATA) * N, MEM_RELEASE);
}
return 0;
}
在这种情况下,与使用 malloc(…)
和 free(…)
函数不同,::VirtualAllocEx(...)
和 ::VirtualFreeEx(…)
WinAPI 函数在 Windows 内核虚拟内存地址空间中分配/释放特定缓冲区。因此,Intel® Inspector XE 不再检测到任何问题,如内存分配/释放不匹配。有关执行虚拟内存管理的这些函数的详细描述,请在此处 查找。
历史
- 2017 年 5 月 1 日- 本技巧/窍门的第一个修订版已发布。