无法释放内存。

5

gcc 4.4.4 c89

我有以下函数,但无法释放内存。在Valgrind中得到的消息怀疑是getline函数引起的。然而,我在函数末尾释放了文件指针。所以不可能是那个问题。

我有一个指向char类型的全局数组'candidate_names',但我没有为它分配任何内存。

非常感谢您的建议,

我在valgrind中得到的消息如下:

HEAP SUMMARY:
==4021==     in use at exit: 840 bytes in 7 blocks
==4021==   total heap usage: 22 allocs, 15 frees, 1,332 bytes allocated
==4021== 
==4021== Searching for pointers to 7 not-freed blocks
==4021== Checked 48,412 bytes
==4021== 
==4021== 840 bytes in 7 blocks are still reachable in loss record 1 of 1
==4021==    at 0x4005BDC: malloc (vg_replace_malloc.c:195)
==4021==    by 0xAAE38D: getdelim (iogetdelim.c:68)
==4021==    by 0xAAADD2: getline (getline.c:34)
==4021==    by 0x804892B: load_candidates (candidate.c:61)
==4021==    by 0x8048686: main (driver.c:24)

我的源代码:

#define NUMBER_OF_CANDIDATES 7
static char *candidate_names[NAME_SIZE] = {0};

int load_candidates()
{
    FILE *fp = NULL;
    size_t i = 0;
    ssize_t read = 0;
    size_t n = 0;
    char *found = NULL;

    fp = fopen("votes.txt", "r");

    /* open the votes file */
    if(fp == NULL) {
        fprintf(stderr, "Cannot open votes file [ %s ]\n", strerror(errno));
        return FALSE;
    }

    /* fill the structure with candidates */
    for(i = 0; i < NUMBER_OF_CANDIDATES; ) {
        read = getline(&candidate_names[i], &n ,fp);
        if(read == -1) {
            fprintf(stderr, "Cannot read candidate [ %d ] [ %s ]\n",
                    i, strerror(errno));
            candidate_names[i] = "Invalid candidate";
            i++;
            continue;
        }
        /* Just ignore the key work in the text file */
        if(strcmp("CANDIDATES\n", candidate_names[i]) != 0) {
            /* Find and remove the carriage return */
            found = strchr(candidate_names[i], '\n');
            if(found != NULL) {
                /* Remove the carriage return by nul terminating */
                *found = '\0';
            }
            i++;
        }
    }

    fclose(fp);

    return TRUE;
}

编辑 ========= 释放候选人名字 ======

All heap blocks were freed -- no leaks are possible
==4364== 
==4364== ERROR SUMMARY: 84 errors from 2 contexts (suppressed: 12 from 8)
==4364== 
==4364== 42 errors in context 1 of 2:
==4364== Invalid free() / delete / delete[]
==4364==    at 0x40057F6: free (vg_replace_malloc.c:325)
==4364==    by 0x8048A95: destroy_candidate (candidate.c:107)
==4364==    by 0x8048752: main (driver.c:44)
==4364==  Address 0x401e1b8 is 0 bytes inside a block of size 120 free'd
==4364==    at 0x40057F6: free (vg_replace_malloc.c:325)
==4364==    by 0x8048A95: destroy_candidate (candidate.c:107)
==4364==    by 0x8048752: main (driver.c:44)
==4364== 
==4364== 
==4364== 42 errors in context 2 of 2:
==4364== Invalid read of size 1
==4364==    at 0x400730E: strcmp (mc_replace_strmem.c:426)
==4364==    by 0x8048A7F: destroy_candidate (candidate.c:106)
==4364==    by 0x8048752: main (driver.c:44)
==4364==  Address 0x401e1b8 is 0 bytes inside a block of size 120 free'd
==4364==    at 0x40057F6: free (vg_replace_malloc.c:325)
==4364==    by 0x8048A95: destroy_candidate (candidate.c:107)
==4364==    by 0x8048752: main (driver.c:44)


void destroy_candidate()
{
    size_t i = 0;
    for(i = 0; i < NUMBER_OF_CANDIDATES; i++) {
        if(strcmp(candidate_names[i], "Invalid candidate") != 0) {
            free(candidate_names[i]);
        }
    }
}

使用 main.c 中的代码进行编辑 =====================

typedef struct Candidate_data_t {
    size_t candidate_data_id;
    Candidates_t *candidate;
} Candidate_data;

static Candidate_data* create_candidate_data(Candidates_t *candidate, size_t i);
static void destroy_candidata_data(Candidate_data *cand_data);

int main(void)
{
    Candidates_t *candidate = NULL;
    Candidate_data *cand_data[NUMBER_OF_CANDIDATES] = {0};
    size_t i = 0;

    load_candidates();

    for(i = 0; i < NUMBER_OF_CANDIDATES; i++) {
         candidate = create_candidates(i);
         if(candidate == NULL) {
             fprintf(stderr, "Cannot failed to initalize candidate [ %d ]\n", i);
         }

         /* Create array of candidates */
         cand_data[i] = create_candidate_data(candidate, i);
         fill_candidates(cand_data[i]->candidate);
    }

    /* Display each candidate */
    for(i = 0; i < NUMBER_OF_CANDIDATES; i++) {
        display_candidate(cand_data[i]->candidate);
        printf("\n");
    }

    for(i = 0; i < NUMBER_OF_CANDIDATES; i++) {
        destroy_candidata_data(cand_data[i]);
    }

    return 0;
}

static Candidate_data* create_candidate_data(Candidates_t *candidate, size_t id)
{
    Candidate_data *cand_data = NULL;

    cand_data = malloc(sizeof *cand_data);

    if(cand_data == NULL) {
        fprintf(stderr, "Failed to allocate memory [ %s ]\n",
                strerror(errno));

        return NULL;
    }
    cand_data->candidate_data_id = id;
    cand_data->candidate = candidate;

    return cand_data;
}

static void destroy_candidata_data(Candidate_data *cand_data)
{
    destroy_candidate(cand_data->candidate);
    free(cand_data);
}

你的更新,包括 destroy_candidate() 仍然存在错误!这个错误在你的程序中不会显示出来,但是如果你需要调用 load_candidates() 多次 *(或者更准确地说,如果你需要对同一个 candidate_name[i] 指针进行 getline())*,那么库就有可能尝试重新分配一个无效的指针。 - pmg
7个回答

7
请查看 getline()函数手册

如果*lineptr为NULL,则getline()将分配一个用于存储行的缓冲区,该缓冲区应由用户程序释放。(在这种情况下,*n中的值将被忽略。)

在程序结束时,您需要遍历candidate_names数组并对非NULL条目调用free,但在这种情况下,您不能执行candidate_names[i] = "Invalid candidate";,因为@pmg在他的答案中指出,您会尝试释放一个字符串字面量。
还要注意以下内容:

或者,在调用getline()之前,*lineptr可以包含一个大小为*n的malloc(3)分配的缓冲区的指针。如果缓冲区不足以容纳该行,则getline()使用realloc(3)调整其大小,必要时更新*lineptr和*n。

无论哪种情况,在成功调用后,*lineptr和*n都将更新以反映缓冲区地址和已分配大小。


6
什么是candidate_names?它是一个指针数组。
当你执行:
candidate_names[i] = "Invalid candidate";

你给一个指针赋了一个字符串常量的值。也许以后在程序中你想要free它。这是不对的

无论如何,之前candidate_names[i]的值都丢失了。如果这个值不是NULL,那么你刚刚泄露了一些内存。


1
假设你的候选人名称足够长,尝试使用strcpy(candidate_names[i],"Invalid candidate");代替。 - pmg
1
@robUK:问题是字符串字面量的存储在可执行文件的文本段中。它不是使用堆上的malloc分配的。你不能释放它。 - JeremyP
1
@robUK:你并没有分配内存,但是getline()函数会分配内存。这块内存需要被释放,否则valgrind将会报告错误。 - pmg
@JeremyP,如果字符串字面量在堆栈上分配,那么它们在哪里驻留?字符串字面量是一个必须在内存中有一些区域的指针。谢谢。 - ant2009
@robUK:字符串字面量不会在堆栈上分配,它们是由编译器作为程序代码的一部分“分配”的。 - JeremyP
显示剩余4条评论

4

getline()函数会为刚读取的行分配空间,并在幕后调用malloc()函数。你将这些行缓冲区存储在candidate_names数组中,但从未释放它们。泄漏不在于文件指针——你已经正确地释放了它。而是你从文件中读取的行,需要在其他地方使用完后释放。


2

getline分配了内存,将其存储在candidate_names指针数组中。这些指针不会被释放。当你完成它们的使用后,应该调用:

for(i = 0; i < NUMBER_OF_CANDIDATES; i++)
{
    if (strcmp(candidate_names[i], "Invalid candidate") != 0)
        free(candidate_names[i]);
}

此外,该数组应声明为:
static char *candidate_names[NUMBER_OF_CANDIDATES];

在你使用getline之前需要添加:

candidate_names[i] = NULL;

NAME_SIZE不需要,因为该内存是动态分配的,除非您在其他地方使用它进行输入验证或其他操作。


我已更新我的问题。我添加了一个函数来销毁候选对象并释放所有内存。然而,我正在释放所有已分配的内存。"没有泄漏可能"。但是,我遇到了一些错误“无效释放”。谢谢。 - ant2009
我已经将我的主函数添加到编辑后的问题中。然而,还有其他一些函数在里面。我保留它们以防在找到解决方案时需要使用。谢谢。 - ant2009
你的 destroy_candidate 调用与你之前发布的定义不匹配。 - Karl Bielefeldt
@Karl。那就是完整的代码。但它确实做了更多的事情。我只是在原问题中发布了一个示例。我会尝试自己解决这个问题。看起来我只能把它拆开了。谢谢。 - ant2009

1

你在getline()函数中分配了内存,但是没有释放这些内存。这就是valgrind告诉你的:你有七个(== NUMBER_OF_CANDIDATES)未被释放的块。

关闭文件指针并不会释放getline()分配的内存。

在load_candidates()函数的结尾,你需要做类似于以下的操作:

for(int i = 0; i < NUMBER_OF_CANDIDATES; i++)
{
    free(candidate_names[i]);
}

编辑

在您的编辑中,您正在释放空指针。请尝试:

void destroy_candidate()
{
    size_t i = 0;
    for(i = 0; i < NUMBER_OF_CANDIDATES; i++) {
        if ( (candidate_names[i] != NULL) && (strcmp(candidate_names[i], "Invalid candidate") != 0) ){
            free(candidate_names[i]);
        }
    }
}

1

我看到你有两个不同的宏 NUMBER_OF_CANDIDATESNAME_SIZE。看起来有麻烦。


1
可能是这样,但它并没有回答问题... 这可能更适合作为评论,而不是答案。 - bdonlan
1
这不是*问题的核心,但它确实是一个问题,特别是当NUMBER_OF_CANDIDATESNAME_SIZE更大时。 - Karl Bielefeldt
#define NAME_SIZE 80. 抱歉忘了这个。 - ant2009
@bdonlan:这可能只是问题,因为那样你会破坏内存,导致未定义的行为,任何事情都可能发生。 - Jens Gustedt

-2

我认为这不正确。

getline(&candidate_names[i], &n ,fp);

没有理由传递指向整数的指针。


getline是一个POSIX标准函数,其原型为ssize_t getline(char **lineptr, size_t *n, FILE *stream);。传递指向size_t的指针是正确的。 - bdonlan
糟糕,由于某种原因我一直在想C++ fstream getline。 - Novikov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接