14

I work on compilers for a couple of embedded platforms. A user has recently complained about the following behaviour from one of our compilers. Given code like this:

extern volatile int MY_REGISTER;

void Test(void)
{
    (void) (MY_REGISTER = 1);
}

The compiler generates this (in pseudo-assembler):

Test:
    move regA, 1
    store regA, MY_REGISTER
    load regB, MY_REGISER

That is, it not only writes to MY_REGISTER, but reads it back afterwards. The extra load upset him for performance reasons. I explained that this was because according to the standard "An assignment expression has the value of the left operand after the assignment, [...]".

Strangely, removing the cast-to-void changes the behaviour: the load disappears. The user's happy, but I'm just confused.

So I also checked this out in a couple of versions of GCC (3.3 and 4.4). There, the compiler never generates a load, even if the value is explicitly used, e.g.

int TestTwo(void)
{
    return (MY_REGISTER = 1);
}

Turns into

TestTwo:
    move regA, 1
    store regA, MY_REGISTER
    move returnValue, 1
    return

Does anyone have a view on which is a correct interpretation of the standard? Should the read-back happen at all? Is it correct or useful to add the read only if the value is used or cast to void?

Ned
  • 2,142
  • 17
  • 24

3 Answers3

7

The relevant paragraph in the standard is this

An assignment operator stores a value in the object designated by the left operand. An assignment expression has the value of the left operand after the assignment, but is not an lvalue. The type of an assignment expression is the type of the left operand unless the left operand has qualified type, in which case it is the unqualified version of the type of the left operand. The side effect of updating the stored value of the left operand shall occur between the previous and the next sequence point.

So this clearly makes the difference between "the value of the left operand" and the update of the stored value. Also note that the return is not an lvalue (so there is no reference to the variable in the return of the expression) and all qualifiers are lost.

So I read this as gcc doing the right thing when it returns the value that it knowingly has to store.

Edit:

The upcoming standard plans to clarify that by adding a footnote:

The implementation is permitted to read the object to determine the value but is not required to, even when the object has volatile-qualified type.

Edit 2:

Actually there is another paragraph about expression statements that might shed a light on that:

The expression in an expression statement is evaluated as a void expression for its side effects.\footnote{Such as assignments, and function calls which have side effects}

Since this implies that the effect of returning a value is not wanted for such a statement, this strongly suggests that the value may only be loaded from the variable if the value is used.

As a summary, your customer really is rightly upset when he sees that the variable is loaded. This behavior might be in accordance with the standard if you stretch the interpretation of it, but it clearly is on the borderline of being acceptable.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • 2
    so what about my example `volatile` variable in my comment on Jim's answer? Why is the value of the left operand the value assigned, as opposed to the value that you would always get by reading it? I'm not claiming that there *should* be a load, necessarily, just that I don't think it's at all clear that there *shouldn't* be a load. – Steve Jessop Feb 28 '11 at 11:27
  • @Steve, we are in complete agreement on that. Please see my edit. – Jens Gustedt Feb 28 '11 at 12:15
  • 1
    "evaluated as a void expression for its side effects" - but reading volatile variables could also be an example of "side-effects". So I don't think it's a stretch to load it, but it *is* a QOI issue. Since the implementation isn't guaranteed to perform the read, and since the read is observable behavior, no strictly conforming program can depend on whether the read is performed or not. Hence it is always an optimization *not* to perform it. – Steve Jessop Feb 28 '11 at 13:39
  • I think it's an extreme interpretation to load the value of `lhs` in `x = lhs = 0`. Please note that loading `lhs` is mandatory in C++. You probably don't want to write such code! – curiousguy Oct 26 '11 at 20:05
  • Surely this is a case of implementation defined? The compiler can choose anyway it likes to evaluate the expression (for its side-effects) and then through away the result. If the compiler is smart and using optimizations it can avoid the read (since the type of the expression is unqualified, so longer no longer volatile), but if it is unoptimized nothing precludes reading the result from the left-hand operand. – benno Jan 05 '13 at 09:24
6

Reading back seems to be nearer to the standard (especially considering that reading a volatile variable can result in a different value than the one written), but I'm pretty sure it isn't what is expected by most code using volatile, especially in contexts where reading or writing a volatile variable triggers some other effects.

volatile in general isn't very well defined -- "What constitutes an access to an object that has volatile-qualified type is implementation-defined."

Edit: If I had to make a compiler, I think I wouldn't read back the variable if it isn't used and reread it if is, but with a warning. Then should a cast to void be an used?

(void) v;

should surely be one, and considering that, I don't any reason for

(void) v = exp;

not to be. But in any case, I'd give a warning explaining how to get the other effect.

BTW, If you work on a compiler, you probably have someone in contact with the C committee, filling a formal defect report will bring you a binding interpretation (well, there is the risk of the DR being classified "Not A Defect" without any hint about what they want...)

AProgrammer
  • 51,233
  • 8
  • 91
  • 143
1

The language in the standard says nothing about reading the volatile variable, only what the value of the assignment expression is, which a) is defined by C semantics, not by the content of the variable and b) isn't used here, so need not be calculated.

Jim Balter
  • 16,163
  • 3
  • 43
  • 66
  • 2
    kind of the point of `volatile`, though is that the value *assigned* to the variable, and the value *of* the variable (in the sense of what you get by reading it) aren't necessarily the same thing. So since the standard says the value *of* the variable, it's not immediately obvious that this is intended to mean the value assigned, and that it has nothing to do with reading the variable. You could very well have a volatile int which always reads as 0, but where writing to it makes the speaker beep that number of times. So what's "the value of the variable" after an assignment? Shrug. – Steve Jessop Feb 28 '11 at 11:23
  • @Steve Indeed. Having now read the standard carefully I don't think this is specified. It says "the value of the left operand", but doesn't quite say what that is. – Jim Balter Feb 28 '11 at 11:31
  • @SteveJessop The English description clearly is defective. The fact that in C the expression is an rvalue, OTOH, is pretty clear. Beware, it's an lvalue in C++. – curiousguy Oct 26 '11 at 20:11