When wanting to read and store an unknown number of anything, you must provide valid storage for whatever you read. You generally do so by allocating a block of memory using malloc(), calloc() or realloc() and assigning the beginning address for the newly allocated block of memory to a pointer of the appropriate type.
To grow the size of the block of memory as needed, you keep track of the number of objects you have currently allocated storage for and the number used. When used == allocated you must realloc() additional storage using a temporary pointer with realloc().
The reason you use a temporary pointer is when realloc() fails it returns NULL. If you are assigning to your actual pointer instead of a temporary, then you overwrite the address to the current block of memory held by your pointer with NULL. That creates a memory-leak because that block of memory can no longer be freed.
In the event realloc() fails, the temporary pointer is set to NULL, but the address to the current block of memory stored in your original pointer is not freed and it remains valid holding the data collected up to the point of failure. That data is valid and usable, so there is no need to exit on the realloc() failure.
You can grow your memory by whatever amount you like each time you realloc(), though it is best to avoid a reallocation for every newly added object. (that's inefficient even if the memory is mmaped). One scheme that provides reasonable balance between the growth of memory and the number of reallocations required is simply to double the size currently allocated each time a reallocation is needed. When you are done with the memory you have allocated, don't forget to free() it.
A short example that reads integers from the filename provided as the first argument on the command line (or reads from stdin by default if no argument is provided) can be written like:
#include <stdio.h>
#include <stdlib.h>
#define NINTS 1 /* if you need a constant, #define one (or more) */
int main (int argc, char **argv) {
int val, *arr = NULL; /* val to read and ptr to memory */
size_t allocated = 0, used = 0; /* number ints allocated & used */
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("fopen-fp");
return 1;
}
while (fscanf (fp, "%d", &val) == 1) { /* for each int read from file */
if (used == allocated) { /* check if realloc needed */
if (!allocated) { /* not allocated set to NINTS */
allocated = NINTS;
}
/* always realloc using a temproary pointer */
void *tmp = realloc (arr, 2 * allocated * sizeof *arr);
if (!tmp) { /* validate EVERY allocation */
perror ("realloc-tmp");
break; /* break on error, arr still OK */
}
arr = tmp; /* set arr to newly allocated block */
allocated *= 2; /* update the number of ints allocated */
}
arr[used++] = val; /* add value to block of memory */
}
for (size_t i = 0; i < used; i++) /* output results */
printf ("%3zu - %d\n", i, arr[i]);
free (arr); /* don't forget to free what you allocate */
}
(note: realloc() can be used for the first allocation so long as the pointer is set NULL)
Example Use/Output
Using your example you could do:
$ echo "200 53 65 98 183 37 122 14 124 65 67" | ./bin/dynarrint
0 - 200
1 - 53
2 - 65
3 - 98
4 - 183
5 - 37
6 - 122
7 - 14
8 - 124
9 - 65
10 - 67
Memory Use/Error Check
In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.
It is imperative that you use a memory error checking program to ensure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.
For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.
$ echo "200 53 65 98 183 37 122 14 124 65 67" | valgrind ./bin/dynarrint
==12405== Memcheck, a memory error detector
==12405== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==12405== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==12405== Command: ./bin/dynarrint
==12405==
0 - 200
1 - 53
2 - 65
3 - 98
4 - 183
5 - 37
6 - 122
7 - 14
8 - 124
9 - 65
10 - 67
==12405==
==12405== HEAP SUMMARY:
==12405== in use at exit: 0 bytes in 0 blocks
==12405== total heap usage: 6 allocs, 6 frees, 5,240 bytes allocated
==12405==
==12405== All heap blocks were freed -- no leaks are possible
==12405==
==12405== For counts of detected and suppressed errors, rerun with: -v
==12405== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Always confirm that you have freed all memory you have allocated and that there are no memory errors.