Callum Thomas Callum Thomas - 6 days ago 4
C Question

Creating a dynamic array of characters using C with pointers to pointers

I'm trying to create an array of pointers to pointers, at least to my understanding. But I'm getting invalid reads and writes with valgrind running.

char **format_file(FILE *infile) {
char **char_array = malloc(20 * sizeof(char*));
int c;
int cUsed = 0;

while ((c = fgetc(infile)) != EOF) {
char_array[cUsed] = c;
cUsed += 1;
}

printf("%s", *char_array);
return char_array;
}


The code works by reading from an already opened file "infile". First I allocated memory for 20 characters with
malloc
, then I'm trying to read the file character by character into the allocated memory array until
EO
F is reached. However, valgrind's output is as follows when I make the code:

==7379== Invalid read of size 1
==7379== at 0x4E7CB36: vfprintf (vfprintf.c:1597)
==7379== by 0x4E85198: printf (printf.c:35)
==7379== by 0x400755: format_file (formatter.c:27)
==7379== by 0x4006C1: main (format265alt.c:21)
==7379== Address 0x6f is not stack'd, malloc'd or (recently) free'd


Line 27 is the
printf
command that valgrind refers to as an invalid read of size 1.

formatter.c is the file containing the
format_file
function, while format265alt.c is a file that calls the formatter.c function and opens the file to be read.

I'm confused by the syntax of **, that is, how do I access and read/write the allocated memory?

I apologize if I have not provided enough information about this problem.

Answer

valgrind complains because you are storing characters beyond the end of the allocated object. The compiler should complain that you are storing characters into an object of the wrong type, use -Wall -W to enable useful warnings.

A char ** is a pointer to a char pointer, it can point to an array of char pointers, also known as an array of strings. You must allocate the array and each of the strings with the appropriate size for the file contents.

Here there are 2 possibilities:

  • the function can either load the whole file into a single string, but there would be no need to return a pointer to a char*, just returning the string (char *) would suffice.

  • the proposed API is more appropriate if the function is to return a pointer to an array of strings, one per line, with an extra NULL at the end, just like the argv array passed as the second argument to the main function.

For this, you must reallocate the string array as more lines are read from the FILE* and each line should be reallocated as it grows. Add a NULL pointer at the end of the string array to indicate its end.

Here is a very inefficient way to do this:

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

char **format_file(FILE *infile) {
      size_t lines = 0;
      char **array = malloc(1 * sizeof(char*));
      size_t pos = 0;
      char *line = malloc(1);
      int c;

      while ((c = getc(infile)) != EOF) {
          if (c == '\n') {
              line[pos] = '\0';
              array = realloc(array, (lines + 2) * sizeof(char *));
              array[lines++] = line;
              line = malloc(1);
              pos = 0;
              continue;
          }
          line = realloc(line, pos + 2);
          line[pos++] = c;
    }
    if (pos > 0) {
        line[pos] = '\0';
        array = realloc(array, (lines + 2) * sizeof(char *));
        array[lines++] = line;
    } else {
        free(line);
    }
    array[lines] = NULL;
    return array;
}
Comments