-3

This has been driving my entire C++ class nuts, none of us has been able to find a solid solution to this problem. We are passing information to our program through the Terminal, via argv* [1]. We would call our program ./main 3 and the program will run 3 times.

The problem comes when we are validating the input, we are trying to cover all of our bases and for most of them we are good, like an alphabetical character entered, a negative number, 0, etc. But what keeps passing through is an int followed by a str for example ./main 3e or ./main 1.3. I've tried this ( Ashwin's answer caught my eye ) but it doesn't seem to work or at least I can't implement it in my code.

This is my code now:

int main(int argc, char * argv[]){
    if (!argv[1]) exit(0);

    int x = atoi(argv[1]);

    if (!x or x <= 0) exit(0); 

    // I would like to add another exit(0); for when the input mixes numbers and letters or doubles.

    for (int i = 0; i < x; i++){
        // rest of the main func.
    }
Community
  • 1
  • 1
lokilindo
  • 682
  • 5
  • 17
  • Is there any reason why you can't write a function that checks each character in `argv[1]`? Also, it doesn't sound to me like checking the argument "doesn't have any alphabetical characters" is what you really want to be doing. – juanchopanza Feb 17 '16 at 05:26
  • 1
    strtol is your friend. – rici Feb 17 '16 at 05:28
  • I tried but it's giving me error (something having to do with char *) – lokilindo Feb 17 '16 at 05:28
  • are you looking for alphabetical input, or *non-integer* input? – jaggedSpire Feb 17 '16 at 05:29
  • @jaggedSpire I would guess both, I really just need argv[1] to be and int, but I'm also curious as to how to solve the issue of `ints` followed by `str` getting though. – lokilindo Feb 17 '16 at 05:30
  • @user657267 Yes, it's fundamental to the program that nothing else other than a valid `int` is passed, the program will read `3e` as `3`, yes, but I need it to reject that input... if possible. – lokilindo Feb 17 '16 at 05:35
  • @lokilindo A valid `int` can start with `0`. – juanchopanza Feb 17 '16 at 05:35
  • @rici Thanks, didn't know that existed. But I don't believe it will help me. I don't need to parse the values, I literally need the program to discard them. As I have it right now, it reads `3e` as `3`, but what I need is for it to quit when those situations arise. – lokilindo Feb 17 '16 at 05:37
  • @lokilindo. Read the manpage. If endptr points to a NUL at the end of the parse, everything was A-OK. Otherwise, no. It doesn't hurt you to ignore the number in the case of an error. – rici Feb 17 '16 at 06:01

2 Answers2

2

Despite the title, it sounds like you really want to do is check whether every single character in the input argument is a digit. You can achieve this by iterating over it, checking that every element is a digit using std::isdigit.

Here's a sketch using the std::all_of algorithm:

size_t len = strlen(argv[1]);
bool ok = std::all_of(argv[1], argv[1] + len,
                      [](unsigned char c) { return std::isdigit(c); } );

You can add an extra check for the first element being '0' if needed.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • Can't you just do `std::all_of(argv[1], argv[1] + len, &std::isdigit)`? (honestly curious) – Qix - MONICA WAS MISTREATED Feb 17 '16 at 05:36
  • @Qix There are two potential problems: there's another `std::isdigit` overload, and I want to convert the input to unsigned char to be on the safe side. I am not sure whether the latter is strictly necessary here though. – juanchopanza Feb 17 '16 at 05:38
  • This is getting me an error, anyway: `candidate template ignored: couldn't infer template argument '_Predicate'` – Qix - MONICA WAS MISTREATED Feb 17 '16 at 05:44
  • same, I've tried a couple of things and also get the error: `error: 'all_of' is not a member of 'std'` – lokilindo Feb 17 '16 at 05:48
  • @lokilindo You need C++11 and the relevant header files. Those can be found in the links in the answer. Otherwise, just write a loop. – juanchopanza Feb 17 '16 at 05:51
  • I thought so, I am using -std=c++11 and #include , but still get the error. – lokilindo Feb 17 '16 at 05:53
  • 1
    @lokilindo Then you're doing something else wrong. [The code works](http://ideone.com/RkM1wK). But just write a loop seriously, the point of this answer isn't to use `all_of`. That is just an example. – juanchopanza Feb 17 '16 at 05:56
  • @juanchopanza You are correct, ( I am a bit tired, and it's late) I was compiling my code `g++ -o file file.cpp` but I should have been compiling it `g++ -std=c++11 -o file file.cpp` . Thanks – lokilindo Feb 17 '16 at 06:02
0

If you want to convert a string to a number and verify that the entire string was numeric, you can use strtol instead of atoi. As an additional bonus, strtol correctly checks for overflow and gives you the option of specifying whether or not you want hexadecimal/octal conversions.

Here's a simple implementation, with all the errors noted (printing error messages from a function like this is not a good idea; I just did it for compactness). A better option might be to return an error enum instead of the bool, but this function returns a std::pair<bool, int>: either (false, <undefined>) or (true, value):

std::pair<bool, int> safe_get_int(const char* s) {
  char* endptr;
  bool ok = false;
  errno = 0; /* So we can check ERANGE later */
  long val = strtol(s, &endptr, 10); /* Don't allow hex or octal. */
  if (endptr == s) /* Includes the case where s is just whitespace */
     std::cout << "You must specify some value." << '\n';
  if (*endptr != '\0')
     std::cout << "Argument must be an integer: " << s << '\n';
  else if (val < 0)
     std::cout << "Argument must not be negative: " << s << '\n';
  else if (errno == ERANGE || val > std::numeric_limits<int>:max())
     std::cout << "Argument is too large: " << s << '\n';
  else
     ok = true;
  return std::make_pair(ok, ok ? int(val) : 0);
}

In general, philosophical terms, when you have an API like strtol (or, for that matter, fopen) which will check for errors and deny the request if an error occurs, it is better programming style to "try and then check the error return", than "attempt to predict an error and only try if it looks ok". The second strategy, "check before use", is plagued with bugs, including security vulnerabilities (not in this case, of course, but see TOCTOU for a discussion). It also doesn't really help you, because you will have to check for error returns anyway, in case your predictor was insufficiently precise.

Of course, you need to be confident that the API in question does not have undefined behaviour on bad input, so read the official documentation. In this case, atoi does have UB on bad input, but strtol does not. (atoi: "If the value cannot be represented, the behavior is undefined."; contrast with strtol)

rici
  • 234,347
  • 28
  • 237
  • 341