在 C 中释放内存后堆损坏
Heap Corruption After Freeing Memory In C
我有以下代码:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#ifdef _WIN32
#include <windows.h>
#elif defined __unix__
#include <unistd.h>
#endif
#define BENCH
#ifndef BENCH
#define N 10000
#endif
int main(void)
{
#ifdef BENCH
FILE* output = fopen("out.csv", "w");
for (int N = 10000; N <= 100000; N += 10000)
#endif
{
int* a = malloc(N * sizeof(int));
if (a == NULL)
abort();
for (int i = 2; i < N; i++)
a[i] = 1;
#ifdef BENCH
clock_t begin = clock();
#endif
for (int i = 2; i < N; i++)
{
if (a[i])
{
#if defined (BENCH) && defined (_WIN32)
Sleep(1);
#elif defined (BENCH) && defined (__unix__)
sleep(0.001);
#endif
for (int j = i; j <= N / i; j++)
a[i * j] = 0;
}
}
#ifdef BENCH
clock_t end = clock();
double time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
fprintf(output, "%d,%f\n",N ,time_spent);
free(a); //This is where the corruption occurs
#else
for (int i = 2; i < N; i++)
{
if (a[i])
printf("%9d ", i);
}
puts("");
#endif
}
#ifdef BENCH
fclose(output);
#endif
}
如果未定义 BENCH
,程序将采用预定义的 N 并在控制台上显示结果。算法的性能未被测量。
如果定义了 BENCH
,将使用 for
循环创建具有不同输入大小 (N)
的问题的多个实例。一旦问题的一个实例完成,算法的性能就会记录在 output.csv
文件中。然后,必须释放为该实例的数组分配的内存。
至少根据 Microsoft Visual C++ 编译器在 Visual Studio 2019 上的说法,这是发生堆损坏的地方。有人知道为什么会这样吗? malloc()
和 free()
对我来说似乎都是正确的。如果删除 free()
,程序工作正常。
问题最终定位在循环的终止条件for (int j = i; j <= N / i; j++)
,而不是free()
。将其更改为 for (int j = i; j < N / i; j++)
解决了堆损坏问题。
正如一些程序员在评论中指出的那样,在调试器没有通知我的情况下发生了越界异常。在 Visual Studio 我主要编写 C# 代码的地方,我有一种错觉,如果出现问题,我至少会在坏事发生的那一行收到异常。
不是这种情况,因为 C 没有任何类型的边界检查。在某些情况下,未定义的行为往往会起作用 "fine",然后一些微不足道的改变就是一切都崩溃了。正如 AbbysSoul 指出的那样,堆损坏在所有情况下都会发生,即使 BENCH
未定义。当在损坏的堆上调用像 free()
这样的关键操作时,进程注定会崩溃。
这里的重要教训是检查 C 程序中的所有代码行,尤其是当您在发生异常的行中没有发现任何错误时。
我有以下代码:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#ifdef _WIN32
#include <windows.h>
#elif defined __unix__
#include <unistd.h>
#endif
#define BENCH
#ifndef BENCH
#define N 10000
#endif
int main(void)
{
#ifdef BENCH
FILE* output = fopen("out.csv", "w");
for (int N = 10000; N <= 100000; N += 10000)
#endif
{
int* a = malloc(N * sizeof(int));
if (a == NULL)
abort();
for (int i = 2; i < N; i++)
a[i] = 1;
#ifdef BENCH
clock_t begin = clock();
#endif
for (int i = 2; i < N; i++)
{
if (a[i])
{
#if defined (BENCH) && defined (_WIN32)
Sleep(1);
#elif defined (BENCH) && defined (__unix__)
sleep(0.001);
#endif
for (int j = i; j <= N / i; j++)
a[i * j] = 0;
}
}
#ifdef BENCH
clock_t end = clock();
double time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
fprintf(output, "%d,%f\n",N ,time_spent);
free(a); //This is where the corruption occurs
#else
for (int i = 2; i < N; i++)
{
if (a[i])
printf("%9d ", i);
}
puts("");
#endif
}
#ifdef BENCH
fclose(output);
#endif
}
如果未定义 BENCH
,程序将采用预定义的 N 并在控制台上显示结果。算法的性能未被测量。
如果定义了 BENCH
,将使用 for
循环创建具有不同输入大小 (N)
的问题的多个实例。一旦问题的一个实例完成,算法的性能就会记录在 output.csv
文件中。然后,必须释放为该实例的数组分配的内存。
至少根据 Microsoft Visual C++ 编译器在 Visual Studio 2019 上的说法,这是发生堆损坏的地方。有人知道为什么会这样吗? malloc()
和 free()
对我来说似乎都是正确的。如果删除 free()
,程序工作正常。
问题最终定位在循环的终止条件for (int j = i; j <= N / i; j++)
,而不是free()
。将其更改为 for (int j = i; j < N / i; j++)
解决了堆损坏问题。
正如一些程序员在评论中指出的那样,在调试器没有通知我的情况下发生了越界异常。在 Visual Studio 我主要编写 C# 代码的地方,我有一种错觉,如果出现问题,我至少会在坏事发生的那一行收到异常。
不是这种情况,因为 C 没有任何类型的边界检查。在某些情况下,未定义的行为往往会起作用 "fine",然后一些微不足道的改变就是一切都崩溃了。正如 AbbysSoul 指出的那样,堆损坏在所有情况下都会发生,即使 BENCH
未定义。当在损坏的堆上调用像 free()
这样的关键操作时,进程注定会崩溃。
这里的重要教训是检查 C 程序中的所有代码行,尤其是当您在发生异常的行中没有发现任何错误时。