So I want to write a program to check if user enter. Say requirement is 4 digits number, if user enters 5 then program keeps asking user to renter exactly 4 digit number.
I got the working code like this: basically use scanf
to read in a string value, then use strlen
to count number of digits. If user enters the correct digit, then I use atoi
to convert that string into an int
, which I will use for later.
Say requirement is 4 digit number:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
int digit, mynumber;
int digit = 5;
char str[5];
/* Checking if enter the correct digit */
do {
printf("Enter a %d digit number\n", digit);
scanf("%s", &str);
if (strlen(str) != digit) {
printf("You entered %d digits. Try again \n", strlen(str));
} else {
printf("You entered %d digits. \n", strlen(str));
printf("Converting string to num.....\n");
mynumber = atoi(str);
printf("The number is %d\n", mynumber);
}
} while (strlen(str) != digit);
return 0;
}
I want to modify this a bit. Instead of doing char str[5]
for 5 digit string. I want to try a dynamic array.
So in place of char str[5]
, I do this:
char *str;
str = malloc(sizeof(char) * digit);
Running this through the code gives seg fault. Can anyone help me with this?
This is the complete code with the issue
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
int mynumber;
int digit = 5;
char *str;
str = malloc(sizeof(char) * digit);
/* Checking if enter the correct digit */
do {
printf("Enter a %d digit number\n", digit);
scanf("%s", &str);
if (strlen(str) != digit) {
printf("You entered %d digits. Try again \n", strlen(str));
} else {
printf("You entered %d digits. \n", strlen(str));
printf("Converting string to num.....\n");
mynumber = atoi(str);
printf("The number is %d\n", mynumber);
}
} while (strlen(str) != digit);
return 0;
}
While you can use the formatted input function scanf
to take your input as a string, scanf
is full of a number of pitfalls that can leave stray characters in your input stream (stdin
) depending on whether a matching-failure occurs. It also has the limitation using the "%s"
conversion specifier of only reading up to the first whitespace. If your user slips and enters "123 45"
, you read "123"
, your tests fail, and "45"
are left in stdin
unread, just waiting to bite you on your next attempted read unless you manually empty stdin
.
Further, if you are using "%s"
without the field-width modifier -- you might as well be using gets()
as scanf
will happily read an unlimited number of characters into your 5 or 6 character array, writing beyond your array bounds invoking Undefined Behavior.
A more sound approach is the provide a character buffer large enough to handle whatever the user may enter. (don't Skimp on buffer size). The read an entire line at a time with fgets()
, which with a sufficient sized buffer ensure the entire line is consumed eliminating the chance for characters to remain unread in stdin
. The only caveat with fgets
(and every line-oriented input function like POSIX getline
) is the '\n'
is also read and included in the buffer filled. You simply trim the '\n'
from the end using strcspn()
as a convenient method obtaining the number of characters entered at the same time.
(note: you can forego trimming the '\n'
if you adjust your tests to include the '\n'
in the length you validate against since the conversion to int
will ignore the trailing '\n'
)
Your logic is lacking one other needed check. What if the user enters "123a5"
? All 5 characters were entered, but they were not all digits. atoi()
has no error reporting capability and will happily convert the string to 123
silently without providing any indication that additional characters remain. You have two-options, either use strtol
for the conversion and validate no characters remain, or simply loop over the characters in your string checking each with isdigit()
to ensure all digits were entered.
Putting that altogether, you could do something like the following:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define NDIGITS 5 /* if you need a constant, #define one (or more) */
#define MAXC 1024
int main (void) {
int mynumber;
size_t digit = NDIGITS;
char buf[MAXC]; /* buffer to hold MAXC chars */
/* infinite loop until valid string entered, or manual EOF generated */
for (;;) {
size_t len;
printf("\nEnter a %zu digit number: ", digit); /* prompt */
if (!fgets (buf, sizeof buf, stdin)) { /* read entire line */
fputs ("(user canceled input)\n", stdout);
break;
}
buf[(len = strcspn(buf, "\n"))] = 0; /* trim \n, get len */
if (len != digit) { /* validate length */
fprintf(stderr, " error: %zu characters.\n", len);
continue;
}
for (size_t i = 0; i < len; i++) { /* validate all digits */
if (!isdigit(buf[i])) {
fprintf (stderr, " error: buf[%zu] is non-digit '%c'.\n",
i, buf[i]);
goto getnext;
}
}
if (sscanf (buf, "%d", &mynumber) == 1) { /* validate converstion */
printf ("you entered %zu digits, mynumber = %d\n", len, mynumber);
break; /* all criteria met, break loop */
}
getnext:;
}
return 0;
}
Example Use/Output
Whenever you write an input routine, go try and break it. Validate it does what you need it to do and catches the cases you want to protect against (and there will still be more validations you can add). Here, it covers most anticipated abuses:
$ ./bin/only5digits
Enter a 5 digit number: no
error: 2 characters.
Enter a 5 digit number: 123a5
error: buf[3] is non-digit 'a'.
Enter a 5 digit number: 123 45
error: 6 characters.
Enter a 5 digit number: ;alsdhif aij;ioj34 ;alfj a!%#$%$ ("cat steps on keyboard...")
error: 61 characters.
Enter a 5 digit number: 1234
error: 4 characters.
Enter a 5 digit number: 123456
error: 6 characters.
Enter a 5 digit number: 12345
you entered 5 digits, mynumber = 12345
User cancels input with ctrl+d on Linux (or ctrl+z on windows) generating a manual EOF
:
$ ./bin/only5digits
Enter a 5 digit number: (user canceled input)
(note: you can add additional checks to see if 1024 or more characters were input -- that is left to you)
This is a slightly different approach to reading input, but from a general rule standpoint, when taking user input, if you ensure you consume an entire line of input, you avoid many of the pitfalls associated with using scanf
for that purpose.
Look things over and let me know if you have further questions.