Причина, по которой это работает, в том, что XOR не теряет информацию. Вы можете сделать то же самое с обычным сложением и вычитанием, если можете игнорировать переполнение. Например, если пара переменных A, B изначально содержит значения 1,2, вы можете поменять их местами следующим образом:
// A,B = 1,2
A = A+B // 3,2
B = A-B // 3,1
A = A-B // 2,1
Кстати, есть старая хитрость для кодирования двухстороннего связанного списка в одном «указателе».
Предположим, у вас есть список блоков памяти по адресам A, B и C. Первое слово в каждом блоке, соответственно:
// first word of each block is sum of addresses of prior and next block
0 + &B // first word of block A
&A + &C // first word of block B
&B + 0 // first word of block C
Если у вас есть доступ к блоку A, он дает вам адрес B. Чтобы добраться до C, вы берете «указатель» в B и вычитаете A, и так далее. Это работает так же хорошо назад. Чтобы бегать по списку, вам нужно сохранить указатели на два последовательных блока. Конечно, вы бы использовали XOR вместо сложения / вычитания, поэтому вам не придется беспокоиться о переполнении.
Вы можете распространить это на "связанную сеть", если хотите повеселиться.