Logic behind bitwise operators in C

Note that you can do the same thing without bitwise operators (at least for unsigned integer types since they can't overflow into undefined behavior):

        // i == x     j == y
i += j; // i == x+y   j == y
j -= i; // i == x+y   j == -x
i += j; // i == y     j == -x
j = -j; // i == y     j == x

Now if we do this bit for bit, but modulo 2 instead of modulo UINT_MAX+1, the XOR operation implements both addition and subtraction, and the final negation is a no-op because $-1\equiv 1$ and $-0\equiv 0 \pmod 2$. So what is left in the bitwise version is exactly

i ^= j; j ^= i; i ^= j;

In algebraic terms, the XOR operator (or $\oplus$) is nothing other than addition modulo $2$: use $1$ and $0$ for true and false, along with $1 \oplus 1 = 0$.

Now, since addition modulo $2$ is associative and commutative, and both elements are their own inverses, we have $$\begin{align} d &= b \oplus c\\ &= b \oplus (a \oplus b)\\ &= b \oplus (b \oplus a)\\ &= (b \oplus b) \oplus a\\ &= a.\\ \end{align}$$

We can show $e = b$ using similar reasoning.


You already answered your question, but if you want an algebraic explanation note that for any $x$:

$$x \oplus 0 = x$$

$$x \oplus x = 0$$

So:

$$i_0 = i, j_0 = j$$

$$i_1 = i_0 \oplus j_0, j_1 = j_0$$

$$i_2 = i_1, j_2 = i_1 \oplus j_1 = i_0 \oplus j_0 \oplus j_0 = i_0$$

$$i_3 = i_2 \oplus j_2 = i_1 \oplus i_0 = i_0 \oplus j_0 \oplus i_0 = j_0, j_3 = j_2 = i_0$$