End-of-file
When nothing is read, code returns an allocated ""
. That is indistinguishable from reading only a '\n'
as that returns ""
too.
To function like other standard input functions, code should return some indication, like NULL
:
Infinite loop
When end-of-file occurs getchar()
returns EOF
and code never exits the loop.
// Infinite loop
while ((ch = getchar())) {
if (ch == 10 || ch == 0)
break;
Redundant test
Code test for the null character twice.
257
getchar()
returns 257 different values (EOF
and all unsigned char
). Saving that in a char
loses information.
Use int ch
.
Repetitive calls to re-allocate
realloc(ptr, len + 1);
calls realloc()
every iteration. A more efficient common approach approximately doubles the size each loop with a right-size allocation in the end.
Ending input on a null character
This is different from fgets(), scanf()
.
[Edit]
Some sample code to illustrate these ideas and a few more. Only lightly tested.
Header:
/*
* Allocate as needed to form a string from user input.
*
* Return NULL on
* * End of file with no input
* * Input error
* * Out of memory
* Caller to use feof(), ferror() to distinguish.
*
* Otherwise return allocated buffer.
* Caller to free buffer.
*/
char* line_alloc(void);
Source
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#define LINE_ALLOC_MIN 63 /* Some power-of-2 minus 1 */
static char* line_alloc_helper(char *buf, size_t *len_max_ptr) {
// Is input longer than supportable size?
if (*len_max_ptr > SIZE_MAX / 2 - 1) {
free(buf);
return NULL;
}
size_t sz = *len_max_ptr + 1; // Old buffer size
sz = 2 * sz + 1; // New buffer size
if (sz < LINE_ALLOC_MIN) {
sz = LINE_ALLOC_MIN;
}
char *buf_new = realloc(buf, sz);
if (buf_new == NULL) {
free(buf);
return NULL;
}
*len_max_ptr = sz - 1;
return buf_new;
}
char* line_alloc(void) {
size_t len = 0;
size_t len_max = 0;
char *buf = NULL;
int ch;
bool nothing_read = true;
while ((ch = getchar()) != EOF) {
nothing_read = false;
if (ch == '\n' || ch == '\0') { // Remove ch == '\0' if desired.
break;
}
if (len >= len_max) {
buf = line_alloc_helper(buf, &len_max);
if (buf == NULL) {
return NULL;
}
}
buf[len++] = (char) ch;
}
if (ch == EOF) {
if (nothing_read || !feof(stdin)) { // Note 1
free(buf);
return NULL;
}
}
char *buf_right_size = realloc(buf, len + 1);
if (buf_right_size == NULL) {
free(buf);
} else {
buf_right_size[len] = '\0';
}
return buf_right_size;
}
/* Note 1: Better as !feof() than ferror() to identify
end-of-file with input error set from prior activity. */
Test:
#include <string.h>
int main(void){
char * buf;
while ((buf = line_alloc()) != NULL) {
size_t len = strlen(buf);
printf("%2zu <%s>\n", len, buf);
free(buf);
fflush(stdout);
}
}
Future improvements/ideas:
Support FILE *input_stream
.
Might as well use local buffer, say 127 bytes, and them malloc()
during the right-size step for short lines. Transition to allocated buffers for long lines. Adds complexity, but makes for only one allocation for common lines.
Cope with '\r'
'\n'
pairs that come up from reading foreign text files.
Provide caller with len
to better support lines with embedded null characters.
Provide an upper bound rather than SIZE_MAX
for max line length to help prevent hacker abuse of code.
getline()
? \$\endgroup\$getline()
is not part of the Standard C library although its functional design is worth reviewing as a guide here. \$\endgroup\$std::string
is also limited by the computer's memory. \$\endgroup\$