我正在编写用于解析文本文件中许多命令的代码,发现我认为这是getopt中的错误。只是想知道以前是否有人看过?
基本上,我有一个循环,在该循环中,我每次读取一个字符串,然后使用“strtok”将其转换为可以馈送给getopt并检查提供的选项的东西。它在某些情况下有效:

$ ./test_short
cmd -ia
  -- option i
  -- option a


cmd -a
  -- option a


cmd -i
  -- option i
但这失败了,例如:
$ ./test_short
cmd -ia
  -- option i
  -- option a


cmd -i -a
cmd: invalid option -- '-'
  -- option unknown
  -- option a
  -- option a
可以从https://github.com/angel-devicente/test-getopt克隆代码,但是为了完整起见,我也将short选项放在了下面。
您是否知道这是一个真正的错误,还是忘记了其他全局变量?
谢谢,
#include <stdio.h>     /* for printf */
#include <stdlib.h>    /* for exit */
#include <string.h>
#include <unistd.h>

#define MAX_ARGS 10
#define MAX_C 100


int main(int argc, char **argv) {
  int c;
  char       cmd[MAX_C+1];

  int argc_i;
  char *argv_i[MAX_ARGS];


  while (1) {
    // Read string, for example: cmd -i -a
    fgets(cmd,MAX_C,stdin);
    cmd[strlen(cmd) - 1] = 0;
    if (strlen(cmd) <= 1) break;

    // Transform into argc_i, argv_i, to feed to getopt
    argc_i = 0;
    char *p2 = strtok(cmd, " ");
    while (p2 && argc_i < MAX_ARGS)
      {
    argv_i[(argc_i)++] = p2;
    p2 = strtok(NULL, " ");
      }
    argv_i[argc_i] = 0;


    // Reset getopt
    optind  = 1;
    int option_index = 0;


    while (1) {
      c = getopt(argc_i, argv_i, "ia");

      if (c == -1)
    break;

      switch (c) {
      case 'a':
    printf("  -- option a\n");
    break;

      case 'i':
    printf("  -- option i\n");
    break;

      case '?':
    printf("  -- option unknown\n");
    break;

      default:
    printf("?? getopt returned character code 0%o ??\n", c);
      }
    }

    printf("\n\n");

    if (optind < argc) {
      printf("non-option ARGV-elements: ");
      while (optind < argc)
    printf("%s ", argv[optind++]);
      printf("\n");
    }
  }

  exit(EXIT_SUCCESS);
}

最佳答案

这看起来像故意偏离getopt的POSIX规范。
首先,我在您的代码中更改了这一行:

argv_i[(argc_i)++] = p2;
对此:
argv_i[(argc_i)++] = strdup(p2);
并将其添加到while (1)循环的末尾:
for (i=0;i<argc_i;i++) {
    free(argv_i[i]);
}
并将其添加到顶部:
memset(argv_i,0,sizeof(argv_i));
然后我通过了valgrind:
==54748== Memcheck, a memory error detector
==54748== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==54748== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==54748== Command: ./x1
==54748==
optind=1
cmd -ia
arg 0: cmd
arg 1: -ia
  -- option i
  -- option a


cmd -i -a
==54748== Invalid read of size 1
==54748==    at 0x4F192A1: _getopt_internal_r (in /usr/lib64/libc-2.17.so)
==54748==    by 0x4F1A56A: _getopt_internal (in /usr/lib64/libc-2.17.so)
==54748==    by 0x4F1A5B7: getopt (in /usr/lib64/libc-2.17.so)
==54748==    by 0x400A2E: main (x1.c:48)
==54748==  Address 0x5205093 is 3 bytes inside a block of size 4 free'd
==54748==    at 0x4C2AF9D: free (vg_replace_malloc.c:540)
==54748==    by 0x400B25: main (x1.c:89)
==54748==  Block was alloc'd at
==54748==    at 0x4C29EA3: malloc (vg_replace_malloc.c:309)
==54748==    by 0x4EC3AA9: strdup (in /usr/lib64/libc-2.17.so)
==54748==    by 0x400991: main (x1.c:31)
==54748==
  -- option i
  -- option a

由于在测试程序中没有对已分配内存的引用,这意味着getopt实现围绕着指向它的指针。
现在,让我们看一下https://github.com/lattera/glibc/blob/master/posix/getopt.c的几行内容,包括_getopt_internal_r:
/* Index in ARGV of the next element to be scanned.
   This is used for communication to and from the caller
   and for communication between successive calls to 'getopt'.
   On entry to 'getopt', zero means this is the first call; initialize.
   When 'getopt' returns -1, this is the index of the first of the
   non-option elements that the caller should itself scan.
   Otherwise, 'optind' communicates from one call to the next
   how much of ARGV has been scanned so far.  */

/* 1003.2 says this must be 1 before any call.  */
int optind = 1;
...
static struct _getopt_data getopt_data;
...
int
_getopt_internal_r (int argc, char **argv, const char *optstring,
            const struct option *longopts, int *longind,
            int long_only, struct _getopt_data *d, int posixly_correct)
{
  int print_errors = d->opterr;

  if (argc < 1)
    return -1;

  d->optarg = NULL;

  if (d->optind == 0 || !d->__initialized)
    optstring = _getopt_initialize (argc, argv, optstring, d, posixly_correct);

...

int
_getopt_internal (int argc, char **argv, const char *optstring,
          const struct option *longopts, int *longind, int long_only,
          int posixly_correct)
{
  int result;

  getopt_data.optind = optind;
  getopt_data.opterr = opterr;

  result = _getopt_internal_r (argc, argv, optstring, longopts,
                   longind, long_only, &getopt_data,
                   posixly_correct);

  optind = getopt_data.optind;
  optarg = getopt_data.optarg;
  optopt = getopt_data.optopt;

  return result;
}

#define GETOPT_ENTRY(NAME, POSIXLY_CORRECT)         \
  int                               \
  NAME (int argc, char *const *argv, const char *optstring) \
  {                             \
    return _getopt_internal (argc, (char **)argv, optstring,    \
                 0, 0, 0, POSIXLY_CORRECT);     \
  }

#ifdef _LIBC
GETOPT_ENTRY(getopt, 0)
GETOPT_ENTRY(__posix_getopt, 1)
#else
GETOPT_ENTRY(getopt, 1)
#endif
在这里,我们可以看到optind初始化为1,而内部静态结构未初始化。还有一条评论说:“进入'getopt'时,零表示这是第一个调用;初始化。”表示不同的初始化条件。getopt调用getopt_internal,后者随后将optind分配给静态结构的相应成员,然后将其传递给_getopt_internal_r。然后,_getopt_internal_r函数检查结构的optind成员是否为0或__initialized成员是否为0,如果是,则调用初始化函数,定义如下:
static const char *
_getopt_initialize (int argc _GL_UNUSED,
            char **argv _GL_UNUSED, const char *optstring,
            struct _getopt_data *d, int posixly_correct)
{
  /* Start processing options with ARGV-element 1 (since ARGV-element 0
     is the program name); the sequence of previously skipped
     non-option ARGV-elements is empty.  */
  if (d->optind == 0)
    d->optind = 1;

  d->__first_nonopt = d->__last_nonopt = d->optind;
  d->__nextchar = NULL;

  /* Determine how to handle the ordering of options and nonoptions.  */
  if (optstring[0] == '-')
    {
      d->__ordering = RETURN_IN_ORDER;
      ++optstring;
    }
  else if (optstring[0] == '+')
    {
      d->__ordering = REQUIRE_ORDER;
      ++optstring;
    }
  else if (posixly_correct || !!getenv ("POSIXLY_CORRECT"))
    d->__ordering = REQUIRE_ORDER;
  else
    d->__ordering = PERMUTE;

  d->__initialized = 1;
  return optstring;
}
我们可以在这里看到optind成员如果当前为0,则将被设置为1。此外,还要特别注意以下这一行:
d->__nextchar = NULL;
如果再查看定义该字段的https://github.com/lattera/glibc/blob/master/posix/getopt_int.h,我们将看到以下内容:
  /* The next char to be scanned in the option-element
     in which the last option character we returned was found.
     This allows us to pick up the scan where we left off.
     If this is zero, or a null string, it means resume the scan
     by advancing to the next ARGV-element.  */
  char *__nextchar;
因此,此变量跟踪处理从何处终止。但是回到_getopt_internal_r中的这些行
  if (d->optind == 0 || !d->__initialized)
    optstring = _getopt_initialize (argc, argv, optstring, d, posixly_correct);
仅当optind为0时才会发生重置。
因此,在此实现中,将optind设置为0将重置处理。但是,这不是可移植的,因为POSIX指定这样做是未指定的行为。
这意味着要么更改此代码以将optind == 1用作初始化条件,要么应该更新文档以反射(reflect)与POSIX标准的偏离并声明它是偏离。

07-28 12:17