You can quite easily implement such a function yourself.
Let's steal the interface (mostly) from POSIX.1 getline()
, as it is known to work well. So:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
size_t get_ints(int **dataptr, size_t *sizeptr, FILE *in)
{
int *data;
size_t size, used = 0;
int value;
/* Invalid parameters? None may be NULL. */
if (!dataptr || !sizeptr || !in) {
errno = EINVAL;
return 0;
}
/* Has an error in the input stream already occurred? */
if (ferror(in)) {
errno = EIO;
return 0;
}
/* Has data already been allocated? */
if (!*dataptr || !*sizeptr) {
/* No, not yet. */
*dataptr = NULL;
*sizeptr = 0;
}
data = *dataptr;
size = *sizeptr;
/* Read loop. */
while (1) {
/* Try reading one int. */
if (fscanf(in, " %d", &value) != 1)
break;
/* Have one. Make sure data array has room for value. */
if (used >= size) {
/* Reallocation policy. We need at least size = used + 1,
but realloc() calls are relatively slow, so we want
to allocate in larger chunks. This is just one typical
policy. */
if (used < 255)
size = 256; /* Minimum allocation is 256 ints. */
else
if (used < 1048575)
size = (3 * used) / 2; /* Grow by 50% ... */
else
size = (used | 1048575) + 1048577; /* up to 1048576, after which round up to next multiple of 1048576. */
data = realloc(data, size * sizeof data[0]);
if (!data) {
/* Note: original data in *data still exists! */
errno = ENOMEM;
return 0;
}
*dataptr = data;
*sizeptr = size;
}
/* Append to array. */
data[used++] = value;
}
/* An actual I/O error? */
if (ferror(in)) {
errno = EIO;
return 0;
}
/* No, either an end of stream, or the next stuff
in the stream is not an integer.
If used == 0, we want to ensure the caller knows
there was no error. For simplicity, we avoid that
check, and simply set errno = 0 always. */
errno = 0;
return used;
}
The interface to the above get_ints()
function is simple: it takes a pointer to a dynamically allocated array, a pointer to the size (in ints) allocated for that array, and the stream handle, and returns the number of ints read from the stream. If an error occurs, it returns 0 with errno
set to indicate the error. (getline()
returns -1
instead.)
If no error occurs, this particular implementation sets errno = 0
, but do note that this behaviour is uncommon; normally, you can only expect errno
to have a valid error number when the function returns the error code (usually -1, but zero for this function).
The way you use this function is very easy. For example, let's assume you want to read an array of ints from the standard input:
int main(void)
{
int *iarray = NULL; /* No array allocated yet, */
size_t isize = 0; /* so allocated size is zero, and */
size_t icount = 0; /* no ints in it yet. */
icount = get_ints(&iarray, &isize, stdin);
if (!icount) {
/* No ints read. Error? */
if (errno)
fprintf(stderr, "Error reading from standard input: %s.\n", strerror(errno));
else
fprintf(stderr, "No integers in standard input.\n");
return EXIT_FAILURE;
}
printf("Read %zu integers from standard input.\n", icount);
/*
* Do something with the integers...
*/
/* Discard the dynamically allocated array. */
free(iarray);
iarray = NULL;
isize = 0;
icount = 0;
return EXIT_SUCCESS;
}
Note that as long as you initialize your iarray = NULL
and isize = 0
when declaring it, the get_ints()
function will allocate as much memory as is needed for the ints it reads. It is also then always safe to do free(iarray); iarray = NULL; isize = 0;
to discard the array, even if iarray
was NULL, because free(NULL)
is safe to do (does nothing).
This is an excellent way of doing memory management in C. If you do it this way -- initialize your pointers to NULL
, then free()
them and reset them to NULL
after they are no longer needed -- your programs won't have memory leaks or crash due to use-after-free or similar bugs. Many instructors won't bother, because they erroneously think that sort of carefulness can be tacked on later, if necessary.
(Do note, however, that even in the above program, in the error cases where the program is about to abort/exit/return, there is no such cleanup done. This is because the operating system will release all resources automatically. [Except for shared memory and filesystem objects.] Simply put, if your program is guaranteed to exit, it is not necessary to free dynamically allocated memory.)