Reputation: 1903
I have a program where the user enters two inputs. Being that I can't control what the user enters, the user can go past the fixed size of the array. Since fgets()
appends retains a newline to the end before the null character, in the event that a newline cannot fit when the user goes beyond the intended size, the null character truncates the input. Does the newline character when the user hits enter still exist in the input stream? If so, is this the reason why fgets()
skips the second time because of the newline from the first input?
#include <stdio.h>
int main(){
char str[5];
fgets(str,5,stdin);
printf("Output:%s",str);
fgets(str,5,stdin);
printf("Output:%s",str);
return 0;
}
Example Input
ABCDE\n
Output
Output:ABCDOutput:E
After reading this SO answer fgets() isn't prompting user a second time
, the issue seems to be not flushing the input stream via fflush(stdin)
, but I've heard conflicting information saying as this leads to undefined behavior. My last question is, what would be the appropriate way to clear the input stream if it's the retained newline that's causing issues?
Upvotes: 2
Views: 2687
Reputation: 180286
the user can go past the fixed size of the array. Since
fgets()
appends a newline to the end before the null character
No, it does not. It writes characters read from the input into the provided buffer, up to and including the first newline, or until the specified buffer size is exhausted (less one byte for the string terminator), or until an error occurs or the end of the stream is reached, whichever comes first. The newline is not invented by fgets()
; it comes from the input.
, in the event that a newline cannot fit when the user goes beyond the intended size, the null character truncates the input. Does the newline character when the user hits enter still exist in the input stream?
All characters entered by the user and not copied into the buffer remain waiting to be read in the stream. That will include the newline, if the user entered one.
If so, is this the reason why fgets()skips the second time because of the newline from the first input?
fgets()
does not skip, but it does pick up where the previous call left off transferring characters from input to the buffer. No characters are lost. That means that the second call returns part of the first input line if the first call did not return the whole thing. You need to account one way or another for the possibility that the input does not conform to your line-length expectations.
the issue seems to be not flushing the input stream via
fflush(stdin)
,
No, it isn't. Flushing is for sending buffered output to the underlying output device. Flushing an input stream produces undefined behavior. In principle, that could manifest as a buffer dump, and a given implementation might even specify such behavior, but you don't want that because there may be more data buffered than you want to get rid of.
but I've heard conflicting information saying as this leads to undefined behavior. My last question is, what would be the appropriate way to clear the input stream if it's the retained newline that's causing issues?
You read from the input until you've read the newline. There are plenty of I/O functions to choose from to accomplish this. fgets()
itself might prove convenient, since you're already using it:
char str[5];
if (fgets(str, 5, stdin)) {
printf("Output:%s", str);
// read and consume the tail of the line, if any (overwrites str)
while (!strchr(str, '\n') && fgets(str, 5, stdin)) { /* empty */ }
}
Upvotes: 5
Reputation: 47952
Being that I can't control what the user enters...
No, you cannot.
the user can go past the fixed size of the array.
Right. This is always a concern. However, In general you'll want to arrange things so that this rarely happens.
For example, if you really want to limit the user to (say) a 4-character-long input string, let him type whatever he wants, then see how much he typed, and if it was more than your limit, print a nice error message or something. But I do not recommend calling fgets(str, 5, stdin)
if you're expecting 4 characters of input plus a newline, because it's just way too hard to recover when (not if) the user types too much.
in the event that a newline cannot fit when the user goes beyond the intended size, the null character truncates the input. Does the newline character when the user hits enter still exist in the input stream?
Absolutely yes.
If so, is this the reason why fgets()skips the second time because of the newline from the first input?
Pretty much yes.
I recommend allocating a much bigger buffer, and then proceeding something like this:
char inpbuf[512);
if(fgets(inpbuf, sizeof(inpbuf), stdin) == NULL) {
fprintf(stderr, "end of file\n");
return;
}
char *p = strrchr(inpbuf, '\n');
if(p == NULL) {
fprintf(stderr, "looks like you typed *way* too much\n");
return;
}
*p = '\0'; /* erase the \n */
if(strlen(inpbuf) > 4) {
fprintf(stderr, "you typed too much (max 4)\n");
return;
}
strcpy(str, inpbuf);
printf("Output:%s", str);
One glitch with this code as written, though: if the user hits the end-of-file key (control-D on Unix/Linux) before hitting Return, you'll falsely get the "looks like you typed way too much" message.
Upvotes: 4
Reputation: 153508
fgets()
reads until
1) New-line
2) Buffer is full
3) End-of-file
4) Input error (rare)
This code reads and takes care of #3 & #4
#define N 5
char buf[N];
if (fgets(buf, sizeof buf, stdin) == NULL) {
// Handle EOF or Error
return EOF;
}
To distinguish if a '\n'
is present ... (#2 from #1)
// look for a lack of \n
if (strchr(buf, '\n') == NULL) {
And if so, read until it is found or EOF
.
int ch;
while ((ch = fgetc(stdin)) != '\n' && ch != EOF);
}
--
Do not use the following code. It can be exploited by reading a null character as the first character.
size_t len = strlen(buf);
if (buf[len - 1] != '\n') { // bad way to detect \n
Could use
if (len > 0 && buf[len - 1] != '\n') { // Good
Upvotes: 5
Reputation: 223972
If the string read by fgets
doesn't end in a newline, you know it's still in the buffer. In that case, call getchar
in a loop until you get a newline.
fgets(str,5,stdin);
printf("Output:%s",str);
if (strchr(str, '\n') == NULL) {
int c;
while ((c = getchar()) != EOF && c != '\n');
}
fgets(str,5,stdin);
printf("Output:%s",str);
Upvotes: 3