首页 > 解决方案 > C中递归函数中的内存泄漏

问题描述

我的小 C 程序正在遍历工作目录的内容,这没有问题,但我用 Valgrind 进行了检查,发现有一些神秘的泄漏,我几天都无法消失。

所以这里是代码:

struct record {
    char* fullPath;
    int wdlen; // working directory length
    int relplen; // relative path length
    int namlen; // name length
    bool folder; // is folder
    long modified;
};

void populateDB() {
    struct record** records = NULL;
    int entryCount = 0;
    bool error = false;

    listDirRecursive(workingDirectory, &entryCount, &records, &error);

    if (error) {
        goto cleanup;
    }

    for (int i = 0; i < entryCount; i++) {
        printfColor(C_MAGENTA, "%d. wdlen: %d,\t\trelPathLen: %d,\t\tnamelen: %d,\t\tfolder: %s,\t\tmod: %ld,\t\tpath: %s\n",
            i, records[i]->wdlen, records[i]->relplen, records[i]->namlen, records[i]->folder ? "true" : "false", records[i]->modified, records[i]->fullPath);
    }

    // initDatabase(&entryCount, &records);

cleanup:

    for (int i = 0; i < entryCount; i++) {
        free(records[i]->fullPath);
        free(records[i]);
    }
    free(records);
}

void listDirRecursive(const char* path, int* count, struct record*** records, bool* error) {
    if (*error) return;

    struct dirent** fileList = NULL;

    int fileCount = scandir(path, &fileList, NULL, alphasort);
    if (fileCount < 0) {
        printfColor(C_RED, "Couldn't open: %s\n", path);
        *error = true;
        return;
    }

    struct record** tmpRecords = malloc(fileCount * sizeof *tmpRecords);
    if (!tmpRecords) {
        printfColor(C_RED, "Couldn't allocate memory for records\n");
        *error = true;
        return;
    }

    struct record* act = NULL;
    struct stat filestat;
    int processedFiles = 0;

    for (int i = 0, wdlen = strlen(workingDirectory), plen = strlen(path); i < fileCount; i++) {
        if (strcmp(fileList[i]->d_name, ".") == 0 ||
            strcmp(fileList[i]->d_name, "..") == 0) continue;

        act = malloc(sizeof *act);
        if (!act) {
            printfColor(C_RED, "Couldn't allocate memory for actual record\n");
            *error = true;
            goto cleanup;
        }
        
        act->fullPath = malloc(plen + fileList[i]->d_namlen + 2);
        if (!act->fullPath) {
            printfColor(C_RED, "Couldn't allocate memory for actual record's full path\n");
            *error = true;
            goto cleanup;
        }

        strcat(strcat(strcpy(act->fullPath, path), "/"), fileList[i]->d_name);

        act->wdlen = wdlen + 1;
        act->relplen = strlen(act->fullPath) - ((wdlen + 1) + fileList[i]->d_namlen);
        act->namlen = fileList[i]->d_namlen;

        act->folder = fileList[i]->d_type == DT_DIR;

        stat(act->fullPath, &filestat);
        act->modified = filestat.st_mtimespec.tv_nsec;

        tmpRecords[processedFiles++] = act;
        
        if (fileList[i]->d_type == DT_DIR) {
            char* p = malloc(plen + fileList[i]->d_namlen + 2);
            strcat(strcat(strcpy(p, path), "/"), fileList[i]->d_name);

            listDirRecursive(p, count, records, error);

            free(p);
        }
    }

    if (processedFiles == 0) {
        goto cleanup;
    }

    struct record** shrinkedTmpRecords = realloc(tmpRecords, processedFiles * sizeof *shrinkedTmpRecords);
    if (!shrinkedTmpRecords) {
        if (verbose) printf("Shrinking tmpRecords failed. Some memory leak will happen\n");
    }
    else {
        tmpRecords = shrinkedTmpRecords;
    }

    int newCount = *count + processedFiles;

    if (*count == 0) {
        *records = tmpRecords;
    }
    else {
        struct record** newRecords = realloc(*records, newCount * sizeof *newRecords);
        if (!newRecords) {
            printfColor(C_RED, "Couldn't allocate memory for further records\n");
            *error = true;
            goto cleanup;
        }

        *records = newRecords;
        for (int i = 0, j = *count; i < processedFiles; i++, j++) {
            (*records)[j] = tmpRecords[i];
        }
    }

    *count = newCount;

cleanup:

    for (int i = 0; i < fileCount; i++) {
        free(fileList[i]);
    }
    free(fileList);
}

运行后,Valgrind 的输出是这样的:

==40861== HEAP SUMMARY:
==40861==     in use at exit: 18,084 bytes in 170 blocks
==40861==   total heap usage: 423 allocs, 253 frees, 180,357 bytes allocated
==40861== 
==40861== 8 bytes in 1 blocks are definitely lost in loss record 1 of 43
==40861==    at 0x100111FD6: realloc (in /usr/local/Cellar/valgrind/HEAD-e0af3eb/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==40861==    by 0x10000162B: listDirRecursive (filesystem.c:137)
==40861==    by 0x1000015DA: listDirRecursive (filesystem.c:127)
==40861==    by 0x1000015DA: listDirRecursive (filesystem.c:127)
==40861==    by 0x1000015DA: listDirRecursive (filesystem.c:127)
==40861==    by 0x1000015DA: listDirRecursive (filesystem.c:127)
==40861==    by 0x100001092: populateDB (filesystem.c:44)
==40861==    by 0x100000E15: init (mtl.c:111)
==40861==    by 0x100000CD2: main (mtl.c:61)
==40861== 
==40861== 16 bytes in 2 blocks are definitely lost in loss record 5 of 43
==40861==    at 0x100111FD6: realloc (in /usr/local/Cellar/valgrind/HEAD-e0af3eb/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==40861==    by 0x10000162B: listDirRecursive (filesystem.c:137)
==40861==    by 0x1000015DA: listDirRecursive (filesystem.c:127)
==40861==    by 0x1000015DA: listDirRecursive (filesystem.c:127)
==40861==    by 0x1000015DA: listDirRecursive (filesystem.c:127)
==40861==    by 0x100001092: populateDB (filesystem.c:44)
==40861==    by 0x100000E15: init (mtl.c:111)
==40861==    by 0x100000CD2: main (mtl.c:61)
==40861== 
==40861== 72 bytes in 5 blocks are definitely lost in loss record 22 of 43
==40861==    at 0x100111FD6: realloc (in /usr/local/Cellar/valgrind/HEAD-e0af3eb/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==40861==    by 0x10000162B: listDirRecursive (filesystem.c:137)
==40861==    by 0x1000015DA: listDirRecursive (filesystem.c:127)
==40861==    by 0x1000015DA: listDirRecursive (filesystem.c:127)
==40861==    by 0x100001092: populateDB (filesystem.c:44)
==40861==    by 0x100000E15: init (mtl.c:111)
==40861==    by 0x100000CD2: main (mtl.c:61)
==40861== 
==40861== 80 bytes in 1 blocks are definitely lost in loss record 23 of 43
==40861==    at 0x100111FD6: realloc (in /usr/local/Cellar/valgrind/HEAD-e0af3eb/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==40861==    by 0x10000162B: listDirRecursive (filesystem.c:137)
==40861==    by 0x100001092: populateDB (filesystem.c:44)
==40861==    by 0x100000E15: init (mtl.c:111)
==40861==    by 0x100000CD2: main (mtl.c:61)
==40861== 
==40861== 112 bytes in 4 blocks are definitely lost in loss record 26 of 43
==40861==    at 0x100111FD6: realloc (in /usr/local/Cellar/valgrind/HEAD-e0af3eb/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==40861==    by 0x10000162B: listDirRecursive (filesystem.c:137)
==40861==    by 0x1000015DA: listDirRecursive (filesystem.c:127)
==40861==    by 0x100001092: populateDB (filesystem.c:44)
==40861==    by 0x100000E15: init (mtl.c:111)
==40861==    by 0x100000CD2: main (mtl.c:61)
==40861== 
==40861== LEAK SUMMARY:
==40861==    definitely lost: 288 bytes in 13 blocks
==40861==    indirectly lost: 0 bytes in 0 blocks
==40861==      possibly lost: 0 bytes in 0 blocks
==40861==    still reachable: 0 bytes in 0 blocks
==40861==         suppressed: 17,796 bytes in 157 blocks

288 字节除以 8(指针的大小)为 36,即最后的总记录(文件和文件夹)。13 个块数等于文件夹数。

我尝试了所有我想到的东西,但没有任何效果,我正在学习 C,所以我可能不知道一些重要的东西(我阅读了很多关于该主题的内容,并试图避免大的禁忌)。

如果有人不明白那是什么printfColor,这里是:

// header file:
enum Color {
    C_RED,
    C_BOLD_RED,
    C_GREEN,
    C_BOLD_GREEN,
    C_YELLOW,
    C_BOLD_YELLOW,
    C_BLUE,
    C_BOLD_BLUE,
    C_MAGENTA,
    C_BOLD_MAGENTA,
    C_CYAN,
    C_BOLD_CYAN,
    C_RESET
};

static const char* colors[] = {
    "\033[0;31m", // RED
    "\033[1;31m", // BOLD RED
    "\033[0;32m", // GREEN
    "\033[1;32m", // BOLD GREEN
    "\033[0;33m", // YELLOW
    "\033[01;33m", // BOLD YELLOW
    "\033[0;34m", // BLUE
    "\033[1;34m", // BOLD BLUE
    "\033[0;35m", // MAGENTA
    "\033[1;35m", // BOLD MAGENTA
    "\033[0;36m", // CYAN
    "\033[1;36m", // BOLD CYAN
    "\033[0m" // RESET
};

// c file:
void printfColor(enum Color color, const char* format, ...) {
    va_list args;

    printf("%s", colors[color]);
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
    printf("%s", colors[C_RESET]);
}

雪儿

标签: crecursionmemory-leaksvalgrind

解决方案


肖恩在评论中是正确的。tmpRecords 被分配并用作一组指针:

struct record** shrinkedTmpRecords = realloc(tmpRecords, processedFiles * sizeof *shrinkedTmpRecords);
if (!shrinkedTmpRecords) {
    if (verbose) printf("Shrinking tmpRecords failed. Some memory leak will happen\n");
}
else {
    tmpRecords = shrinkedTmpRecords;
}

然后,如果 count 为 0,则您只是分配recordstmpRecords

if (*count == 0) {
    *records = tmpRecords;
}

稍后这将被释放。在这里,您释放 的元素records 以及为保存它们而分配的指针集。

for (int i = 0; i < entryCount; i++) {
    free(records[i]->fullPath);
    free(records[i]);
}
free(records);

如果在迭代期间计数不为零,我们会为记录分配更多空间。然后,您循环遍历tmpRecords并将元素分配给records. 这些元素将在被释放时records被释放,但为其tmpRecords自身分配的指针集永远不会被释放。我在下面的代码中添加了一行来修复这个泄漏。

if (*count == 0) {
    *records = tmpRecords;
}
else {
    struct record** newRecords = realloc(*records, newCount * sizeof *newRecords);
    if (!newRecords) {
        printfColor(C_RED, "Couldn't allocate memory for further records\n");
        *error = true;
        goto cleanup;
    }

    *records = newRecords;
    for (int i = 0, j = *count; i < processedFiles; i++, j++) {
        (*records)[j] = tmpRecords[i];
    }
    free(tmpRecords);  /*** THIS LINE ***/
}

推荐阅读