Почему a = (a + b) — (b = a) плохой выбор для замены двух целых чисел?

Я наткнулся на этот код для замены двух целых чисел без использования временной переменной или использования побитовых операторов.

int main(){

int a=2,b=3;
printf("a=%d,b=%d",a,b);
a=(a+b)-(b=a);
printf("\na=%d,b=%d",a,b);
return 0;
}

Но я думаю, что этот код имеет неопределенное поведение в операторе подкачки a = (a+b) - (b=a); так как не содержит последовательность точек определить порядок оценки.

Мой вопрос: Является ли это приемлемым решением для замены двух целых чисел?

58

Решение

Нет, это не приемлемо Этот код вызывает Неопределенное поведение. Это из-за операции на b не определено. В выражении

a=(a+b)-(b=a);

не уверен b изменяется первым или его значение используется в выражении (a+b) из-за отсутствия точка последовательности.
Посмотрите, что стандартно syas:

C11: 6.5 Выражения:

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

Читать C-faq- 3.8 и это ответ для более подробного объяснения точки последовательности и неопределенного поведения.


1. Акцент мой.

83

Другие решения

Мой вопрос — это приемлемое решение поменять местами два целых числа?

Приемлемо для кого? Если вы спрашиваете, приемлемо ли это для меня, это не поможет пройти проверку кода, в которой я был, поверьте мне.

почему a = (a + b) — (b = a) плохой выбор для замены двух целых чисел?

По следующим причинам:

1) Как вы заметили, в C нет гарантии, что это действительно так. Это может сделать что угодно.

2) Предположим, ради аргумента, что он действительно поменяет местами два целых числа, как в C #. (C # гарантирует, что побочные эффекты будут происходить слева направо.) Код все равно будет неприемлемым, потому что совершенно не очевидно, в чем его смысл! Код не должен быть кучей умных трюков. Напишите код для человека, идущего за вами, который должен прочитать и понять его.

3) Опять же, предположим, что это работает. Код по-прежнему неприемлем, потому что это просто ложь:

Я наткнулся на этот код для замены двух целых чисел без использования временной переменной или использования побитовых операторов.

Это просто ложь. Этот трюк использует временную переменную для хранения вычислений a+b, Переменная генерируется компилятором от вашего имени и не имеет имени, но она есть. Если цель состоит в том, чтобы устранить временных, это делает это хуже, а не лучше! И почему вы хотите устранить временные потери? Они дешевые!

4) Это работает только для целых чисел. Много вещей нужно поменять местами, кроме целых чисел.

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

48

Есть как минимум две проблемы с a=(a+b)-(b=a),

Тот, который вы упоминаете сами: отсутствие точек последовательности означает, что поведение не определено. Таким образом, все может случиться. Например, нет гарантии того, что оценивается первым: a+b или же b=a, Компилятор может сначала сгенерировать код для назначения или сделать что-то совершенно другое.

Другая проблема заключается в том, что переполнение арифметики со знаком является неопределенным поведением. Если a+b переполнения нет гарантии результатов; даже исключение может быть брошено.

32

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

Так что, скорее всего, ваш код медленнее, сложнее для понимания и, вероятно, ненадежным неопределенным поведением.

29

Если вы используете GCC и -Wall компилятор уже предупреждает вас

a.c: 3: 26: предупреждение: операция на «b» может быть неопределенной [-Wsequence-point]

Следует ли использовать такую ​​конструкцию спорно с точки производительности, а также. Когда вы смотрите на

void swap1(int *a, int *b)
{
*a = (*a + *b) - (*b = *a);
}

void swap2(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}

и изучить код сборки

swap1:
.LFB0:
.cfi_startproc
movl    (%rdi), %edx
movl    (%rsi), %eax
movl    %edx, (%rsi)
movl    %eax, (%rdi)
ret
.cfi_endproc

swap2:
.LFB1:
.cfi_startproc
movl    (%rdi), %eax
movl    (%rsi), %edx
movl    %edx, (%rdi)
movl    %eax, (%rsi)
ret
.cfi_endproc

вы не видите никакой пользы от запутывания кода.


Рассматривая код C ++ (g ++), который делает то же самое, но занимает move в учетную запись

#include <algorithm>

void swap3(int *a, int *b)
{
std::swap(*a, *b);
}

дает одинаковый вывод сборки

_Z5swap3PiS_:
.LFB417:
.cfi_startproc
movl    (%rdi), %eax
movl    (%rsi), %edx
movl    %edx, (%rdi)
movl    %eax, (%rsi)
ret
.cfi_endproc

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

28

Заявление:

a=(a+b)-(b=a);

вызывает неопределенное поведение. Второе в цитируемом пункте нарушается:

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

8

Вопрос был отправлен обратно в 2010 с точно таким же примером.

a = (a+b) - (b=a);

Стив Джессоп предупреждает против этого:

Кстати, поведение этого кода не определено. И a и b читаются
и написано без промежуточной точки последовательности. Для начала,
Компилятор будет в пределах своих прав, чтобы оценить b = a до
оценивая a + b.

Вот объяснение вопроса, размещенного в 2012. Обратите внимание, что образец не именно так то же самое из-за отсутствия скобок, но ответ все равно актуален.

В C ++ подвыражения в арифметических выражениях не имеют временного
упорядоченность.

a = x + y;

Сначала оценивается x или y? Компилятор может выбрать либо
выбрать что-то совершенно другое. Порядок оценки не
то же самое, что и приоритет оператора: приоритет оператора строго
определены и порядок оценки определяется только по гранулярности
что ваша программа имеет точки последовательности.

Фактически, на некоторых архитектурах возможно испускать код, который
оценивает x и y одновременно — например, VLIW
архитектуры.

Теперь для стандартных цитат С11 от N1570:

Приложение J.1 / 1

Это неопределенное поведение, когда:

— Приказ
в каких подвыражениях оцениваются и в каком порядке
эффекты имеют место, за исключением случаев, указанных для вызова функции (), &&,
||, ? :и операторы запятой (6.5).

— порядок, в котором оцениваются операнды оператора присваивания (6.5.16).

Приложение J.2 / 1

Это неопределенное поведение, когда:

— Побочный эффект на скалярный объект не секвенирован относительно
другой побочный эффект на тот же скалярный объект или вычисление значения
используя значение того же скалярного объекта (6.5).

6.5 / 1

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

6.5 / 2

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

6.5 / 3

Группировка операторов и операндов указывается синтаксисом. 85)
За исключением случаев, указанных позже, побочные эффекты и расчет стоимости
подвыражения не упорядочены. 86)

Вы не должны полагаться на неопределенное поведение.

Некоторые альтернативы: в C ++ вы можете использовать

  std::swap(a, b);

XOR-своп:

  a = a^b;
b = a^b;
a = a^b;
8

Проблема в том, что в соответствии со стандартом C ++

Если не указано иное, оценки операндов отдельных операторов
и подвыражений отдельных выражений не упорядочены.

Так что это выражение

a=(a+b)-(b=a);

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

5
По вопросам рекламы [email protected]