5

gcc 4.4.4 c89

I have the following function but I cannot free the memory. The message I get in Valgrind is suspecting the getline function. However, I am free the file pointer at the end of the function. So it cannot be that.

I have a global array of pointers to char 'candidate_names'. However, I haven't allocated any memory for it.

Many thanks for any advice,

The message I get in valgrind is the following.

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)

My source code:

#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;
}

EDIT ========= FREEING candidate_names ======

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]);
        }
    }
}

EDIT with code from 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);
}
ant2009
  • 27,094
  • 154
  • 411
  • 609
  • Your update, with `destroy_candidate()` is still in error! The error will never show with your program, but if you ever need to call `load_candidates()` more than once *(or more to the point, if you need to `getline()` to the same candidate_name[i] pointer)* there's a chance the library will try to realloc an invalid pointer. – pmg Sep 15 '10 at 17:10

7 Answers7

7

Have a look at the getline() man page.

If *lineptr is NULL, then getline() will allocate a buffer for storing the line, which should be freed by the user program. (In this case, the value in *n is ignored.)

At the end of your program, you need to loop over your candidate_names array and call free on non NULL entries but in that case you must not do candidate_names[i] = "Invalid candidate";as @pmg pointed in his answer as you would try to free a string literal.

Pay also attention to:

Alternatively, before calling getline(), *lineptr can contain a pointer to a malloc(3)-allocated buffer *n bytes in size. If the buffer is not large enough to hold the line, getline() resizes it with realloc(3), updating *lineptr and *n as necessary.

In either case, on a successful call, *lineptr and *n will be updated to reflect the buffer address and allocated size respectively.

Community
  • 1
  • 1
Gregory Pakosz
  • 69,011
  • 20
  • 139
  • 164
6

What is candidate_names? It's an array of pointers.
When you do

candidate_names[i] = "Invalid candidate";

you assign the pointer to a string literal. Maybe later in the program you want to free it. That's a NO-NO!

In any case, the previous value of candidate_names[i] is lost. If the value was not NULL, you just leaked some memory.

pmg
  • 106,608
  • 13
  • 126
  • 198
  • I thought was perfectly ok. Isn't the string literal a pointer? And I am assigning that pointer to the an element of the array of pointers to char. I am not allocating any memory, so there is no need to free. This would be freed by the O/S once the program exits as its allocated on the stack. Am I correct? Thanks. – ant2009 Sep 15 '10 at 15:52
  • 1
    Assuming your candidate names are long enough, try `strcpy(candidate_names[i], "Invalid candidate");` instead – pmg Sep 15 '10 at 15:53
  • 1
    @robUK: The point is that the string literal's storage is in the text segment of your executable. It was not allocated with malloc from the heap. You can't free it. – JeremyP Sep 15 '10 at 15:55
  • 1
    @robUK: you don't allocate but `getline()` does. That memory needs to be freed or valgrind complains. – pmg Sep 15 '10 at 15:58
  • @JeremyP, I understand now. So the string literal was allocated on the stack. So trying to free it would end up with an 'Invalid Free' error. As it was never allocated on using malloc. Thanks. – ant2009 Sep 15 '10 at 16:14
  • @robUK: String literals aren't allocated on the stack, but you're right that the point is that they're not allocated with `malloc`. – jamesdlin Sep 15 '10 at 18:04
  • @JeremyP, If string literals are allocated on the stack, there where do they reside? A string literal is a pointer that must have some area in memory. Thanks. – ant2009 Sep 16 '10 at 02:15
  • @robUK: maybe this SO post will help ... ( http://stackoverflow.com/questions/1966920/more-info-on-memory-layout-of-an-executable-program-process ) – pmg Sep 16 '10 at 06:48
  • @robUK: String literals are not allocated on the stack, they are "allocated" as part of the code of your program by the compiler. – JeremyP Sep 16 '10 at 07:56
4

getline() allocates space for the line it just read, calling malloc() for you behind the scenes. You store these line buffers in the candidate_names array, but never free it. The leak isn't the file pointer - you free that just fine. It's the lines you read from the file, which need to be freed elsewhere, when you're done using them.

bdonlan
  • 224,562
  • 31
  • 268
  • 324
2

getline allocates memory which you store in your candidate_names array of pointers. Those pointers are not getting freed. When you are done with them, you should call:

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

Additionally, that array should be declared as:

static char *candidate_names[NUMBER_OF_CANDIDATES];

And right before your getline you need:

candidate_names[i] = NULL;

NAME_SIZE isn't needed because that memory is dynamically allocated, unless you are using it elsewhere for input validation or something.

Karl Bielefeldt
  • 47,314
  • 10
  • 60
  • 94
  • I have updated my question. I have added a function to destroy_candidate() and free all memory. However, i am freeing all allocated memory. 'No leaks possible'. However, I am getting some errors 'Invalid free'. Thanks. – ant2009 Sep 15 '10 at 16:26
  • I have added my main function to the edited question. However, there is some other function in there. I kept them in case they were needed in finding a solution. Thanks. – ant2009 Sep 16 '10 at 07:02
  • Your `destroy_candidate` call doesn't match the definition you posted earlier. – Karl Bielefeldt Sep 16 '10 at 14:48
  • @Karl.That was the complete code. However, it did do a lot more. I just posted a sample in the ordinal question. I will try and sort this out myself. Looks like I will just have to take it apart. Thanks. – ant2009 Sep 16 '10 at 15:02
1

I see that you have two different macros NUMBER_OF_CANDIDATES and NAME_SIZE. Looks like trouble.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
1

You are allocating memory inside getline(). You are never freeing that memory. This is what valgrind is telling you: that you have seven (== NUMBER_OF_CANDIDATES) blocks that you have not freed.

Closing the file pointer does not free the memory that getline() allocated.

You need to do something like this at the end of load_candidates():

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

EDIT

In your edit you are freeing null pointers. Try:

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]);
        }
    }
}
Andy Johnson
  • 7,938
  • 4
  • 33
  • 50
-2

I don't think this is correct.

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

No reason to pass a pointer to an integer.

Novikov
  • 4,399
  • 3
  • 28
  • 36
  • getline is a POSIX standard function with prototype ` ssize_t getline(char **lineptr, size_t *n, FILE *stream);`. Passing a pointer-to-a-size_t is correct. – bdonlan Sep 15 '10 at 15:42
  • whoops, for some reason I was thinking of C++ fstream getline. – Novikov Sep 15 '10 at 15:53