12

Предположительно «умный» (но на самом деле неэффективный) способ замены двух целочисленных переменных, вместо использования временного хранения, часто включает в себя строку: Существуют ли точки последовательности в выражении a^= b^= a^= b, или она не определена?

int a = 10; 
int b = 42; 

a ^= b ^= a ^= b; /*Here*/ 

printf("a=%d, b=%d\n", a, b); 

Но я задаюсь вопросом, составные операторы присваивания, как ^= являются а не точки последовательности, не так ли? Означает ли это, что это на самом деле неопределенное поведение?

+2

Если вы пишете код, который трудно сказать, что происходит, спросите себя, есть ли более простой способ, который может понять будущий разработчик? –

+1

Обратите внимание, что если вы видели это в коде на C++, у C++ есть разные правила для операторов присваивания, которые допускают определенные конструкции (я не уверен в этом), которые не определены в C. – hvd

+3

Возможный дубликат [Sequence Point - Xor Swap on Array получить неправильный результат] (http://stackoverflow.com/questions/9958514/sequence-point-xor-swap-on-array-get-wrong-result) –

ответ

17
a ^= b ^= a ^= b; /*Here*/ 

Это неопределенное поведение.

Вы изменяете объект (a) более одного раза между двумя точками последовательности.

(С99, 6.5p2) «Между предыдущей и следующей точкой последовательности объект должен быть его сохраненное значение модифицированного не более одного раза в ходе оценки выражения.

Простые задания, а также соединения задания не вводить точку последовательности. Здесь есть точка последовательности перед выражением выражение заявления и после выражения утверждения.

последовательности точек приведены в приложении с (информативное) от c99 и c11 стандарта.

13

^= не являются точками последовательности, они

Они не являются.

Означает ли это, что это на самом деле неопределенное поведение?

Да, это так. Не используйте эту «умную» технику.

+0

Спасибо за подтверждение. Не волнуйтесь, я никогда не собирался использовать его в первую очередь. – Medinoc

7

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

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

a ^= b, b ^= a, a ^= b; 
+1

На этом этапе вы можете разместить три разных строчки. Это сделает приятную коробку, если вам все это нужно. – Thomas

5

Порядка оценки ^= операторов корректно определен. Что не определено, так это порядок, в котором изменены a и b.

a ^= b ^= a ^= b; 

эквивалентно

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

Оператор не может быть оценена, прежде чем ее аргументы оцениваются, так что, безусловно, будет выполнять a ^= b первый.

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

int a1 = a^b; 
int b1 = b^a1; 
int a2 = a^b1; 
a = a1; 
a = a2; 
b = b1; 

или это:

int a1 = a^b; 
int b1 = b^a1; 
a = a1; 
int a2 = a^b1; 
a = a2; 
b = b1; 

или даже это:

int a1 = a^b; 
int b1 = b^a1; 
int a2 = a^b1; 
a = a2; 
a = a1; 
b = b1; 

Если компилятор может выбрать только один из этих трех способов сделать вещи, это будет просто «неуказанное» поведение. Однако стандарт идет дальше и делает это «неопределенным» поведением, что в основном позволяет компилятору предположить, что он даже не может произойти.

+0

Первое объяснение - это окончательное, но я не мог понять причину: * «предварительно измененные или измененные значения» * –

+1

Это вводит в заблуждение. Ничего не мешает побочному эффекту выражения после того, как оценка уже завершена. Результатом 'a^= b' является' a^b', а в качестве побочного эффекта 'a' устанавливается на этот результат. * Когда * 'a' установлен на этот результат, он не указан. В частности, нет ничего, что требовало бы, чтобы он закончил * до того, как начнется внешнее 'a^= ...'. – hvd

+0

@hvd: Я согласен. Я попытался изменить его, чтобы сделать его более ясным. –