【问题标题】:Command line argument validation in CC中的命令行参数验证
【发布时间】:2020-06-09 21:23:45
【问题描述】:

我有一个程序需要从命令行从用户那里获取int

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

我唯一的问题是我需要检查argv 是否是int。如果不是,我需要返回一个错误。我怎样才能做到这一点?在使用atoi 之前,我必须检查输入是否为int。有人可以帮我吗?

【问题讨论】:

  • char* argv[] 是一个指向字符串的指针数组。它们都不是int,尽管它可能是数字。你必须自己转换它。
  • 有什么方法可以在转换之前发现它是否是数字?如果我尝试转换它并且它不是数字我的程序会崩溃,所以我想在转换之前发现它
  • 验证测试包括 1) argc 足够大吗? 2) strtol(argv[]...) 是否发生了转换?尾随非数字文本?在int 的范围内? 3) 迂腐的细节包括是否允许前导/尾随空格?
  • 这能回答你的问题吗? Command line arguments in C

标签: c int


【解决方案1】:

这是一种方法,使用strtol 并检查字符串的结尾:

#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;
}

更新:

次要:代码未检测到 strtol() 的转换溢出 代码错误地假定 long 大于 int 的范围。如果范围相同,if (val != lval) 始终为真。建议看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;
}

【讨论】:

  • 次要:代码未检测到strtol()的转换溢出。
  • @chux-ReinstateMonica 我很乐意添加支票,但我不确定您的意思。如果我不得不冒险猜测,这是否会指定(例如)一个大于 INT_MAX 的大正 int 值导致翻转为负值?
  • @chux-ReinstateMonica 我已经编辑 [和测试] 以检查 errnoINT_MIN/INT_MAX 范围。
  • 不错的改进。随着改进if (val != lval) 永远不会是真的。
  • 没有溢出检查就足够了。通过命令行参数传递这么大的数字并不典型。
【解决方案2】:

C 中的命令行参数验证

我需要检查 argv 是否为 int

1) 首先通过检查argc 来测试argv[] 是否包含字符串。

for (int a = 1; a < argc; a++) {
  int_validation(argv[a]);
}

2) 尝试使用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) {
    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;
}

根据需要调整返回类型和消息。


"1e5""123.0" 这样的典型输入,虽然在数学上是一个整数,但不是有效的int 输入。允许这些需要额外的代码。

【讨论】:

    【解决方案3】:

    您可以尝试使用strtol() 转换参数,如果该值不可解析或解析后的值,它将返回0

    您还可以使用第二个参数对输入进行更详细的验证,您可以区分错误输入或0 输入,因为在这两种情况下,返回值都是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);
    }
    

    【讨论】:

    • 哦,谢谢,这确实有效。程序不需要处理值 0,因为在这种情况下它是无用的。谢谢
    • strtol 提供了其他机制来检测错误。特别是 endptr 参数,它将指向第一个无效字符。如果 endptr 等于字符串的开头而不是 NULL,那么您知道该字符串不是整数。
    • 次要:代码未检测到strtol()的转换溢出。
    • 鉴于这是main()errno 可以假定为 0,但由于未知的先前历史,在一般的 int_validation(char *s) 测试中不是。
    • errno作为发生错误的指示符时,先设置为0。如果有另一个指标明确地告诉您有一个错误提示您检查errno,那么您可以不先将其设置为 0。在这种情况下,errno 确实作为发生错误的指示符,因此应首先将其设置为零。
    【解决方案4】:

    使用isdigit 测试“digit-ness”参数的字符,然后根据结果进行转换(或不转换)。例如:

    #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;
    }
    

    使用命令行参数运行时

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

    产生以下输出:

    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
    

    正如其他人所指出的,is_all_digits 不保证表示整数的字符串可以使用atoi 或任何其他例程进行解析,但您可以根据自己的意愿随意调整。 :-)

    【讨论】:

      【解决方案5】:

      作为strtol() 的替代方案(即规范答案),您可以使用isdigit() function 执行手工验证,并检查前导符号字符+-):

      #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;
      }
      

      此实现依赖于输入字符串以空值结尾的事实。但是由于每个argv[N] 都是一个以空结尾的字符串,所以我们很好。

      用法:

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

      注意 (1):此验证器不会检查超出 int 范围(从 INT_MININT_MAX)的输入值。这是一个在许多情况下可能被认为可以接受的限制。


      注意 (2):此函数不会像 strto* 那样修剪前导空格。如果需要这样的功能,可以在for-loop的顶部添加这样的检查:

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

      这样可以容忍空格,但只在flag 设置为false 之前遇到第一个非空格字符。

      【讨论】:

      • 仅仅检查每个字符都是数字是不够的。前导 +- 符号也是有效字符串的一部分,表示 atoi() 能够转换的数字。但是您还必须验证数字字符串表示的值是否适合int,以避免未定义的行为。因此,要在未知输入上安全地使用atoi() 而不会调用未定义的行为,您必须解析完整的字符串并验证INT_MIN &lt;= value &lt;= INT_MAX。您必须完全解决问题,才能安全地使用atoi() 解决问题。
      • @AndrewHenle 由于某些原因我忘记了符号字符。我至少解决了这个问题。您对 INT_MIN / INT_MAX 检查也是正确的:关于它,我将添加一条说明,将其声明为限制(恕我直言,在大多数情况下可以接受)。
      • @AndrewHenle 我在验证器中进行了字符串 len 检查(必须小于 11),使用atol 进行外部转换,以及转换为目标整数。但是,从断言 strtol 是正确的选择开始的答案就太过分了……来自电话。特别是因为类似的实现会引发有关当前平台中整数大小的问题。太多了:)
      • 请注意,strto*()isValidInteger() 不同,允许使用前导空格 - 取决于目标的好坏。
      • @chux 这也是正确的。但在这种情况下,我认为这无关紧要,因为 cmdline 解析器在构建argv 时会修剪 前导空格。不确定 " 1235" 等参数会发生什么。
      【解决方案6】:

      试试这个。我会让你按照你喜欢的方式对错误消息进行排序:

      #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.
      }
      

      【讨论】:

      • "假设 // 十进制数字的代码点是连续的。" --> 不需要假设。 C 要求这样做。
      • @chux-ReinstateMonica:为你修好了。
      • while ( s[i] != '\0' )... 是否缺少i++
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-26
      相关资源
      最近更新 更多