Search code examples
cmallocfreegetline

bytes lost after freeing getline() buffer


I have looked around for posts regarding valgrind memory leaks when using getline().

essentially what my code does is read in a line using getline().

int main(){
LL A;
LLinit(&A);
size_t len = 5;
char *lineOrigin = (char *)malloc(len);
char *line_ptr = lineOrigin;
getline(&line_ptr,&len,stdin);
int status, val;
while( (status = sscanf(line_ptr,"%d",&val) ) >= 0){
    printf("%d\n",status);
    //if(isBadInput(line_ptr)){...}
    if(status == 0){
        printf("ENCOUNTERED BAD INPUT. STOPPING\n");
        return 1;
    }
    Node *node_ptr = (Node *)malloc(sizeof(Node));
    node_ptr->val = val;
    append(&A,node_ptr);
    line_ptr = lookPastSpace(line_ptr);
}
printLL(&A);
freeLL(&A);
free(lineOrigin);
return 0;

}

As you can see, I allocate some memory for char *lineOrigin, and then have a second pointer *line_ptr, which I make point to *lineOrigin, that I mutate later in the code.

This block of code pretty much reads in integers from stdin and stores them in order into a LinkedList.

each time sscanf reads an int into &val, I use lookPastSpace() to move *line_ptr over the non-space characters in the string until i encounter another space, then i move the pointer one more notch over, so that line_ptr now points to the next place in the string where a new int can be read in

line_ptr = "123 456 789\n"
line_ptr = lookPastSpace(line_ptr)
line_ptr = "456 789\n"
...
line_ptr = "" -->sscanf stops.

Afterwords, I walk down the linked list and free every allocated Node, and since the nodes have no contents with allocated memory, thats as far as I have to go to free the memory of the LL.

Then, later, i free char *lineOrigin, and this presumably should work, since it IS the originally allocated buffer, but it does not.

    tylerjgabb@lectura:~/HW6/medianDir$ make mem
valgrind median
==11827== Memcheck, a memory error detector
==11827== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==11827== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==11827== Command: median
==11827==
123 456 789
1
1
1
[123, 456, 789]
==11827== Invalid free() / delete / delete[] / realloc()
==11827==    at 0x4C2BDEC: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11827==    by 0x400B37: main (in /p3/ht/tylerjgabb/HW6/medianDir/median)
==11827==  Address 0x51ff040 is 0 bytes inside a block of size 5 free'd
==11827==    at 0x4C2CE8E: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11827==    by 0x4EA5F9A: getdelim (iogetdelim.c:106)
==11827==    by 0x400A73: main (in /p3/ht/tylerjgabb/HW6/medianDir/median)
==11827==
==11827==
==11827== HEAP SUMMARY:
==11827==     in use at exit: 13 bytes in 1 blocks
==11827==   total heap usage: 5 allocs, 5 frees, 66 bytes allocated
==11827==
==11827== LEAK SUMMARY:
==11827==    definitely lost: 13 bytes in 1 blocks
==11827==    indirectly lost: 0 bytes in 0 blocks
==11827==      possibly lost: 0 bytes in 0 blocks
==11827==    still reachable: 0 bytes in 0 blocks
==11827==         suppressed: 0 bytes in 0 blocks
==11827== Rerun with --leak-check=full to see details of leaked memory
==11827==
==11827== For counts of detected and suppressed errors, rerun with: -v
==11827== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
tylerjgabb@lectura:~/HW6/medianDir$

I'm a little stuck, and confused. Any ideas whats going on?

--------------CLARIFICATION ON TYPEDEFS AND LL FUNCTIONS----------------- LLfuncs.c

    #include "LinkedListHeader.h"
    #include <stdlib.h>

    void LLinit(LL *LL_ptr){
        LL_ptr->head_ptr = NULL;
        LL_ptr->tail_ptr = NULL;
        LL_ptr->length = 0;
    }


void freeLL(LL *LL_ptr){
    Node *curr_ptr = LL_ptr->head_ptr;
    while(curr_ptr != NULL){
        Node *next_ptr = curr_ptr->next_ptr;
        free(curr_ptr);
        curr_ptr = next_ptr;
    }
}

above is only a small portion of my LLfuncs.c

LinkedListHeader.h

/* This is a header file containing typedefs and macros for linked lists *Tyler J Gabb
 *For assignment 6a. Shuffle.
 */
#include <stdio.h>
#define showLocs(LL) printf("head_ptr = %p, tail_ptr = %p length = %d\n",LL.head_ptr,LL.tail_ptr,LL.length)


typedef struct LinkedListNode {
  int val;
  struct LinkedListNode *next_ptr;
} Node;

typedef struct LinkedList {
    Node *head_ptr;
    Node *tail_ptr;
    int length;
} LL;

LLinit() initializes any recently declared LL type, to have head and tail at 0x0, and a length of 0. This is conducive to other functions which mutate an LL

--------------------------- SOME ADDITIONAL INTERESTING INFO-------------

If the size of the input string is less than the original value of len, then I don't get memory leak (sometimes)


Solution

  • You essentially have the following pattern:

    char *lineOrigin, *line_ptr;
    size_t len = 5;
    
    lineOrigin = malloc(len);
    line_ptr = lineOrigin;
    getline(&line_ptr, &len, stdin);
    

    Because getline() will reallocate the buffer whenever necessary, after the getline() call, lineOrigin may not be valid anymore.

    Essentially, you are keeping a copy of an old pointer in lineOrigin that is possibly freed by the getline() call. Instead of freeing the current buffer possibly reallocated by getline(), line_ptr, you mistakenly (use) and free() some old value no longer valid: lineOrigin.


    The correct way to write the pattern is simple. You start by defining the line pointer as NULL and its allocated size zero. I call the allocated size line_max, with line_len reflecting the length of the current line, but you can obviously use whatever variable naming you find most maintainable.

    char   *line_ptr = NULL;
    size_t  line_max = 0;
    ssize_t line_len;
    

    You read one or more lines using

    line_len = getline(&line_ptr, &line_max, stdin);
    if (line_len > 0) {
        /* You have a line in line_ptr */
    } else
    if (ferror(stdin)) {
        /* Error reading from standard input (rare!) */
    
    } else
    if (feof(stdin)) {
        /* No more data to read from standard input. */
    }
    

    You can repeat the above as many times as you want, but you need to realize that the value of line_ptr and line_max may vary from one call to the next.

    After you are done, free the line buffer. Because free(NULL); is always safe, this is safe to do even if no actual lines were read:

    free(line_ptr);
    line_ptr = NULL;
    line_max = 0;
    

    Now, clearing the variables is not strictly necessary, but I have found that it is a good practice, and may occasionally even help with debugging.

    Also note that it is completely okay to free the buffer and clear the variables as above even just before a getline() call, because getline() does not care whether it gets a NULL pointer with zero size, or a previously allocated buffer with a nonzero size.