The one approach you did not consider, is probably the most portable one: write your own "read one token from a FILE stream, and convert it to a long; but never cross a newline boundary" function:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
/* Reads one decimal integer (long) from 'input', saving it to 'to'.
Returns: 0 if success,
EOF if end of input,
'\n' if newline (end of line),
'!' if the number is too long, and
'?' if the input is not a number.
*/
int read_long(FILE *input, long *to)
{
char token[128], *end;
size_t n = 0;
long value;
int c;
/* Consume leading whitespace, excluding newlines. */
do {
c = fgetc(input);
} while (c == '\t' || c == '\v' || c == '\f' || c == ' ');
/* End of input? */
if (c == EOF)
return EOF;
/* Newline? */
if (c == '\n' || c == '\r') {
/* Do not consume the newline character! */
ungetc(c, input);
return '\n';
}
/* Accept a single '+' or '-'. */
if (c == '+' || c == '-') {
token[n++] = c;
c = fgetc(input);
}
/* Accept a zero, followed by 'x' or 'X'. */
if (c == '0') {
token[n++] = c;
c = fgetc(input);
if (c == 'x' || c == 'X') {
token[n++] = c;
c = fgetc(input);
}
}
/* Accept digits. */
while (c >= '0' && c <= '9') {
if (n < sizeof token - 1)
token[n] = c;
n++;
c = fgetc(input);
}
/* Do not consume the separator. */
if (c != EOF)
ungetc(c, input);
/* No token? */
if (!n)
return '?';
/* Too long? */
if (n >= sizeof token)
return '!';
/* Terminate token, making it a string. */
token[n] = '\0';
/* Parse token. */
end = token;
errno = 0;
value = strtol(token, &end, 0);
if (end != token + n || errno != 0)
return '?';
/* Save value. */
if (to)
*to = value;
return 0;
}
To advance to the next line, you can use e.g.
/* Skips the rest of the current line,
to the beginning of the next line.
Returns: 0 if success (next line exists, although might be empty)
EOF if end of input.
*/
int next_line(FILE *input)
{
int c;
/* Skip the rest of the current line, if any. */
do {
c = fgetc(input);
} while (c != EOF && c != '\n' && c != '\r');
/* End of input? */
if (c == EOF)
return EOF;
/* Universal newline support. */
if (c == '\n') {
c = fgetc(input);
if (c == EOF)
return EOF;
else
if (c == '\r') {
c = fgetc(input);
if (c == EOF)
return EOF;
}
} else
if (c == '\r') {
c = fgetc(input);
if (c == EOF)
return EOF;
else
if (c == '\n') {
c = fgetc(input);
if (c == EOF)
return EOF;
}
}
ungetc(c, input);
return 0;
}
To read the longs on each line, you can use a dynamically resized buffer, shared across lines:
int main(void)
{
long *field_val = NULL;
size_t field_num = 0;
size_t field_max = 0;
int result;
do {
/* Process the fields in one line. */
field_num = 0;
do {
/* Make sure the array has enough room. */
if (field_num >= field_max) {
void *temp;
/* Growth policy; this one is linear (not optimal). */
field_max = field_num + 5000;
temp = realloc(field_val, field_max * sizeof field_val[0]);
if (!temp) {
fprintf(stderr, "Out of memory.\n");
return EXIT_FAILURE;
}
field_val = temp;
}
result = read_long(stdin, field_val + field_num);
if (result == 0)
field_num++;
} while (result == 0);
if (result == '!' || result == '?') {
fprintf(stderr, "Invalid input!\n");
return EXIT_FAILURE;
}
/*
* You now have 'field_num' longs in 'field_val' array.
*/
/* Proceed to the next line. */
} while (!next_line(stdin));
free(field_val);
field_val = NULL;
field_max = 0;
return EXIT_SUCCESS;
}
While reading input character by character is not the most efficient way (it tends to be slightly slower than reading eg. line by line), it is compensated by its versatility.
For example, the above code works for any newline convention (CRLF or \r\n
, LFCR or \n\r
, CR \r
, and LF \n
) (but in Windows you'll want to specify the "b"
flag for fopen()
to stop it from making its own newline mangling).
The read-field-by-field approach is also easily extended to e.g. CSV format, including its peculiar quoting rules, and even embedded newlines.