Замена значений с помощью XOR

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

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

Or

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

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


person yunusaydin    schedule 30.12.2013    source источник
comment
@quamrana спасибо за форматирование   -  person yunusaydin    schedule 30.12.2013
comment
а также @user3075488   -  person yunusaydin    schedule 30.12.2013


Ответы (3)


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

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

C-faq:

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

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

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

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

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

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

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

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

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

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


1. Акцент сделан на мне.

person haccks    schedule 30.12.2013
comment
Во втором есть ,. - person Uchia Itachi; 30.12.2013
comment
@UchiaItachi: Не думаю, что это имеет значение. b ^= a ^= b все еще UB. - person Oliver Charlesworth; 30.12.2013
comment
@OliCharlesworth Это действительно UB? В C11 мне кажется, что вычисление значения правого b будет упорядочено до вычисления значения результата a ^= b и, следовательно, перед присвоением b. Так что я не понимаю, как это UB. Хотя это может быть UB в C99. - person interjay; 30.12.2013
comment
@interjay: Это определенно UB в C99. Если C11 добавил дополнительные ограничения последовательности, это нормально, но пока большая часть мира не использует C11, вероятно, безопаснее использовать C99 в качестве базовой линии... (или, по крайней мере, четко указать это различие в ответе) - person Oliver Charlesworth; 30.12.2013
comment
@ОлиЧарльсворт; В ПОРЯДКЕ. Второй не вызывает неопределенное поведение ни в C99, ни в C11. the prior value shall be accessed only to determine the value to be stored не может применяться в случае (b) ^= (a) ^= (b). - person haccks; 30.12.2013

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

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

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

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

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

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

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

person Shafik Yaghmour    schedule 30.12.2013
comment
Разве это не одна причина; а именно отсутствие точек следования? - person Oliver Charlesworth; 30.12.2013
comment
@OliCharlesworth, вторая причина заключается в том, что предыдущее значение a и b используется не для определения сохраняемого значения, поэтому добавление оператора запятой не устраняет все неопределенное поведение. - person Shafik Yaghmour; 30.12.2013
comment
Но это та же самая причина; это приводит к UB, потому что в b ^= a ^= b недостаточно точек следования. Добавление оператора запятой по-прежнему не добавляет достаточного количества точек последовательности. - person Oliver Charlesworth; 30.12.2013
comment
@OliCharlesworth, это справедливое замечание, но это несколько разные причины, как видно из второго фрагмента кода, который, как кто-то, очевидно, думал, удалил все неопределенное поведение. - person Shafik Yaghmour; 30.12.2013
comment
@ШафикЯгмур; Я думаю, что мы ошибаемся. Второй также не вызывает неопределенное поведение в C99. Оператор предыдущее значение должно быть прочитано только для определения сохраняемого значения. не имеет ничего общего с (b) ^= (a) ^= (b). - person haccks; 30.12.2013

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

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

Для первого а, встречающегося после =, компилятор C может использовать начальное значение а или измененное значение а. Таким образом, это явно неоднозначно и приводит к неопределенному поведению.

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

b = b ^(a=a^b)

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

person aka.nice    schedule 30.12.2013