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, thengetline()
will allocate a buffer for storing the line. This buffer should befree
d by the user program even ifgetline()
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);
}