Инкремент изменчивой переменной в C
Рассмотрим следующие три выражения:
++x;
x += 1;
x = x + 1;
Насколько я знаю, они идентичны по семантике, игнорируя перегрузку операторов в C++. Однако сегодня я прочитал утверждение, что они разные, особенно когда 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
не гарантирует атомарность. Это не то, о чем я здесь спрашиваю - если только атомарность не отличается между этими тремя.
2 ответа
Из проекта стандартного раздела C++ 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
Насколько я могу судить, это относится и к С11. Из раздела С11 6.5.3.1
Префиксные операторы увеличения и уменьшения:
Выражение ++E эквивалентно (E+=1).
и из раздела 6.5.16.2
Составное назначение:
Составное присваивание в форме E1 op = E2 эквивалентно выражению простого присваивания E1 = E1 op (E2), за исключением того, что значение E1 вычисляется только один раз
В абстрактной семантике все эти три выражения делают одно и то же. Они имеют доступ 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
должен соответствовать двум фактическим доступам.