I read user input from stdin
in plain C
.
The problem is that I want a sane implementation that is robust to errors and restricts the user to a certain input and doesn't suck in terms of complexity.
The function get_strings()
reads input char by char as long there is no new line (\n
), no EOF
and all chars are passing the isalpha()
test. But I want to keep spaces.
Some points that (I think) deserve special attention during the review:
-
- I would love to get rid of the outer while loop which is basically testing wether the user just pressed Enter without any meaningful input.
-
- Do I even need to use ferror()? fgetc already returns EOF when something did go wrong, but I will just stop reading from the stream instead of telling the user something went wrong.
-
- It doesn't happen everytime but it happens: A \n stays in the stdin stream and the next time I want to get meaningful input fgetc() just gets skipped. It doesn't matter here, but it does when I am asking single character Yes/No questions. I can only get rid of this problem with a construct that clears out all previous things that are left in stdin. See the second codeblock for this.
So, am I handling this totally wrong? Are there better practices to embrace? It looks really clunky to me, and clunky is always bad.
/** @brief Contains the dictionary */
static char **strings = NULL;
/** @brief Helps with containing the dicionary */
static char *string;
/* Reads input char by char with fgetc() */
static char *get_strings()
{
char *string = NULL;
char ch;
size_t len = 0;
while (string == NULL && ch != EOF) {
while (EOF != (ch = fgetc(in_stream)) && ch != '\n') {
if (ch != ' ' && isalpha((int)ch) == 0) {
fprintf(stderr, "Only [a-z] is a valid input. | \t"
"| Input another or end with CTRL+D: ");
continue;
}
string = (char*) realloc(string, len+2);
if (string == NULL) {
bail_out(EXIT_FAILURE, "realloc(3) failed");
}
string[len++] = toupper(ch);
if (len >= MAX_DATA) {
bail_out(EXIT_FAILURE, "Input too long\n");
}
}
if (ferror(in_stream)) {
bail_out(EXIT_FAILURE, "Error while reading from stream");
}
}
if(string) {
string[len] = '\0';
} else {
printf("\nFinished dictionary...\n");
}
printf("Added string: %s | Input another or end with CTRL+D: ", string);
return string;
}
/* Saves the returned strings from get_strings() in a linked list */
static void read_dict()
{
int index;
for (index = 0; (string = get_strings()); ++index) {
if (string[0] == '\0') continue;
strings = (char**) realloc(strings, (index+1)*sizeof(*strings));
if (strings == NULL) {
bail_out(EXIT_FAILURE, "realloc(3) failed");
}
strings[index] = string;
}
/* Take a note of how many entries we have yet. */
dict_size = index;
}
Second CodeBlock with a more simple case:
while(1) {
char tmp;
printf("Please enter your guess [a-z]: ");
guess = fgetc(stdin);
/* Jump back to start of loop */
if (guess == '\n') {
continue;
}
/* HERE IS THE CLEAR FOR STDIN
This part really just eats all remaining \ns from the user,
so that later inputs can start uninterrupted. Can I get rid of
it in some better way? */
while((tmp = getchar()) != '\n' && tmp != EOF);
if(!isalpha(guess)) {
fprintf(stderr, "Enter a valid letter [a-z]!\n");
continue;
}
}
stdbool.h
, but don't try to roll your own bools. \$\endgroup\$