views:

605

answers:

5

On pg. 109 of K&R, we see:

void writelines(char *lineptr[], int nlines)
{
  while (nlines -- > 0) printf("%s\n", *lineptr++);
}

I'm confused about what *lineptr++ does exactly. From my understanding, printf requires a char pointer, so we provide that with *lineptr. Then we increment lineptr up to the next char pointer in the array? Isn't this illegal?

On page 99, K&R writes that "an array name is not a variable; constructions like a=pa [where a is an array, pa is a pointer to an array] and a++ are illegal."

+2  A: 

The following code will

*lineptr++
  1. dereference lineptr then
  2. increment the lineptr variable by the sizeof(lineptr[0]) using the post-increment operator
Adam Markowitz
+5  A: 

lineptr is not really an array; it's a pointer to a pointer. Note that the postincrement operator ++ has higher precedence than the dereference operator *, so what happens in lineptr++ is this:

  1. lineptr gets incremented to point to the next element in the array
  2. The result of lineptr++ is the old value of lineptr, namely a pointer to the current element in the array
  3. *lineptr++ dereferenced that, so it is the value of the current element in the array

Thus, the while loop iterates over all of the elements in the array pointed to by lineptr (each element is a char*) and prints them out.

Adam Rosenfield
Ah, this makes sense. Thank you everyone for answer my question!
Elben Shira
+5  A: 

It might be easier to imagine the *lineptr++ as the following:

*(lineptr++)

There, the pointer to the array of char*s is moved to the next element of the array, that is, from lineptr[0], we move to lineptr[1]. (The increment of the pointer occurs after the dereferencing due to the postfix increment of the pointer.)

So basically, the following things happen:

  1. lineptr[0] (of type char*) is retrieved by deferencing.
  2. The pointer is incremented to the next element of the array. (By the postfix increment on pointer lineptr.)
  3. The pointer now points to lineptr[1], repeat the process from step 1 again.
coobird
I think that *(lineptr++) is not very helpful here. That implies that lineptr is incremented *before* it is dereferenced. What's really happening is more like two separate tasks: *lineptr, ++lineptr.
Naaff
If you use (*lineptr)++ you will dereference and then increment. The lineptr being pointer to an array you select first row by dereferencing and then you increment pointer to point to next character. So if line pointer is array of 3 lines your bracketing will print the first line 3 times starting from first, second and third character. The *(lineptr++) will print each of the three lines.
stefanB
*(lineptr++) is still one statement, so the dereferencing of the pointer will occur before the post-increment takes place. Adding a parenthesis only changes the operator precedence. In this case, as pointed out by Adam Rosenfield's answer, ++ is higher precedence than *, so the parenthesis only serves as an visual indication of the two things happening in that single statement.
coobird
One word: man operator
Johannes Schaub - litb
+5  A: 

Keep reading! At the very bottom of p. 99

As formal parameters in a function definition,

 char s[];

and

 char *s;

are equivalent; we prefer the latter because it says more explicitly that the parameter is a pointer.

I.e. you can never pass an array (which is not a variable) to a function. If you declare a function that looks like it takes an array, it really takes a pointer (which is a variable). This makes sense. It would be odd for a function argument not to be a variable -- it can have a different value every time you call the function so it sure ain't a constant!

Tom Future
Well, you can pass an array name to a function, there is just some behind the scenes action that makes it appear as though you really passed in a pointer. It seems more like coping than making sense.
Derek E
Oh right, I remember now. The array isn't copied into the function's stack frame, only a pointer to that array. Thanks for the reminder!
Elben Shira
+4  A: 

The selected answer of Adam Rosenfield is wrong. The answer of coobird too. For that reason I down voted both answers.

Adam Markowitz interpretation of *lineptr++ is right, but he doesn't answer the main question whether this is legal C99 code. Only Tom Future does. Unfortunately he doesn't explain *lineptr++. But I granted them a point each.

So for short, lineptr is a variable and can be manipulated as a pointer. It is thus legal to increment the pointer.

lineptr is a pointer into a sequence of pointers to sequences of chars. In other words it is a pointer to the first string of a string array. According to the code we can assume that the strings are null ('\0') terminated char sequences. nlines is the number of strings in the array.

The while test expression is nlines-- > 0. nlines-- is a post decrement (because -- is on the right of the variable). It is thus executed after the test has been performed and regardless of the test result, so in anycase. So if the nlines value given as argument was 0, the test is performed first and returns false. The instructions in the loop are not executed. Note that since nlines is decremented anyway, the value of nlines after the while loop will be -1.

If nlines is 1, the test will return true and nlines will be decremented. The instructions in the loop will be executed once. Note that while these instructions are executed the value of nlines is 0. When the test is performed again, we are back to the case when nlines was 0.

The printf instruction uses the *lineptr++ expression. It is a post increment of the pointer (++ is on the right of the variable). This means the expression is evaluated first and the increment is performed after its use. So on the first execution printf receives a copy of the first element of the string array, which is a pointer to the first char of the strings. The lineptr is incremented only after that. The next time the printf is to be executed, lineptr points on the second element and will be move to the third when the second string has been printed. This make sense because we obviously want to print the first string. If Adam Rosenfield was right, the first string would have been skipped and at the end we would try to print the string beyond the last one which is obviously a bad thing to do.

So the printf instruction is a concise form of the two following instructions

printf("%s\n", *lineptr);
++lineptr; // or lineptr++ witch is equivalent but less good. lineptr += 1; is ok too.

Note, as a rule of thumb, that when pre and post increment are equivalent in their action, the pre increment is preferable for performance reason. The compilers will take care to switch it for you ... most of the time. Better pick pre operators yourself whenever possible so it is used always. The reason becomes more explicit once you implement a post and pre increment yourself in C++.

chmike
Thanks for clarifying... I was wondering whether I was going crazy or not :)
Adam Markowitz