Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C, if B is volatile, should the expression (void)(B = 1) read B

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?

like image 418
Ned Avatar asked Feb 28 '11 10:02

Ned


People also ask

What happens when a variable is declared volatile?

Volatile is a qualifier that is applied to a variable when it is declared. It tells the compiler that the value of the variable may change at any time-without any action being taken by the code the compiler finds nearby.

What is volatile data type in C?

The volatile qualifier is applied to a variable when we declare it. It is used to tell the compiler, that the value may change at any time. These are some properties of volatile. The volatile keyword cannot remove the memory assignment. It cannot cache the variables in register.

Where volatile variables are stored in C?

on an AVR, both will probably be stored in SRAM. On an ARM, the volatile will be stored in SRAM and the const will probably be stored in flash. This is mainly because the AVR flash is not accessible as "normal memory" to C programs, and you can't put any variables there.

Which of the following items should generally be declared using C's volatile keyword?

Thus, a task asynchronously modifying a shared global is conceptually the same as the ISR scenario discussed above. Thus all shared global objects (variables, memory buffers, hardware registers, etc.) must also be declared volatile to prevent compiler optimization from introducing unexpected behaviors.


2 Answers

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.

like image 125
Jens Gustedt Avatar answered Oct 18 '22 12:10

Jens Gustedt


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...)

like image 34
AProgrammer Avatar answered Oct 18 '22 13:10

AProgrammer