Behavior of C scanf() formatting - c

Disclaimer. I've seen tons of questions including almost the exact same code snippets, but none seem to answer this question.
For an entry level CS class we are tasked with making a simple program that takes ID, name and age input from a user and saves it to a file. This was simple enough and I got it working pretty quick. The problem is, to get one part, the name input, working properly, I had to "cheat" my way around a problem I met.
The code snippet in question.
int id, age;
char name[40]={0};
printf("ID: ");
scanf("%i",&id);
printf("Name: ");
scanf("%*c");
scanf("%[^\n]%*c",name);
printf("Age: ");
scanf("%i",&age);
This works fine. But this line annoys me; scanf("%*c"); Its only purpose is disposing of a '\n' character lurking in the stream, probably from the previous input. For some reason I feel like this is cheating or that I'm doing something wrong if I have to use this workaround. Any tips appreciated.

But this line annoys me; scanf("%*c"); Its only purpose is disposing
of a '\n' character lurking in the stream, probably from the previous
input.
scanf() is pretty heavyweight for reading a single character. I would typically go with getchar() for that, and just ignore the result if you don't care what it is.
For your particular case, however, I would go with the suggestion made by #OlafDietsche in comments: insert a space (or a newline or tab) at the beginning of the format string for the next scanf:
scanf(" %[^\n]%*c",name);
That will match any run of zero or more whitespace characters, and thus will eat up your newline, plus any subsequent blank lines and any leading whitespace on the next line that has any non-whitespace. That's the standard behavior for most other field directives anyway, which is why you don't have the same issue with your numeric fields, but it is intentionally not done automatically for %[ and %c fields, so that these can read and return whitespace.
That also means that you don't need to explicitly consume the newline at the end of the name line, either ...
scanf(" %[^\n]",name);
... because it will be consumed automatically by the %i directive in the subsequent scanf() call.

Consider using fget() to read a line of user input #Barmar, then parse.
char buffer[100];
int id, age;
char name[40]={0};
printf("ID: ");
fgets(buffer, sizeof buffer, stdin);
sscanf(buffer, "%i", &id);
printf("Name: ");
fgets(buffer, sizeof buffer, stdin);
sscanf(buffer, "%[^\n]", name);
printf("Age: ");
fgets(buffer, sizeof buffer, stdin);
sscanf(buffer, "%i", &age);
As one's skills improve, add error checking.
printf("ID: ");
if (fgets(buffer, sizeof buffer, stdin) == NULL) Handle_end_of_file();
if (sscanf(buffer, "%i",&id) != 1) Handle_non_numeric_input();
// also research strtol()
printf("Name: ");
if (fgets(buffer, sizeof buffer, stdin) == NULL) Handle_end_of_file();
if (sscanf(buffer, " %99[^\n]",name) != 1) Handle_space_only_name();
// Later, additional code to detect excessively long name

Related

What is the use of scanf("\n"); statement before scanf("%[^\n]%*c", s)?

What is the use of scanf("\n"); statement before scanf("%[^\n]%*c", s) ?
int main()
{
char ch;
char str [100];
char s[100];
scanf("%c",&ch);
printf("%c",ch);
scanf("%s",&str);
printf("\n%s",str);
scanf("\n"); // <<< what is the purpose of this line
scanf("%[^\n]%*c",s);
printf("\n%s",s);
return 0;
}
So what is the use of scanf("\n"); statement before scanf("%[^\n]%*c", s) ?
What is the use of scanf("\n");
The true answer is probably that the original author of this code was flailing, desperately trying to get scanf to work, despite scanf's various foibles.
Other evidence that the original author was having problems:
scanf("%c", &ch);
When reading individual characters, the %c often does not work as expected or as desired. Most of the time, at least in code like this, it is necessary to add a space, like this: " %c".
scanf("%[^\n]%*c", s);
This line, also, is difficult to understand. It is attempting to read one full line of text, a task which scanf is not well-suited for.
Overall the code appears to be attempting to read one single character, followed by one string (not containing whitespace), followed by one full line of text (possibly containing whitespace).
Given its shortcomings (and scanf's shortcomings), I'd say it's not even worth trying to figure out what the original code will do. A considerably cleaner way of accomplishing this task (still using scanf) would be
if(scanf(" %c", &ch) != 1) exit(1);
printf("%c\n",ch);
if(scanf("%99s", str) != 1) exit(1);
printf("%s\n", str);
if(scanf(" %99[^\n]", s) != 1) exit(1);
printf("%s\n", s);
Note these changes:
checking return value of scanf
extra space in " %c", as mentioned
%99s instead of plain %s, to avoid array overflow
no & before str with %s
extra space with %[…] also
length modifier 99 with %[…] also
no %*c after %[…]
(cosmetic/stylistic) printing \n at the end of each line
If you're trying to do anything at all fancy, it's often much easier to just skip scanf, and go with more powerful techniques. I might use something like this:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char chbuf[5];
char ch;
char str [100];
char s[100];
printf("enter a character:\n");
if(fgets(chbuf, 5, stdin) == NULL) exit(1);
ch = *chbuf;
printf("%c\n", ch);
printf("enter a string:\n");
if(fgets(str, 100, stdin) == NULL) exit(1);
str[strcspn(str, "\n")] = 0; /* strip \n */
printf("%s\n", str);
printf("enter a line:\n");
if(fgets(s, 100, stdin) == NULL) exit(1);
s[strcspn(s, "\n")] = 0;
printf("%s\n", s);
}
This simply used fgets to read all input, one line at a time. To fetch a single character, it then grabs the first character of a short line. (Also it's printing explicit prompts, since that helps to avoid confusion.)
One small drawback of fgets is that it always leaves the \n in the buffer. I've used a common albeit somewhat obscure trick to strip it back off; see this question for an explanation.
This modified program works, although it is different from the original in one significant respect: it will allow whitespace in the first string read as well as the second.
Also, the modified program insists that the three inputs be on three separate lines, while the original would have accepted them all on the same line (or on two lines, or on three lines). Whether that is an improvement or a disimprovement, I can't say. :-)
If you want to limit yourself to a subset of scanf's full complexity, using simple invocations of it for the simple uses it's well-suited for, and avoiding it for the more complicated problems that it's dreadfully painful to use for, you might read the suggestions at this answer.
After this incorrect call of scanf
scanf("%s",&str);
where the second parameter shall be
scanf("%s",str);
the input buffer can contain the new line character '\n' and the next call of scanf
scanf("%[^\n]%*c",s);
can read as a result an empty string.
So this call
scanf("\n");
is an attempt to remove the new line character from the input buffer.
However it will be better just to write
scanf(" %[^\n]%*c",s);
See the leading space in the format string. It allows to skip white space characters in the input buffer.

Error while input 2 strings, int and float in C from keyboard

I have some issues whith inputs from keyboard in C.
My input shoud be:
string with spaces
string with spaces
int
float
This declaration of input worked, but if first I enter int and float and then strings:
char title[60], author[50];
int year;
float rate;
char temp;
scanf("%d", &year);
scanf("%f", &rate)
scanf("%c",&temp);
scanf ("%[^\n]", title);
scanf("%c",&temp);
scanf ("%[^\n]", author);
With another order input behaves unpredictably.
I need a fast and efficient way to read inputs in the exactly order I described above.
scanf leaves a newline character (\n) in the buffer, for chained scanfs, when reading characters or strings you'll need to get rid of those, a simple solution is to add a space before the specifier, wether it's %s, %c or %[ ].
For your code, mixing the scanfs around:
scanf(" %59[^\n]", title);
scanf("%f", &rate);
scanf(" %49[^\n]", author);
scanf("%d", &year);
This should parse all your inputs correctly. As would any other reordering, provided that you then input the values in the correct order.
Live demo
I added size limits to the strings in scanf, you should always do this to avoid buffer overflow.
Verifying the return values of scanf is also advised to make sure the inputs were read correctly, e.g.:
if(scanf(" %59[^\n]", title) == 1){
//Ok
}
else{
//Deal with bad input
}
Note that if you want a more robust way to parse inputs that allow you to perform complete input checks, you shoud use fgets generally and then strtol / strtof to parse the numerical inputs.
With another order input behaves unpredictably.
This is due to the left-over '\n' after reading a number.
Rather than patch broken scanf(), read a line of user input with fgets() and then parse.
Easy and simply to re-order parsing as needed.
char buffer[100] = { 0 };
char title[60] = { 0 };
char author[50] = { 0 };
int year;
float rate;
fgets(buffer, sizeof buffer, stdin);
year = atoi(buffer);
fgets(buffer, sizeof buffer, stdin);
rate = atof(buffer);
fgets(buffer, sizeof buffer, stdin);
sscanf(buffer, "%59[^\n]", title);
fgets(buffer, sizeof buffer, stdin);
sscanf(buffer, "%49[^\n]", author);
Robust code would employ checks of fgets(), sscanf(), long lines and use strtol() and strtof().
scanf ("%[^\n]%*c", title);
scanf ("%[^\n]%*c", author);
scanf("%d", &year);
scanf("%f", &rate);
with this %*c, it reads newline character and here used * indicates that this newline character is discarded.
it works fine.

Using getchar() after scanf()? [duplicate]

This question already has answers here:
scanf() leaves the newline character in the buffer
(7 answers)
Closed 5 years ago.
I have two questions:
why only when i do space in "%d " --> scanf("%d ", &num); it works?
I tried fflush(stdin) \ _flushall() between the scnaf and the gets and it doesn't works, it skips the gets.
When I do the space, it first does scanf then the gets and after that it print the number and print the string.
void main()
{
char ch, str[10];
int num;
printf("Enter your number : ");
scanf("%d ", &num);
printf("%d\n",num);
gets(str);
puts(str);
system("pause");
}
why only when i do space in "%d " --> scanf("%d ", &num); it works?
scanf("%d", &num); without a space after the "%d", stops scanning after reading a number. So with input 123Enter, the '\n' remains in stdin for the next input function like the now non-standard gets(). gets() reads that single '\n' and returns. By adding a space, scanf("%d ", &num); consumes the white-space after the number and does not return until non-white-scape is entered after the number.
When I do the space, it first does scanf then the gets and after that it print the number and print the string.
By adding a space, scanf("%d ", &num); does not return until non-white-space is entered after the number (as in 'a' in the following). Since stdin is usually line buffered, this means input of 2 lines needs to first occur. 123Enter abcEnter.
Recommend to instead use fgets() to read a line of user input.
char str[10*2]; // no need for such a small buffer
int num;
printf("Enter your number : ");
fflush(stdout);
fgets(str, sizeof str, stdin);
sscanf(str, "%d", &num);
printf("%d\n",num);
printf("Enter data : ");
fflush(stdout);
fgets(str, sizeof str, stdin);
fputs(str, stdout);
More robust code would check the results of fgets(), sscanf() and use strtol() rather than sscanf().
The C FAQ covers all these problems with scanf. See Why does everyone say not to use scanf? What should I use instead? and associated entries. Generally you'll use fgets followed by processing the resulting line such as with sscanf and checking that sscanf succeeded. This avoids leaving unparsed input and risking an infinite loop.
int number;
char line[255];
fgets( line, sizeof(line), stdin );
if( sscanf( line, "%d", &number ) != 1 ) {
fputs("That doesn't look like a number.\n", stdin);
}
Note that fgets will read to a newline or as much as your buffer can hold. If the line is larger than your buffer, it might only read part of the line. Next read from input will get the rest of the line. There's ways to avoid this, such as the POSIX getline function, but at least you don't wind up in an infinite loop.
Let's decipher some comments.
Do not ever use gets. Use fgets.
The reason you don't use gets is because there's no way to limit how much is read from stdin. This means the user can overflow the buffer causing havoc.
char buffer[32];
// What the line is more than 31 characters?
gets(buffer);
fgets() takes the size of the buffer and will read that many characters at most. This prevents a buffer overflow.
char buffer[32];
// If there's more than 31 characters it will stop reading.
// The next read of stdin will get the rest of the line.
fgets( buffer, sizeof(buffer), stdin );
"There's no gets() function in C."
Yes, there is a gets() function in C.
Yes, there isn't a gets() function in C.
It depends on which C you're talking about.
Some people when they say "C" mean C11, the current standard. Others when they say "C" mean C99 the previous standard. Some still adhere to C90, the original standard. There is a gets() function in C90. It was deprecated in C99. It was removed from the language in C11.
C compilers and documentation lag very, very, very far behind the standard. Many are still working on full support of C99. If you work to C11 you're going to be very surprised by the lack of support. If you want your code to work on most any compiler, write to C99.
Anyway, don't use gets.

Why is my second "scanf" being skipped?

This is in my main function..
printf("How many marking components in the course? ");
scanf("%d", &numberOfComponents );
for (int i=0; i<numberOfComponents; i++){
char c[MAX_STR];
printf("enter next component name: ");
fgets(c, sizeof(c), stdin);
scanf(c, " %c", &c);
Component comp;
initComp(&comp, c);
class.comps[i] = comp;
}
printf("How many marking schemes? ");
scanf(" %d", &numberOfSchemes);
I have tried the white space but it still persists
You should test that your input operations worked; things go haywire when you don't.
This call to scanf():
scanf(c, " %c", c);
should (as chux diagnoses in his answer) be written differently, but I think it should be more like:
char filler;
if (scanf(" %c", &filler) != 1)
…report problem…
The original version uses the line that was just read as the format string, and it is almost certain to fail matching the second input. If you simply use scanf(" %c", c), which is the simplest edit, then you overwrite the first character of the line of input with the next non-blank character.
There's then the question of why you're making the user enter the extra data after the component name. They have to enter something. On the next fgets(), the code will read after the first character up to the next newline. So, if you typed:
component-1
component-2
The first fgets() would read component-1 and the newline; the scanf() as amended would read the c of component-2 into c[0], and then the next fgets() would read omponent-2 plus the newline into c on the next iteration.
You could see more of what is going on by adding code to print what you read as you read it:
printf("Line 1: [[%s]]\n", c); // after the fgets()
printf("Line 2: [[%s]]\n", c); // after the scanf()
This is one of the most basic of debugging techniques; echo what you've read to make sure the program got the data you think it got.
Mixing fgets() with scanf() often causing problems.
scanf("%d"... leaves any following white-space like '\n' in stdin for fgets() to get. The same happens after scanf(c, " %c", &c); (which likely was meant to be scanf(" %c", c); or sscanf(c, " %c", c);).
scanf("%d", &numberOfComponents );
...
fgets(c, sizeof(c), stdin);
Recommend using only fgets() to get user input and use sscanf(), strtol(), etc. to parse that input.
fgets(c, sizeof(c), stdin);
sscanf(c, "%d", &numberOfComponents );
....
fgets(c, sizeof(c), stdin);
// I do not think you need the following line of code.
// Besides it is UB as it attempts to scan and save into the same buffer.
// sscanf(c, "%c", &c );
Error checking of the result of fgets(), sscanf() omitted, but still good to do.

fscanf reading newline character

I have been slowly tracking down my error for my program. I have narrowed it down to this.
I have a user input
fscanf(stdin, "%c %c %d", &car, &dir, &amount);
the first time I access it it works fine, correctly reading in the values. The second time in the loop it reads a \n into car instead of the char I give it. it then reads what should have been in car into dir. amount reads correctly. As car is passed to other functions for counting I eventually end up with a segfault.
Is it reading in the \n from the previous line or something?
The "%c" conversion specifier does not do the usual whitespace trimming.
Try adding a space before the first conversion specifier
if (fscanf(stdin, " %c %c %d", &var, &dir, &amount) != 3) { /* error */ }
Or, maybe better, read a full line and parse it within your program
char buf[1000];
fgets(buf, sizeof buf, stdin);
parse(buf);

Resources