简体   繁体   中英

Incrementing a volatile variable in C

Consider the following three expressions:

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

As far as I'm aware, they are identical in semantics, ignoring operator overloading in C++. However, today I read an assertion that they are different, specifically when x is declared volatile .

To test this assertion, I wrote the following and compiled it for PowerPC, AMD64, ARMv6 and 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;
}

On all four of these platforms, the three functions produced identical assembler output, whether at -O1 or -O3. On AMD64, that was just two instructions:

incq    _x(%rip)
retq

Therefore, is there any truth behind that assertion? If so, what is the difference, and how can I expose it?

NB: I'm perfectly aware that volatile doesn't guarantee atomicity. That's not what I'm asking about here - unless the atomicity itself is what is different between the three.

From the draft C++ standard section 5.3.2 [expr.pre.incr] says:

If x is not of type bool, the expression ++x is equivalent to x+=1

and 5.17 [expr.ass] says:

The behavior of an expression of the form E1 op = E2 is equivalent to E1 = E1 op E2 except that E1 is evaluated only once.

So ++x and x += 1 are equivalent.

Now the one case where x += 1 differs from x = x + 1 is that E1 is only evaluated once. In this particular case it does not matter but we can come up with a case where it does:

#include <stdint.h>

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

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

in this case the x will be evaluated twice as opposed to this case:

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

and a godbolt session shows for b() :

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

and for c() :

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

As far as I can tell this applies to C11 as well. From C11 section 6.5.3.1 Prefix increment and decrement operators:

The expression ++E is equivalent to (E+=1).

and from section 6.5.16.2 Compound assignment:

Acompound assignment of the form E1 op= E2 is equivalent to the simple assignment expression E1 = E1 op (E2), except that the lvalue E1 is evaluated only once

In the abstract semantics, all three of these expressions do exactly the same thing. They access x to retrieve its value, calculate the new value, then store the updated value back in x . There is an access and a store. (The expressions also yield a value, which is being discarded).

Although x = x + 1 mentions x twice, the left side x isn't evaluated. That is to say, not completely: its value is not computed. It is evaluated only to the extent of determining the location where the assigned value will go.

So there is possibly a double evaluation of location here: the left side determines the location of x and so does the right side. But determining location doesn't involve accessing the location itself.

For some kinds of expressions, determining the location does involve accessing values. For example:

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

This is quite different from

i = i + 1

because i is only a secondary variable here whose value must be known in order to determine the storage location of a[i] (and i isn't itself even being incremented). If i is volatile , then the two abstract accesses to it in a[i] = a[i] + 1 must correspond to two actual accesses.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM