Question

I have the following snippet of code:

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

     ...
}

Containing the following expressions:

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

Does this expression in the parentheses (*++argv)[0] differ from while(c = *++argv[0]) without parentheses?

If so, how? Does (*++argv) mean pointer to the next argument, and does *++argv[0] mean pointer to the next character in the current char array which is being pointed to?

Was it helpful?

Solution

First, K&R have an errata on this particular snippet:

117(§5.10): In the find example, the program increments argv[0]. This is not specifically forbidden, but not specifically allowed either.

Now for the explanation.

Let's say your program is named prog, and you execute it with: prog -ab -c Hello World. You want to be able to parse the arguments to say that options a, b and c were specified, and Hello and World are the non-option arguments.

argv is of type char **—remember that an array parameter in a function is the same as a pointer. At program invocation, things look like this:

                 +---+         +---+---+---+---+---+
 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
                 +---+

Here, argc is 5, and argv[argc] is NULL. At the beginning, argv[0] is a char * containing the string "prog".

In (*++argv)[0], because of the parentheses, argv is incremented first, and then dereferenced. The effect of the increment is to move that argv ----------> arrow "one block down", to point to the 1. The effect of dereferencing is to get a pointer to the first commandline argument, -ab. Finally, we take the first character ([0] in (*++argv)[0]) of this string, and test it to see if it is '-', because that denotes the start of an option.

For the second construct, we actually want to walk down the string pointed to by the current argv[0] pointer. So, we need to treat argv[0] as a pointer, ignore its first character (that is '-' as we just tested), and look at the other characters:

++(argv[0]) will increment argv[0], to get a pointer to the first non- - character, and dereferencing it will give us the value of that character. So we get *++(argv[0]). But since in C, [] binds more tightly than ++, we can actually get rid of the parentheses and get our expression as *++argv[0]. We want to continue processing this character until it's 0 (the last character box in each of the rows in the above picture).

The expression

c = *++argv[0]

assigns to c the value of the current option, and has the value c. while(c) is a shorthand for while(c != 0), so the while(c = *++argv[0]) line is basically assigning the value of the current option to c and testing it to see if we have reached the end of the current command-line argument.

At the end of this loop, argv will point to the first non-option argument:

                 +---+         +---+---+---+---+---+
                 | 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
                 +---+

Does this help?

OTHER TIPS

Incrementing argv is a very bad idea, as once you have done so it is difficult to get the original value back. It is simpler, clearer and better to use an integer index - after all argv IS an array!

To answer your question ++argv increments the pointer. This then has indirection applied to it to get the first character.

yes, you are correct.

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

is scanning the array (of length argc) of command line arguments one by one looking for those starting with a - option prefix. For each of those:

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

is scanning through the set of switch characters that follow the first - in the current argument (i.e. t and n in -tn, until it hits the string null terminator \0, which terminates the while loop, since it evaluates as false.

This design allows both

myApp -t -n

and

myApp -tn

to both work and be understood as having the options t and n.

The parentheses change the order in which the expressions are evaluated.

Without parentheses *++argv[0]:

  1. argv[0] gets the pointer to character data currently pointed to by argv.
  2. ++ increments that pointer to the next character in the character array.
  3. * gets the character.

with parentheses (*++argv)[0]:

  1. ++argv increments the argv pointer to point to the next argument.
  2. * defereferences it to obtain a pointer to the character data.
  3. [0] gets the first character in the character array.

Yes, the two expressions differ (though only slightly). IMO, this code is a bit on the excessively clever side. You'd be better off with something like this:

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':
               // ...

This is pretty much equivalent to the code above, but I doubt anybody (who knows C at all) would have any difficulty figuring out what it really does.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top