(*++argv)[0]和while(c = *++argv[0])之间的区别

13

我有以下代码片段:

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

     char line[MAXLINE];
     long lineno = 0;
     int c, except = 0, number = 0, found = 0;

     while(--argc > 0 && (*++argv)[0] == '-') //These two lines
        while(c = *++argv[0])                 //These two lines
          switch(c) {
             case 'x':
                  except = 1;
                  break;
             case 'n':
                  number = 1;
                  break;
             default:
                  printf("find: illegal option %c\n", c);
                  argc = 0;
                  found = -1;
                  break;
          }

     ...
}

包含以下表达式:

while(--argc > 0 && (*++argv)[0] == '-')

圆括号中的表达式 (*++argv)[0] 和没有括号的表达式 while(c = *++argv[0]) 有什么区别?

如果有区别,是什么?(*++argv) 是指向下一个参数的指针吗?*++argv[0] 是指向当前被指向的字符数组中的下一个字符的指针吗?


我还对一件事情很感兴趣:while(c = *++argv[0]) 这个表达式。这实际上是指:while(c = ++argv[0] != 0),也就是说,如果没有找到字符,++argv[0] 是否会返回一个空指针给 c? - Tool
正如我在答案中所指出的,有关此代码的信息,请参阅K&R的勘误表:http://cm.bell-labs.com/cm/cs/cbook/2ediffs.html - Alok Singhal
5个回答

40

首先,K&R有一份关于这个特定代码片段的勘误说明:

117(§5.10): 在 find 的例子中,程序会递增 argv[0]。这不是明确禁止的,但也不是明确允许的。

现在来解释一下。

假设你的程序叫做prog,并使用以下命令来执行:prog -ab -c Hello World。你想要能够解析这些参数,以便指定选项 abc,以及 HelloWorld 是非选项参数。

argv 的类型为 char ** —请记住,函数中的数组参数就是指针。在程序调用时,情况如下:

                 +---+         +---+---+---+---+---+
 argv ---------->| 0 |-------->| p | r | o | g | 0 |
                 +---+         +---+---+---+---+---+
                 | 1 |-------->| - | a | b | 0 |
                 +---+         +---+---+---+---+
                 | 2 |-------->| - | c | 0 |
                 +---+         +---+---+---+---+---+---+
                 | 3 |-------->| H | e | l | l | o | 0 |
                 +---+         +---+---+---+---+---+---+
                 | 4 |-------->| W | o | r | l | d | 0 |
                 +---+         +---+---+---+---+---+---+
                 | 5 |-------->NULL
                 +---+
在这里,argc 的值为 5,argv[argc] 的值为 NULL。一开始,argv[0] 是一个包含字符串 "prog"char * 变量。

在表达式 (*++argv)[0] 中,由于括号的作用,先对 argv 进行了自增操作,然后再进行解引用操作。自增操作的作用是将那个指向 argv ----------> 的箭头“向下移动”一个块,指向数字 1。解引用操作的作用是获取指向第一个命令行参数(即字符串 -ab)的指针。最后,我们取该字符串的第一个字符(在表达式 (*++argv)[0] 中为 [0]),并检查它是否为 '-',因为这表示选项的开始。

对于第二个结构体,我们实际上想要遍历当前 argv[0] 指针所指向的字符串。因此,我们需要将 argv[0] 视为指针,忽略其第一个字符(即我们刚刚测试过的 '-'),并查看其他字符:

++(argv[0]) 会将 argv[0] 递增,以获取指向第一个非 '-' 字符的指针,并对其进行解引用,从而得到该字符的值。因此,我们得到了表达式 *++(argv[0])。但是由于在 C 中,[] 的优先级高于 ++,所以我们实际上可以省略括号并将表达式写成*++argv[0]。我们希望继续处理此字符,直到它为 0(上面图片中每行最后一个字符框中的值)。

这个表达式为:

c = *++argv[0]

将当前选项的值赋给c,并且它的值为cwhile(c)while(c != 0)的简写形式,因此while(c = *++argv[0])行基本上是将当前选项的值赋给c并测试它以查看是否已经到达当前命令行参数的末尾。

循环结束时,argv将指向第一个非选项参数:

                 +---+         +---+---+---+---+---+
                 | 0 |-------->| p | r | o | g | 0 |
                 +---+         +---+---+---+---+---+
                 | 1 |-------->| - | a | b | 0 |
                 +---+         +---+---+---+---+
                 | 2 |-------->| - | c | 0 |
                 +---+         +---+---+---+---+---+---+
 argv ---------->| 3 |-------->| H | e | l | l | o | 0 |
                 +---+         +---+---+---+---+---+---+
                 | 4 |-------->| W | o | r | l | d | 0 |
                 +---+         +---+---+---+---+---+---+
                 | 5 |-------->NULL
                 +---+

这个有帮助吗?


@Alok,你能解释一下这个步骤吗:if((strstr(line, *argv) != NULL) != except)? - HELP PLZ
@AbhimanyuAryan strstr(a, b) 检查字符串 b 是否存在于 a 中。如果 b 不在 a 中,则返回 NULL。因此,strstr(line, *argv) != NULL 检查由 argv 指向的字符串是否在 line 中,并且如果是,则值为 1,否则为 0。根据 x 标志的存在,except 在早期设置为 10 - Alok Singhal
非常感谢仔细的逐步解释。我已经为此纠结了一个小时。现在一切都清晰明了。谢谢。 - Aniruddha

5

是的,你说得对。

while(--argc > 0 && (*++argv)[0] == '-')

扫描长度为 argc 的命令行参数数组,逐个查找以 - 选项前缀开头的参数。对于每一个这样的参数:

while(c = *++argv[0])

该程序扫描跟随当前参数中第一个-后面的转换字符集(即-tn中的tn),直到遇到字符串的空值终止符\0,该值为假,则停止while循环。

这种设计允许同时:

myApp -t -n

and

myApp -tn

要同时工作并被理解为具有选项tn


1
这个设计很简单,大多数都是合理的,除了修改 argc 和数组 argv 的内容,这是一个糟糕的设计,因为它阻止了这些变量的进一步使用。 - Alex Brown

5

递增argv是一个非常糟糕的想法,因为一旦这样做了,很难恢复原始值。使用整数索引更简单、更清晰、更好 - 毕竟argv是一个数组!

回答你的问题,++argv会递增指针。然后对其进行间接引用以获取第一个字符。


实际上,间接引用始于 - 后的第一个字符,并且每个周期它都会移动到下一个字符,以支持单个 - 字符后的选项标志集群。 - Alex Brown
我指的是 (*++argv)[0] == '-'。 - anon

4

括号改变了表达式求值的顺序。

没有括号 *++argv[0]:

  1. argv[0] 获取指向 argv 当前指向的字符数据的指针。
  2. ++ 将该指针递增到字符数组中的下一个字符。
  3. * 获取字符。

有括号 (*++argv)[0]:

  1. ++argv 递增 argv 指针以指向下一个参数。
  2. * 解引用它以获取指向字符数据的指针。
  3. [0] 获取字符数组中的第一个字符。

2

是的,这两个表达式不同(虽然只有轻微的差别)。在我看来,这段代码稍微有点过于聪明了。您最好使用类似以下代码:

for (int i=1; i<argc; i++)
    if (argv[i][0] == '-') {
       size_t len = strlen(argv[i]);
       for (int j=0; j<len; ++j)
           switch(argv[i][j]) {
               case 'x':
               // ...

这段代码与上面的代码基本相同,但我怀疑任何(懂C语言的)人都不会有困难理解它的实际含义。


但是这段代码无法检测到选项链 - 您需要另一个迭代器来遍历选项链 -tn - Alex Brown
@Alex Brown:我相信我已经修复了这个问题,但我不确定它是否真正有所改善。允许使用“-tn”而不是“-t -n”在传统终端是Teletype时会有很大的意义,但现在几乎没有什么价值了。 - Jerry Coffin
@Jerry 这完全是值得的。每个命令行用户都希望能够在一组中提供单字母选项,而对代码的懒惰意味着违反了这些根深蒂固的期望。更深层次的问题在于自定义编码此功能,而不是使用getopt或类似工具。 - Phil Miller
@Novelocrat:恐怕我不能完全同意——相当多的命令行工具要么根本不允许聚合参数,要么对可以和不可以聚合的参数有特定限制。任何有实质经验的人都不可能对这个问题有太高的期望。考虑到它只支持两个参数,而且没有任何相关参数,我可以理解为什么使用 getopt 可能会使代码更加复杂,所以我可以理解为什么不使用它,尽管我同意它可能应该使用。 - Jerry Coffin
1
你试着从我这里拿走tar -xvzf a.tar.gz,看看会发生什么。或者ls -laTr,或者ps -elF等等。 - Alex Brown

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接