Search code examples
cposix

Seg fault when reading a line into dynamically allocated char pointer array


In C, I am trying to implement a function that uses getline() to read all the lines from a file. It is implemented similarly to getline(), specifically the fact that it is using realloc() to resize a char** if there is not enough memory allocated to store the next pointer to a line. Unfortunately I am getting seg faults during the string dupilcation process.

After a little poking around, I discovered that the segfault happens during the second iteration while attempting to store the second line in the char pointer array.

ssize_t fgetlines(char*** linesptr, size_t* n, FILE* fp)
{
    char* line = NULL;
    size_t sz_line = 0;
    size_t cur_len = 0;
    size_t needed;

    if (linesptr == NULL || n == NULL) {
        errno = EINVAL;
        return -1;
    }

    if (*linesptr == NULL) {
        if (*n == 0)
            *n = sizeof(**linesptr) * 30; /* assume 30 lines */
        *linesptr = malloc(*n);
        if (*linesptr == NULL) {
            *n = 0;
            return -1;
        }
    }

    while (getline(&line, &sz_line, fp) > 0) {
        needed = (cur_len + 1) * sizeof(**linesptr);
        while (needed > *n) {
            char** new_linesptr;
            *n *= 2;
            new_linesptr = realloc(*linesptr, *n);
            if (new_linesptr == NULL) {
                *n /= 2;
                free(line);
                return -1;
            }
            *linesptr = new_linesptr;
        }
        *linesptr[cur_len] = strdup(line);
        printf("%s", *linesptr[cur_len]);
        if (*linesptr[cur_len] == NULL) {
            free(line);
            free(*linesptr);
            return -1;
        }
        ++cur_len;
    }

    free(line);
    return cur_len;
}

And I call the function like so:

    char **settings = NULL;
    size_t sz_settings = sizeof(*settings) * 6;
    int count = fgetlines(&settings, &sz_settings, f_cfg);

Due to the function not being able to successfully complete I do not get any output. But after printing back the string after strdup() I managed to get one line of f_cfg, "Hello World" before a seg fault.


Solution

  • Should change

    *linesptr[cur_len]  =>  (*linesptr)[cur_len]
    

    The modified function is as follows:

    ssize_t fgetlines(char *** linesptr, size_t *n, FILE *fp)
    {
        char *line = NULL;
        size_t sz_line = 0;
        size_t cur_len = 0;
        size_t needed;
    
        if (linesptr == NULL || n == NULL) {
            errno = EINVAL;
            return -1;
        }
    
        if (*linesptr == NULL) {
            if (*n == 0)
                *n = sizeof(**linesptr) * 30; /* assume 30 lines */
            *linesptr = malloc(*n);
            if (*linesptr == NULL) {
                *n = 0;
                return -1;
            }
        }
    
        while (getline(&line, &sz_line, fp) > 0) {
            needed = (cur_len + 1) * sizeof(**linesptr);
            while (needed > *n) {
                char **new_linesptr;
                *n *= 2;
                new_linesptr = realloc(*linesptr, *n);
                if (new_linesptr == NULL) {
                    *n /= 2;
                    free(line);
                    return -1; // Possible memory leak
                }
                *linesptr = new_linesptr;
            }
            (*linesptr)[cur_len] = strdup(line);
            printf("%s", (*linesptr)[cur_len]);
            if ((*linesptr)[cur_len] == NULL) {
                free(line);
                free(*linesptr);
                return -1;  // Possible memory leak
            }
            ++cur_len;
        }
    
        free(line);
        return cur_len;
    }
    

    In addition, when your memory allocation fails, the memory of "strdup" is not free, which will lead to memory leak.