Обмен значениями с помощью XOR

В чем разница между этими двумя макросами?

#define swap(a, b)    (((a) ^ (b)) && ((a) ^= (b) ^= (a) ^= (b)))

или

#define swap(a, b)    (((a) ^ (b)) && ((b) ^= (a) ^= (b), (a) ^= (b)))

Я увидел второй макрос здесь, но не мог понять, почему он не был написан как первый. Есть ли специальная причина, по которой я пропустил?

Ответы

Ответ 1

Сначала вызывается поведение undefined как в C99, так и в C11.

В C99 это можно понять; они будут вызывать поведение undefined из-за отсутствия точек последовательности.

C-faq:

Между предыдущей и следующей точками последовательности объект должен иметь измененное значение хранимого значения не более одного раза путем оценки выражения. Кроме того, к предыдущему значению следует обращаться только для определения значения, которое необходимо сохранить.

Объяснение:
Первый из них дважды меняет a между двумя точками последовательности, и, следовательно, поведение undefined в соответствии с оператором: между предыдущей и следующей точкой последовательности объект должен иметь значение, которое его хранимое значение изменялось не более одного раза путем оценки выражения. Это (не нужно думать о b).

Документация C11 гласит:

6.5 Выражения (p2):

Если побочный эффект на скалярном объекте не влияет на или на другой побочный эффект на один и тот же скалярный объект или вычисление значения с использованием значения одного и того же скаляра object, поведение undefined. Если существует несколько допустимых порядков подвыражений выражения, поведение undefined, если такой какой-либо побочный эффект имеет место в любом из порядков. 84)

В (a) ^= (b) ^= (a) ^= (b) побочный эффект на a не имеет последовательности и, следовательно, вызывает поведение undefined. Следует отметить, что C11 6.5 p1 говорит, что:

[...] Вычисления значений операндов оператора секвенируются перед вычислением значения результата оператора.

Это гарантирует, что в

(a) ^= (b) ^= (a) ^= (b)  
 |      |      |      | 
 1      2      3      4  

все подвыражения 1, 2, 3 и 4 гарантированно будут вычисляться до вычисления результата самого левого оператора ^=. Но это не гарантирует, что побочный эффект выражения 3 гарантирован перед вычислением значения результата самого левого оператора ^=.


<суб > 1. Акцент мой.

Ответ 2

Первый вызывает undefined поведение на C99 по двум причинам, наиболее очевидным, поскольку вам не разрешено изменять одну и ту же переменную больше, чем один раз в пределах той же точки последовательности и этот макрос изменяет как a, так и b более одного раза, а второй использует оператор запятой:

#define swap(a, b)    (((a) ^ (b)) && ((b) ^= (a) ^= (b), (a) ^= (b)))
                                                        ^

который вводит точку последовательности, но не удаляет все поведение undefined в C99, так как предыдущее значение b считывается для вычисления значения a, но может использоваться только для определения значения, которое нужно сохранить до b.

Соответствующий раздел из стандартного раздела проекта C99 6.5 Выражения, указанные в параграфе 2, говорят (акцент мой вперед):

Между предыдущей и следующей точкой последовательности объект должен иметь сохраненное значение изменен не более чем на один раз оценкой выражения. 72) Кроме того, предыдущее значение должно быть прочитано только для определения значения, которое нужно сохранить. 73)

и для оператора запятой, из раздела 6.5.17 Оператор запятой параграф 2 говорит:

левый операнд оператора запятой оценивается как выражение void; есть после его оценки. [...]

Ответ 3

Чтобы лучше понять, почему первый из них undefined, вот еще один способ его представить:
Это потому, что в C вы не контролируете порядок выполнения среди подвыражений:

a = a^(b=b^(a=a^b))

Для первого появления после =, у компилятора C есть выбор использовать начальное значение a или измененное значение a. Это, таким образом, явно неоднозначно и ведет к поведению undefined.

Второй выглядит мне хорошо, как не двусмысленный:

b = b ^(a=a^b)

Тот факт, что a и b встречаются в первой части выражения (a^b)&&..., для меня не кажется проблемой, потому что && сначала сначала оценивает первую часть. Но тогда я предпочитаю, чтобы эксперты анализировали стандарт, я не эксперт...