Подтвердить что ты не робот

Приращение изменчивой переменной в C

Рассмотрим следующие три выражения:

++x;
x += 1;
x = x + 1;

Насколько мне известно, они идентичны в семантике, игнорируя перегрузку операторов на С++. Однако сегодня я прочитал утверждение, что они разные, особенно когда x объявлено volatile.

Чтобы проверить это утверждение, я написал следующее и скомпилировал его для PowerPC, AMD64, ARMv6 и 68k:

#include <stdint.h>

static volatile uint64_t x = 0;

void a(void)
{
    ++x;
}

void b(void)
{
    x += 1;
}

void c(void)
{
    x = x + 1;
}

На всех четырех этих платформах три функции генерируют идентичный выход ассемблера, независимо от того, находятся ли они на -O1 или -O3. На AMD64 это было всего две инструкции:

incq    _x(%rip)
retq

Следовательно, есть ли какая-то правда за этим утверждением? Если да, в чем разница, и как я могу его выставить?

NB: Я прекрасно понимаю, что volatile не гарантирует атомарность. Это не то, о чем я прошу здесь, - если сама атомарность не отличается от трех.

4b9b3361

Ответ 1

Из проекта стандартного раздела С++ 5.3.2 [expr.pre.incr] говорится:

Если x не имеет тип bool, выражение ++ x эквивалентно x + = 1

и 5.17 [expr.ass] говорит:

Поведение выражения вида E1 op = E2 эквивалентно E1 = E1 op E2, за исключением того, что E1 оценивается только один раз.

Итак, ++x и x += 1 эквивалентны.

Теперь один случай, когда x += 1 отличается от x = x + 1, заключается в том, что E1 оценивается только один раз. В этом конкретном случае это не имеет значения, но мы можем придумать случай, когда он это делает:

#include <stdint.h>

volatile uint64_t x = 0;
volatile uint64_t y[2] = {0} ;

void c(void)
{
   y[x] = y[x] + 1;
}

в этом случае x будет оцениваться дважды в противоположность этому случаю:

void b(void)
{
   y[x] += 1;
}

и сеанс godbolt показывает для b():

b():                                  # @b()
movq    x(%rip), %rax
incq    y(,%rax,8)
retq

и для c():

c():                                  # @c()
movq    x(%rip), %rax
movq    y(,%rax,8), %rax
incq    %rax
movq    x(%rip), %rcx
movq    %rax, y(,%rcx,8)
retq

Насколько я могу судить, это относится и к C11. Из раздела C11 6.5.3.1 Операторы приращения и уменьшения префиксов:

Выражение ++ E эквивалентно (E + = 1).

и из раздела 6.5.16.2 Составное назначение:

Сопутствующее присвоение формы E1 op = E2 эквивалентно простому выражение E1 = E1 op (E2), за исключением того, что lvalue E1 является оценивается только один раз

Ответ 2

В абстрактной семантике все три этих выражения делают то же самое. Они получают доступ к x, чтобы получить его значение, вычислить новое значение, а затем сохранить обновленное значение обратно в x. Существует доступ и магазин. (Выражения также дают значение, которое отбрасывается).

Хотя x = x + 1 дважды упоминает x, левая сторона x не оценивается. То есть не полностью: его значение не вычисляется. Он оценивается только в той степени, в которой определяется местоположение, в котором будет присвоено заданное значение.

Таким образом, здесь возможно двойная оценка местоположения: левая сторона определяет местоположение x, а также правую сторону. Но определение местоположения не предполагает доступа к самому местоположению.

Для некоторых видов выражений определение местоположения подразумевает доступ к значениям. Например:

a[i] = a[i] + 1;

Это сильно отличается от

i = i + 1

потому что i является только второстепенной переменной, значение которой должно быть известно для определения местоположения хранилища a[i]i сам по себе даже не увеличивается). Если i - volatile, то два абстрактных обращения к нему в a[i] = a[i] + 1 должны соответствовать двум действительным доступам.