Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct behaviour of trivial statements involving expressions with volatile variables?

Consider the following statements

volatile int a = 7;
a;   // statement A
volatile int* b = &a;
*b;  // statement B
volatile int& c = a;
c;   // statement C

Now, I've been trying to find a point in the standard that tells me how a compiler is to behave when coming across these statements. All I could find is that A (and possibly C) gives me an lvalue, and so does B:

"§ 5.1.1.8 Primary expressions - General" says

An identifier is an id-expression provided it has been suitably declared (Clause 7). [..]
[..] The result is the entity denoted by the identifier. The result is an lvalue if the entity is a function, variable, or data member and a prvalue otherwise.
[..]

"§ 5.3.1 Unary operators" says

The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.

clang and gcc

I tried this with clang++ 3.2-11 and g++ 4.7.3, and the first produced three reads in C++11 mode and zero reads in C++03 mode (outputting three warnings) while g++ only produced the first two, explicitly warning me that the third would not be generated.

Question

It is clear which type of value comes out of the expression, from the quoted line in the standard, but:
which of the statements (A,B,C) should produce a read from the volatile entity according to the C++ standard?

like image 583
bitmask Avatar asked Nov 27 '13 12:11

bitmask


2 Answers

The G++ warning about the "implicit dereference" comes from code in gcc/cp/cvt.c which intentionally does not load the value through a reference:

    /* Don't load the value if this is an implicit dereference, or if
       the type needs to be handled by ctors/dtors.  */
    else if (is_volatile && is_reference)

G++ does that intentionally, because as stated in the manual (When is a Volatile C++ Object Accessed?) the standard is not clear about what constitutes an access of a volatile-qualified object. As stated there you need to force lvalue-to-rvalue conversion to force a load from a volatile.

Clang gives warnings in C++03 mode that indicate a similar interpretation:

a.cc:4:3: warning: expression result unused; assign into a variable to force a volatile load [-Wunused-volatile-lvalue]
  a;   // statement A
  ^
a.cc:6:3: warning: expression result unused; assign into a variable to force a volatile load [-Wunused-volatile-lvalue]
  *b;  // statement B
  ^~
a.cc:8:3: warning: expression result unused; assign into a variable to force a volatile load [-Wunused-volatile-lvalue]
  c;   // statement C
  ^
3 warnings generated.

The G++ behaviour and the GCC manual seem to be correct for C++03, but there is a difference in C++11 relative to C++03, introduced by DR 1054 (which also explains why Clang behaves differently in C++)3 and C++11 modes). 5 [expr] p10 defines a discarded-value-expression and says that for volatiles the lvalue-to-rvalue conversion is applied to an id-expression such as your statements A and C. The spec for lvalue-to-rvalue conversion (4.1 [conv.lval]) says that the result is the value of the glvalue, which constitutes an access of the volatile. According to 5p10 all three of your statements should be accesses, so G++'s handling of statement C needs to be updated to conform to C++11. I've reported it as http://gcc.gnu.org/bugzilla/show_bug.cgi?id=59314

like image 80
Jonathan Wakely Avatar answered Sep 30 '22 12:09

Jonathan Wakely


This gcc document 7.1 When is a Volatile C++ Object Accessed? is relevant here, and I quote (emphasis mine going forward):

The C++ standard differs from the C standard in its treatment of volatile objects. It fails to specify what constitutes a volatile access, except to say that C++ should behave in a similar manner to C with respect to volatiles

The C and C++ language specifications differ when an object is accessed in a void context:

and provides this example:

volatile int *src = somevalue;
*src;

and continues by saying:

The C++ standard specifies that such expressions do not undergo lvalue to rvalue conversion, and that the type of the dereferenced object may be incomplete. The C++ standard does not specify explicitly that it is lvalue to rvalue conversion that is responsible for causing an access.

which should be referring to draft standard section 5.3.1 Unary operators paragraph 1 which says :

The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points. [...]

and with respect to references:

When using a reference to volatile, G++ does not treat equivalent expressions as accesses to volatiles, but instead issues a warning that no volatile is accessed. The rationale for this is that otherwise it becomes difficult to determine where volatile access occur, and not possible to ignore the return value from functions returning volatile references. Again, if you wish to force a read, cast the reference to an rvalue.

so it looks like gcc is choosing to treat references to volatile differently and in order to force a read you need to cast to an rvalue, for example:

static_cast<volatile int>( c ) ;

which generates a prvalue and hence a lvalue to rvalue conversion, from section 5.2.9 Static cast:

The result of the expression static_cast(v) is the result of converting the expression v to type T. If T is an lvalue reference type or an rvalue reference to function type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue.

Update

The C++11 draft standard adds 5 Expressions paragraph 11 which says:

In some contexts, an expression only appears for its side effects. Such an expression is called a discarded-value expression. The expression is evaluated and its value is discarded. The array-to-pointer (4.2) and functionto-pointer (4.3) standard conversions are not applied. The lvalue-to-rvalue conversion (4.1) is applied if and only if the expression is an lvalue of volatile-qualified type and it is one of the following:

and includes:

— id-expression (5.1.1),

This seems ambiguous to me since with respect to a; and c; section 5.1.1 p8 says it is an lvalue and it is not obvious to me that it covers this case but as Jonathan found DR 1054 says it does indeed cover this case.

like image 26
Shafik Yaghmour Avatar answered Sep 30 '22 12:09

Shafik Yaghmour