2

I have a program that needs to get an int from the user from the command line

int main(int argc, char* argv[]) 

My only problem is that I need to check whether argv is an int. If it isn't, I need to return an error. How can I do that? I have to check if the input is an int before using atoi. Can someone help me?

4
  • char* argv[] is an array of pointers to string. None of them is an int, although it could be numeric. You have to convert it yourself. Commented Jun 9, 2020 at 21:25
  • Is there any way I can discover if it is numeric before the conversion? If I try to convert it and it isn't numeric my program will crash, so I wanted to discover it before converting Commented Jun 9, 2020 at 21:27
  • Validation tests include 1) argc big enough? 2) with strtol(argv[]...) did conversion occur?, trailing non-numeric text? in range of int? 3) Pedantic details include if leading/trailing white-space allowed? Commented Jun 9, 2020 at 22:38
  • Does this answer your question? Command line arguments in C Commented Jun 9, 2020 at 23:51

6 Answers 6

8

Here's one way, using strtol and checking the end of the string:

#include <stdio.h> #include <stdlib.h> int main(int argc,char **argv) { char *cp; long lval; int val; // skip over program name --argc; ++argv; if (argc < 1) { fprintf(stderr,"main: no argument specified\n"); exit(1); } cp = *argv; if (*cp == 0) { fprintf(stderr,"main: argument an empty string\n"); exit(1); } lval = strtol(cp,&cp,10); if (*cp != 0) { fprintf(stderr,"main: argument '%s' is not an integer -- '%s'\n", *argv,cp); exit(1); } val = (int) lval; // NOTE: just going for extra credit here ;-) // ensure number fits in a int (since strtol returns long and that's 64 // bits on a 64 bit machine) #if 1 if (val != lval) { fprintf(stderr,"main: argument '%s' (with value %ld) is too large to fit into an integer -- truncated to %d\n", *argv,lval,val); exit(1); } #endif printf("val=%d\n",val); return 0; } 

UPDATE:

Minor: Code does not detect conversion overflow of strtol() Code incorrectly assumes range of long more than int. If same range, if (val != lval) is always true. Suggest looking at errno, INT_MAX,INT_MIN

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <limits.h> int main(int argc,char **argv) { char *cp; long lval; int val; // skip over program name --argc; ++argv; if (argc < 1) { fprintf(stderr,"main: no argument specified\n"); exit(1); } cp = *argv; if (*cp == 0) { fprintf(stderr,"main: argument an empty string\n"); exit(1); } errno = 0; lval = strtol(cp,&cp,10); if (*cp != 0) { fprintf(stderr,"main: argument '%s' is not an integer -- '%s'\n", *argv,cp); exit(1); } // on a 32 bit machine, entering 2147483648 will produce a non-zero errno if (errno) { fprintf(stderr,"main: argument '%s' parse error -- '%s'\n", *argv,strerror(errno)); exit(1); } // on a 64 bit machine, entering 2147483648 will not produce an error, so // we should check the range ourselves if ((lval < INT_MIN) || (lval > INT_MAX)) { fprintf(stderr,"main: argument '%s' range error -- %ld outside of range (%ld to %ld)\n", *argv,lval,(long) INT_MIN,(long) INT_MAX); exit(1); } val = (int) lval; // NOTE: just going for extra credit here ;-) // ensure number fits in a int (since strtol returns long and that's 64 // bits on a 64 bit machine) // FIXME -- with above tests this can never be true (i.e. fault), so // I've nop'ed it -- left in to show prior/original test #if 0 if (val != lval) { fprintf(stderr,"main: argument '%s' (with value %ld) is too large to fit into an integer -- truncated to %d\n", *argv,lval,val); exit(1); } #endif printf("val=%d\n",val); return 0; } 
Sign up to request clarification or add additional context in comments.

5 Comments

Minor: Code does not detect conversion overflow of strtol().
@chux-ReinstateMonica I'd be glad to add a check, but I'm not sure what you mean. If I had to hazard a guess, would this be specifying (e.g.) a large positive int value that is > INT_MAX causing a rollover to a negative value?
@chux-ReinstateMonica I've edited [and tested] to check errno and INT_MIN/INT_MAX range.
Nice improvement. With improvement if (val != lval) is never true.
Good enough without overflow check. It's not typical to pass such big numbers via command line parameters.
4

Command line argument validation in C

I need to check whether argv is an int

  1. Test first if argv[] contains a string by checking argc.
 for (int a = 1; a < argc; a++) { int_validation(argv[a]); } 
  1. Attempt conversion using strtol()
 #include <ctype.h> #include <errno.h> #include <limits.h> #include <stdlib.h> #include <stdio.h> void int_validation(const char *s) { // If leading space not OK // isspace() only valid in unsigned char range and EOF. if (isspace(*(unsigned char *)s)) { puts("Fail - leading spaces"); return; } // Convert int base = 0; // Use 10 for base 10 only input char *endptr; errno = 0; long val = strtol(s, &endptr, base); if (s == endptr) { // When endptr is same as s, no conversion happened. puts("Fail - no conversion"); return; } // detect overflow if (errno == ERANGE || val < INT_MIN || val > INT_MAX) { errno = ERANGE; puts("Fail - overflow"); return; } // If trailing space OK, seek pass them while (isspace(*(unsigned char *)endptr)) { endptr++; } // If trailing non-numeric text bad if (*endptr) { puts("Fail - overflow"); return; } printf("Success %d\n", (int) val); return; } 

Adjust return type and messages as desired.


Typically input like "1e5" or "123.0", although mathematically a whole number, is not consider valid int input. Additional code needed to allow those.

Comments

2

You can try to convert the argument using strtol(), it will return 0 if the value is not parseable or the parsed value.

You can also use the second argument for a more detailed verification of the input, you can differenciate between a bad input or a 0 input, since in both cases the returned value is 0.

#include <stdio.h> #include <stdlib.h> #include <errno.h> int main(int argc, char *argv[]) { long parsed_value = 0; int value = 0; //for command + 1 argument if (argc == 2) { errno = 0; char *end_ptr; parsed_value = strtol(argv[1], &end_ptr, 10); //argument check, overflow, trailing characters, underflow, errno if(*end_ptr == argv[1][0] || *end_ptr != '\0' || errno == ERANGE || parsed_value < INT_MIN || parsed_value > INT_MAX){ fprintf(stderr, "Invalid argument"); return EXIT_FAILURE; } } else{ fprintf(stderr, "Wrong number of arguments, %d provided, 2 needed", argc); return EXIT_FAILURE; } //otherwise the value was parsed correctly value = parsed_value; printf("%d", value); } 

9 Comments

Oh, thx, that actually works. The program doesn't need to process the value 0 because it would be useless in this context. Thanks
strtol provides other mechanisms for detecting error. Specifically the endptr argument, which will point to the first invalid character. If endptr is equal to the start of your string and not NULL, then you know that the string was not an integer.
Minor: Code does not detect conversion overflow of strtol().
Given this is main(), errno can be assumed to be 0, but not in a general int_validation(char *s) test due to unknown prior history.
When errno serves as the indicator that an error occured, then set it to 0 first. If there is another indicator that unambiguously tells you there is an error that prompts you to check errno, then you are fine to not set it to 0 first. In this case, errno does serve as an indicator that an error occurred, so it should be set to zero first.
|
1

Use isdigit to test the characters of the argument for "digit-ness", then convert it (or not) based on the results. For example:

#include <stdio.h> #include <stdbool.h> #include <ctype.h> #include <stdlib.h> bool is_all_digits(char *s) { bool b = true; for( ; *s ; ++s) if(!isdigit(*s)) { b = false; break; } return b; } int main(int argc, char *argv[]) { for(int i = 0 ; i < argc ; ++i) { if(is_all_digits(argv[i])) printf("argv[%d] is an integer = %d\n", i, atoi(argv[i])); else printf("argv[%d] is not an integer \"%s\"\n", i, argv[i]); } return 0; } 

When run with the command-line arguments

123 "Not a number" 456.789 "Not another number" 10 

the following output is produced:

argv[0] is not an integer "./a.out" argv[1] is an integer = 123 argv[2] is not an integer "Not a number" argv[3] is not an integer "456.789" argv[4] is not an integer "Not another number" argv[5] is an integer = 10 

As others have noted, is_all_digits doesn't guarantee that a string which represents an integer is parse-able using atoi or any other routine, but feel free to doctor this to your heart's content. :-)

Comments

0

As an alternative to strtol() (that is the canonical answer) you can perform an hand made validation by using isdigit() function, and also checking for leading sign characters (+ and -):

#include <ctype.h> #include <bool.h> bool isValidInteger( char * str ) { bool ret = true; if( str ) { char p = str; for( int i=0; str[i] != 0; i++ ) { if ( !isdigit( str[i] ) ) { if( i == 0 && ( str[i] == '+' || *p == '-' ) && str[i+1] ) continue; ret = false; break; } } } else { return false; } return ret; } 

This implementation relies on the fact that input string is null terminated. But since every argv[N] is a null terminated string, we are fine.

Usage:

if ( isValidInteger( argv[1] ) ) { int par = atoi( argv[1] ); } 

Note (1): this validator doesn't check against input values that exceed the int range (from INT_MIN to INT_MAX). This is a limitation that might be considered acceptable in many cases.


Note (2): this function doesn't trim leading spaces like strto* do. If such feature is required, a check like this one can be added at the top of the for-loop:

bool flag = true; if( str[i] == ' ' ) continue; flag = false; 

In this way spaces will be tolerated, but only until flag is set to false that is when the first non-space character is encountered.

7 Comments

Just checking that every character is a digit is not sufficient. Leading + and - signs are also part of valid strings that represent numbers that atoi() has the capability to convert. But you also have to validate that the value that the string of digits represent can fit into an int in order to avoid undefined behavior. So to safely use atoi() on unknown input without invoking undefined behavior, you have to parse the full string and verify that INT_MIN <= value <= INT_MAX. You have to fully solve the problem before you can safely use atoi() to solve the problem.
@AndrewHenle for some reasons I forgotten the sign chars. I fixed at least that. You are right also about INT_MIN / INT_MAX check: about it I'll add a note declaring it as a limitation (imho acceptable in most cases).
@AndrewHenle I though about a string len check inside validator (that must be less than 11), an external conversion with atol, and a casting to the target integer. But it was too much for an answer starting with the assertion that strtol was the right choice... from a phone. Especially since a similar implementation would have raised issues about the integer size in the current platform. Too much :)
Note that strto*() allows leading white-space unlike isValidInteger() - Better or worse depending on goals.
@chux this is correct too. But in this scenario I think that wouldn't matter since cmdline parser trims leading spaces while building argv. Not sure about what happens for a parameter such as " 1235".
|
0

Try this. I'll let you sort error messaging however you like:

#define TRUE 1 #define FALSE 0 int is_integer( char *s ) { int i = 0 ; int is_digit; int is_sign; while ( s[i] != '\0' ) { // this test makes the assumption that the code points for // decimal digits are contiguous. True for ASCII/UNICODE and EBCDIC. // If you're using some other bizarro encoding, you're out of luck. is_digit = s[i] >= '0' && s[i] <= '9' ? TRUE : FALSE ; is_sign = i == 0 && s[i] == '-' ? TRUE : FALSE ; if ( !is_digit && !is_sign ) { return FALSE; } ++i; } return TRUE; } int main( int argc, char *argv[] ) { int i = 0 ; int cc = 0 ; // assume success; for ( i = 0 ; i < argc ; ++i ) { if ( !is_integer(argv[i]) ) { cc = 1; } } return cc; // exit code 0 is success; non-zero exit code is failure. } 

3 Comments

"makes the assumption that the code points for // decimal digits are contiguous." --> No assumption needed. C requires that.
@chux-ReinstateMonica: fixed it for you.
Is while ( s[i] != '\0' )... missing an i++?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.