Here is one way you might approach this problem. If you are going to dynamically allocate storage for an unknown number of words of unknown length, you can start with a buffSize
that seems reasonable, allocate that much space for the readLine
buffer, and grow this memory as needed. Similarly, you can choose a reasonable size for the number of words expected, and grow word storage as needed.
In the program below, myArray
is a pointer to pointer to char
. arrSize
is initialized so that pointers to 100 words may be stored in myArray
. First, readLine
is filled with an input line. If more space than provided by the initial allocation is required, the memory is realloc
ed to be twice as large. After reading in the line, the memory is again realloc
ed to trim it to the size of the line (including space for the '\0'
).
strtok_r()
breaks the line into tokens. The pointer store
is used to hold the address of the memory allocated to hold the word, and then word
is copied into this memory using strcpy()
. If more space is needed to store words, the memory pointed to by myArray
is realloc
ed and doubled in size. After all words have been stored, myArray
is realloc
ed a final time to trim it to its minimum size.
When doing this much allocation, it is nice to write functions which allocate memory and check for errors, so that you don't have to do this manually every allocation. xmalloc()
takes a size_t
argument and an error message string. If an allocation error occurs, the message is printed to stderr
and the program exits. Otherwise, a pointer to the allocated memory is returned. Similarly, xrealloc()
takes a pointer to the memory to be reallocated, a size_t
argument, and an error message string. Note here that realloc()
can return a NULL
pointer if there is an allocation error, so you need to assign the return value to a temporary pointer to avoid a memory leak. Moving realloc()
into a separate function helps protect you from this issue. If you assigned the return value of realloc()
directly to readLine
, for example, and if there were an allocation error, readLine
would no longer point to the previously allocated memory, which would be lost. This function prints the error message and exits if there is an error.
Also, you need to free
all of these memory allocations, so this is done before the program exits.
This method is more efficient than realloc
ing memory for every added character in the line, and for every added pointer to a word in myArray
. With generous starting values for buffSize
and arrSize
, you may only need the initial allocations, which are then trimmed to final size. Of course, there are still the individual allocations for each of the individual words. You could also use strdup()
for this part, but you would still need to remember to free those allocations as well.Still, not nearly as many allocations will be needed as when readLine
and myArray
are grown one char
or one pointer at a time.
#define _POSIX_C_SOURCE 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void * xmalloc(size_t size, char *msg);
void * xrealloc(void *ptr, size_t size, char *msg);
int main(void)
{
char **myArray;
size_t buffSize = 1000;
size_t arrSize = 100;
size_t charIndex = 0;
size_t wordIndex = 0;
char *readLine;
char *inLine;
char *word;
char *rest;
char *store;
/* Initial allocations */
readLine = xmalloc(buffSize, "Allocation error: readLine");
myArray = xmalloc(sizeof(*myArray) * arrSize,
"Allocation error: myArray\n");
/* Get user input */
printf("\n enter a line of input:\n");
int c;
while ((c = getchar()) != '\n' && c != EOF) {
if (charIndex + 1 >= buffSize) { // keep room for '\0'
buffSize *= 2;
readLine = xrealloc(readLine, buffSize,
"Error in readLine realloc()\n");
}
readLine[charIndex++] = c;
}
readLine[charIndex] = '\0'; // add '\0' terminator
/* If you must, trim the allocation now */
readLine = xrealloc(readLine, strlen(readLine) + 1,
"Error in readLine trim\n");
/* Tokenize readLine */
inLine = readLine;
while((word = strtok_r(inLine, " \n", &rest)) != NULL) {
store = xmalloc(strlen(word) + 1, "Error in word allocation\n");
strcpy(store, word);
if (wordIndex >= arrSize) {
arrSize *= 2;
myArray = xrealloc(myArray, sizeof(*myArray) * arrSize,
"Error in myArray realloc()\n");
}
myArray[wordIndex] = store;
wordIndex++;
inLine = NULL;
}
/* You can trim this allocation, too */
myArray = xrealloc(myArray, sizeof(*myArray) * wordIndex,
"Error in myArray trim\n");
/* Print words */
for(size_t i = 0; i < wordIndex; i++){
printf("%s ",myArray[i]);
}
printf("\n");
/* Free allocated memory */
for (size_t i = 0; i < wordIndex; i++) {
free(myArray[i]);
}
free(myArray);
free(readLine);
return 0;
}
void * xmalloc(size_t size, char *msg)
{
void *temp = malloc(size);
if (temp == NULL) {
fprintf(stderr, "%s\n", msg);
exit(EXIT_FAILURE);
}
return temp;
}
void * xrealloc(void *ptr, size_t size, char *msg)
{
void *temp = realloc(ptr, size);
if (temp == NULL) {
fprintf(stderr, "%s\n", msg);
exit(EXIT_FAILURE);
}
return temp;
}