Существуют ли точки последовательности в выражении 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;
Если бы компилятор мог выбрать только один из этих трех способов, это было бы просто "неопределенным" поведением. Тем не менее, стандарт идет дальше и делает это поведение неопределенным, что в основном позволяет компилятору предполагать, что это даже не может произойти.