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

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

int a = 10;
int b = 42;

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

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

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

4 ответа

Решение
a ^= b ^= a ^= b; /*Here*/

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

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

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

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

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

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

Они не.

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

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

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

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

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

Порядок оценки ^= операторы четко определены. Что не очень хорошо определено, так это порядок, в котором 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;

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

Другие вопросы по тегам