Существуют ли точки последовательности в выражении a ^ = b ^ = a ^ = b, или это undefined?

Предполагаемый "умный" (но фактически неэффективный) способ обмена двумя целыми переменными вместо использования временного хранилища часто включает в себя эту строку:

int a = 10;
int b = 42;

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

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

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

Ответ 1

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

Это поведение undefined.

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

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

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

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

Ответ 2

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

Это не так.

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

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

Ответ 3

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

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

a ^= b, b ^= a, a ^= b;

Ответ 4

Порядок оценки операторов ^= определен корректно. То, что не определено, - это порядок, в котором изменяются a и b.

a ^= b ^= a ^= b;

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

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

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

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

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;

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