Home > database >  Memory leaks when reading and transforming a file into a two-dimensional array in C
Memory leaks when reading and transforming a file into a two-dimensional array in C

Time:02-01

I am writing a function in C to load a file as a double dimensional array (char **), the problem is that according to Valgrind I have memory leaks, can you help me?

I provide you with a complete example that can be reproduced. Also, my school only allows me certain functions to perform this task: open, fopen, close, fclose, malloc, free, getline, lseek... (I am not allowed to use stat).

I can use my own implementation of realloc too.

My code:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void my_free_word_array(char **word_array)
{
    size_t i = 0;
    if (!word_array) {
        return;
    }
    while (word_array[i] != NULL) {
        free(word_array[i]);
          i;
    }
    free(word_array);
}

char **append_word_array(char **array, char *line)
{
    size_t array_len = 0;
    while (array[array_len])
        array_len  ;
    size_t len = strlen(line);
    if (line[len - 1] == '\n')
        line[len - 1] = '\0';
    char **new_array = malloc(sizeof(char *) * (array_len   2));
    for (size_t i = 0; i < array_len; i  )
        new_array[i] = array[i];
    new_array[array_len] = strdup(line);
    new_array[array_len   1] = NULL;
    free(array);
    return new_array;
}

char **fill_from_file(char **array, FILE *file)
{
    char *line_buff = NULL;
    size_t line_buff_size = 0;
    ssize_t line_size = getline(&line_buff, &line_buff_size, file);
    while (line_size >= 0) {
        array = append_word_array(array, line_buff);
        free(line_buff);
        line_buff = NULL;
        line_size = getline(&line_buff, &line_buff_size, file);
    }
    free(line_buff);
    return array;
}

char **my_load_file_to_line_array(const char *filepath)
{
    char **word_array = NULL;
    FILE *file = fopen(filepath, "r");
    if (!file)
        return NULL;
    word_array = malloc(sizeof(char *));
    if (!word_array)
        return NULL;
    word_array[0] = NULL;
    word_array = fill_from_file(word_array, file);
    fclose(file);
    return word_array;
}

int main(int argc, char **argv)
{
    if (argc < 2)
        return -1; 
    char **array = my_load_file_to_line_array(argv[1]);
    my_free_word_array(array);
    return 0;
}

My input test file :

 |A B C D E F G H
- ---------------
1|. . . . . . . .
2|. . . . . . . .
3|. . . . . . . .
4|. . . . . . . .
5|. . . . . . . .
6|. . . . . . . .
7|. . . . . . . .
8|. . . . . . . .

Valgrind's report:

❯ valgrind --leak-check=full ./navy coord.txt
==99157== Memcheck, a memory error detector
==99157== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==99157== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==99157== Command: ./navy coord.txt
==99157== 
==99157== 
==99157== HEAP SUMMARY:
==99157==     in use at exit: 72 bytes in 5 blocks
==99157==   total heap usage: 84 allocs, 79 frees, 18,512 bytes allocated
==99157== 
==99157== 72 (40 direct, 32 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
==99157==    at 0x4842888: malloc (vg_replace_malloc.c:381)
==99157==    by 0x10980F: append_word_array (my_load_file_to_line_array.c:18)
==99157==    by 0x109914: fill_from_file (my_load_file_to_line_array.c:33)
==99157==    by 0x1099F0: my_load_file_to_line_array (my_load_file_to_line_array.c:62)
==99157==    by 0x1092A5: main (navy.c:17)
==99157== 
==99157== LEAK SUMMARY:
==99157==    definitely lost: 40 bytes in 1 blocks
==99157==    indirectly lost: 32 bytes in 4 blocks
==99157==      possibly lost: 0 bytes in 0 blocks
==99157==    still reachable: 0 bytes in 0 blocks
==99157==         suppressed: 0 bytes in 0 blocks
==99157== 
==99157== For lists of detected and suppressed errors, rerun with: -s
==99157== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

CodePudding user response:

Here are some problems:

  • you should reset line_buff_size to 0 after line_buff = NULL; in fill_from_file. Or better: keep the line_buff buffer growing as you iterate in the loop and only free it after the end of the loop.

  • you should pass the current array length to append_word_array to avoid scanning for the NULL terminator.

  • testing if (line[len - 1] == '\n') has undefined behavior if line has a length of 0, which is possible if the file contains embedded null bytes.

  • in case of allocation error for the initial call to malloc(), you return NULL omitting the fclose(file).

  • if realloc is allowed, you could use it with caution in append_word_array.

These issues are unlikely to have caused the reported memory leaks. There is a chance the problem is not related to your code (or to this version of the code). You should try and reduce the test file length and post the smallest input that still produces a leak summary with valgrind?

You can also try this modified version:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void my_free_word_array(char **word_array)
{
    if (word_array) {
        size_t i = 0;
        while (word_array[i] != NULL) {
            free(word_array[i]);
              i;
        }
        free(word_array);
    }
}

char **append_word_array(char **array, size_t *array_len, const char *line)
{
    char *copy = strdup(line);
    char **new_array = realloc(array, sizeof(*array) * (*array_len   2));
    if (copy == NULL || new_array == NULL) {
        fprintf(stderr, "memory allocation error");
        exit(1);
    }
    new_array[*array_len] = copy;
    new_array[*array_len   1] = NULL;
    *array_len  = 1;
    return new_array;
}

char **fill_from_file(FILE *file)
{
    char **array = NULL;
    size_t array_len = 0;
    char *line_buff = NULL;
    size_t line_buff_size = 0;
    ssize_t line_length;
    while ((line_size = getline(&line_buff, &line_buff_size, file)) >= 0) {
        if (line_length > 0 && line_buff[line_length - 1] == '\n')
            line_buff[line_length - 1] = '\0';
        array = append_word_array(array, &array_len, line_buff);
    }
    free(line_buff);
    return array;
}

char **my_load_file_to_line_array(const char *filepath)
{
    FILE *file = fopen(filepath, "r");
    if (!file)
        return NULL;
    char **word_array = fill_from_file(file);
    fclose(file);
    return word_array;
}

int main(int argc, char **argv)
{
    if (argc < 2)
        return -1; 
    char **array = my_load_file_to_line_array(argv[1]);
    my_free_word_array(array);
    return 0;
}
  • Related