Home > other >  Dynamic array of strings in C is not working properly
Dynamic array of strings in C is not working properly

Time:12-27

I'm somehow having troubles creating a dynamic array of strings in C. I'm not getting the expected results and I want to know why ?

readLine() function will read each line seperately and will do some changes if necessary :

char *readLine(FILE *f, size_t *len)
{
    char *line = NULL;
    ssize_t nread;

    if (f == NULL)
    {
        return NULL;
    }

    if ((nread = getline(&line, len, f)) != -1)
    {
        if (line[nread - 1] == '\n')
        {
            line[strlen(line)-1] = '\0';
            *len = strlen(line);
        }
        return line;
    }
    else
    {
        return NULL;
    }
}

readFile() function will return an array of strings after reading all of the lines using readLine and then storing them into an array of strings :

char **readFile(const char *filename, size_t *fileLen)
{
    char  *result;
    int    idx   = 0;
    char **array = calloc(1,  sizeof(char*) );

    if (filename == NULL || fileLen == NULL)
    {
        return NULL;
    }

    FILE *f = fopen(filename, "r");
    if (f == NULL)
    {
        return NULL;
    }

    while (1)
    {
        result = readLine(f, fileLen);
        if (result == NULL)
            break;
        else
        {
            *(array   idx) = malloc(LENGTH * sizeof(char *));
            strncpy(array[idx], result, strlen(result)   1);
            idx  ;
            array = realloc(array, (idx   1) * sizeof(char *));
        }
    }

    return array;
}

In main I created a temporary file to test my functions but it didn't work properly :

int main()
{    
    char   filename[] = "/tmp/prefXXXXXX";
    int    fd;
    size_t len = 0;
    FILE  *f;

    if (-1 == (fd = mkstemp(filename)))
        perror("internal error: mkstemp");

    if (NULL == (f = fdopen(fd, "w")))
        perror("internal error: fdopen");

    for (int i = 0; i < 10000; i  )
        fprintf(f, "%d\n", i);
    fclose(f);

    char **number = readFile(filename, &len);

    for (int i = 0; i < sizeof(number) / sizeof(number[0]); i  )
        printf("number[%i] = %s\n", i, number[i]);

    return 0;
}

When I execute the program, I get the following output:

 number[0] = 0

What am I doing wrong here ?

CodePudding user response:

There are lots of issues in that code, it's difficult to find where to start...

Let's look at each function.

char *readLine(FILE *f, size_t *len)
{
    char *line = NULL;
    ssize_t nread;

    if (f == NULL)
    {
        return NULL;
    }

    if ((nread = getline(&line, len, f)) != -1)
    {
        if (line[nread - 1] == '\n')
        {
            line[strlen(line)-1] = '\0';
            *len = strlen(line);
        }
        return line;
    }
    else
    {
        return NULL;
    }
}

There is not much wrong here. But manpage for geline tells us:

If *lineptr is set to NULL before the call, then getline() will allocate a buffer for storing the line. This buffer should be freed by the user program even if getline() failed.

You do not free the buffer if nread==-1 but only do return NULL; possibly causing a memory leak.

You should also check whether len==NUL as you already do it with f.

Then look at the next function:

char **readFile(const char *filename, size_t *fileLen)
{
    char  *result;
    int    idx   = 0;
    char **array = calloc(1,  sizeof(char*) );

    if (filename == NULL || fileLen == NULL)
    {
        return NULL;
    }

    FILE *f = fopen(filename, "r");
    if (f == NULL)
    {
        return NULL;
    }

    while (1)
    {
        result = readLine(f, fileLen);
        if (result == NULL)
            break;
        else
        {
            *(array   idx) = malloc(LENGTH * sizeof(char *));
            strncpy(array[idx], result, strlen(result)   1);
            idx  ;
            array = realloc(array, (idx   1) * sizeof(char *));
        }
    }

    return array;
}

In this function you fail to free(array) in case you hit a return NULL; exit.

readLine puts strlen(result) into filelen. Why don't you use it to allocate memory? Instead you take some unknown fixed length LENGTH that may or may not be sufficient to hold the string. Instead you should use fileLen 1 or strlen(result) 1 as you do it with strncpy.

You are also using size of wrong type. You allocate a pointer to char, not char*. As size of char is defined to be 1 you can just drop the size part here.

Then, the length parameter for strncpy should hold the length of the destination, not the source. Otherwise it is completely useless to use strncpy at all. As you already (should) use the string length to allocate the memory, just use strncpy.

Then, just passing fileLen to the next function does not make sense. In readLine it means length of a line while in readFile that would not make any sense. Instead it should mean number of lines. And as we just came to the topic... You should pass some value to the caller.

Finally, you should not assign the return value of realloc directly to the varirable you passed into it. In case of an error, NULL is returned and you cannot access or free the old pointer any longer.

This block should look like this:

        {
            array[idx] = malloc(fileLen 1);
            strcpy(array[idx], result);
            idx  ;
            void *temp = realloc(array, (idx   1) * sizeof(char *));
            if (temp != NULL)
              array = temp;
            // TODO: else <error handling>
        }
    }
    *fileLen = idx;
    return array;
}

This still has the flaw that you have allocated memory for one more pointer that you do not use. You can change this as further optimization.

Lastly the main function:

int main()
{    
    char   filename[] = "/tmp/prefXXXXXX";
    int    fd;
    size_t len = 0;
    FILE  *f;

    if (-1 == (fd = mkstemp(filename)))
        perror("internal error: mkstemp");

    if (NULL == (f = fdopen(fd, "w")))
        perror("internal error: fdopen");

    for (int i = 0; i < 10000; i  )
        fprintf(f, "%d\n", i);
    fclose(f);

    char **number = readFile(filename, &len);

    for (int i = 0; i < sizeof(number) / sizeof(number[0]); i  )
        printf("number[%i] = %s\n", i, number[i]);

    return 0;
}

char **number = readFile(filename, &len); You get an array holding all the lines of a file. number is a very poor name for this.

You return NULL from readFile in case of an error. You should check for that after calling.

Then you forgot that arrays are not pointers and pointers are not arrays. They behave similar in many places but are very different at the same time.

i < sizeof(number) / sizeof(number[0])

Here number is a pointer and its size of the size of a pointer. Also number[0] is a pointer again. Different type, but same size. What you want is the number of lines which you get from readFile. Use that variable.

This part should look like this:

    char **all_lines = readFile(filename, &len);

    if (all_lines != NULL)
    {
        for (int i = 0; i < len; i  )
            printf("all_lines[%i] = %s\n", i, all_lines[i]);

And you should not forget that you have allocated a lot of memory which you should also free. (This might not strictly be necessary when you terminate your program, but you should keep in mind to clean up behind you)

    if (all_lines != NULL)
    {
        for (int i = 0; i < len; i  )
            printf("all_lines[%i] = %s\n", i, all_lines[i]);

        for (int i = 0; i < len; i  )
            free(all_lines[i];
        free(all_lines);
    }
  •  Tags:  
  • c
  • Related