在 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 程序中的所有代码行,尤其是当您在发生异常的行中没有发现任何错误时。