*p++ = *q++You saw this compressed idiom in lesson 2 and were told to keep writing the three-line version for now. Now it's time to own it โ by seeing exactly what the compiler does.
*p = *q; // copy: read q's target, write to p's target
p++; // advance the write head
q++; // advance the read head
This is the two-pointer lockstep from lesson 2 โ clear, explicit, nothing hidden. Every grown-up C programmer compresses it to one line. Let's see why that works.
++Postfix ++ does two things, and the order is the whole lesson:
| Step | What happens |
|---|---|
| 1. Yield | The expression produces the current value (before incrementing). |
| 2. Side effect | The variable is incremented after the value is yielded. |
So p++ means: "give me p's current value, then add 1 to p." It's a post-increment โ use, then change.
++p
Prefix ++p increments first, then yields the new value. Change, then use. We won't need it in this lesson, but knowing it exists is important โ it's the other side of the coin.
* (dereference) operatorYou know *p means "the thing p points at." The question is: in *p++, which binds tighter โ * or ++?
| Expression | Operator precedence | What it means |
|---|---|---|
*p++ | ++ (postfix) binds tighter than * | *(p++) โ dereference the old value of p, then increment p |
(*p)++ | Explicit grouping | Dereference p, then increment the value at that address |
*++p | ++ (prefix) binds tighter than * | Increment p first, then dereference the new address |
*p++ reads from p's current target, then moves p forward. The data you get is from the old position. The pointer has already moved on by the time the expression is done. That's exactly the lockstep move โ in a single expression.
*p++ = *q++The assignment = evaluates right-to-left. The compiler sees:
*p++ = *q++;
// Right side: *q++
1. Yield current value of q โ say, address 0x1000
2. Dereference that address โ say, character 'W'
3. Side effect: q becomes 0x1001
// Left side: *p++
4. Yield current value of p โ say, address 0x2005
5. Dereference that address โ slot at 0x2005
6. Side effect: p becomes 0x2006
// Assignment
7. Write the right-side result ('W') into the left-side slot (0x2005)
Seven steps, one line. And it produces exactly the same result as the three-line version:
*p = *q; // copy 'W' from 0x1000 to 0x2005
p++; // p โ 0x2006
q++; // q โ 0x1001
Nothing has changed about what happens in memory. *p++ = *q++ is just a shorthand for the lockstep copy you already understand. Here's one iteration:
dest: H e l l o '\0' ? ? ? ...
โ
p
str2: W o r l d '\0'
โ
q
After one iteration of *p++ = *q++:
dest: H e l l o W ? ? ...
โ
p (advanced)
str2: W o r l d '\0'
โ
q (advanced)
Same bytes moved, same pointers advanced. Just fewer keystrokes.
Because combining * and ++ creates confusion, here's the complete set. Don't memorize this โ derive each one from the rule "postfix ++ binds tighter than *." The table is for later reference.
| Expression | Expanded | Effect |
|---|---|---|
*p++ | *(p++) | Read value at p, then advance p. โ this is the one you use |
*++p | *(++p) | Advance p first, then read new value. |
(*p)++ | โ | Read value at p, increment the value (not the pointer). |
*--p | *(--p) | Step p back, then read new value. โ reverse copy |
*p-- | *(p--) | Read value at p, then step p back. |
++*p | ++(*p) | Increment the value at p, yield new value. |
*p = *q; p++; q++; โ three lines, explicit, no ambiguity.*p++ = *q++ because you've seen it unrolled. Use it when reading other people's code.*p++ = *q++ in your own code, when the three-line version feels tedious. You'll know you're ready when you can read *p++ = *q++ and instantly see "copy, advance both" without mentally unrolling it.
*p++ evaluate to?p's current address (before the increment). It dereferences the old value, then moves p forward as a side effect. So *p++ yields a char (or whatever p points at), not a pointer.
*p++ and (*p)++?*p++ (i.e. *(p++)) advances the pointer. (*p)++ advances the value the pointer targets. p points at an int holding 42: *p++ yields 42 and moves p to the next int. (*p)++ yields 42 and changes the value at the current address to 43, without moving p.
*p++ = *q++ using prefix increment instead.*p = *q; ++p; ++q; โ you can't directly replace *p++ with *++p because the semantics would change (prefix increments first, so you'd skip the first element). The compact *p++ = *q++ works precisely because both are postfix โ they yield the old position, then advance.
while (*p++ = *q++); โ what's the loop condition actually testing?*q. The loop continues while that character is non-zero (i.e. not '\0'). It stops when *q yields '\0' (which is 0), which also copies that '\0' into *p before terminating. This is actually the classic one-liner strcpy implementation โ it copies the terminator and stops, all in the condition. Elegant, but don't write this in production โ it's a readability trap.
mystrcat using the compact formTake your working mystrcat function from the lesson 2 hands-on and create a second version:
mystrcat_compact.*p++ = *q++;while (*p) p++;. Does it still work? (Yes โ *p is non-zero until '\0', so this skips the explicit != '\0' test.)*p++ = *q++ and instantly know it means "copy, advance both." You can mentally unroll it to the three-line version. You know postfix ++ binds tighter than *, and you know the difference between *p++ and (*p)++. You've also seen the classic one-liner strcpy trick โ even if you shouldn't write it yourself yet.
*p++ + *q++?", "is there a performance difference?", "why does the one-liner strcpy work?" โ type it and we'll dig in.
++ is precedence 2 (second-highest); dereference * is precedence 3.*p++ idiom.