Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Requirements for behavior of pointer-to-volatile pointing to non-volatile object

C11 6.7.3 Type qualifiers, paragraph 7, reads:

An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3.

In the following example, is the object accessed in the third line subject to the above rule?

int x;
volatile int *p = &x;
*p = 42;

In other words, does the fact that the lvalue *p has type volatile int mean that a volatile object is being accessed, or does the fact that p points to a non-volatile object x mean that the compiler can optimize with this knowledge and omit the volatile access?

Since it may be of interest, the particular usage case I'm interested in is outside the scope of plain C; it involves atomics for thread synchronization using pre-C11 constructs (which could be inline asm or simply thought of as a black box) for atomic compare-and-swap, with the following idiom:

do {
    tmp = *p;
    new = f(tmp);
} while (atomic_cas(p, tmp, new) != success);

Here the pointer p would have type volatile int *, but I'm concerned about what happens when the actually pointed-to object is non-volatile, particularly whether the compiler could transform the single access to *p from tmp = *p into two accesses of the following form:

do {
    new = f(*p);
} while (atomic_cas(p, *p, new) != success);

which would obviously render the code incorrect. Thus the goal is to determine whether all such pointed-to objects actually need to be volatile int.

like image 500
R.. GitHub STOP HELPING ICE Avatar asked Feb 22 '15 04:02

R.. GitHub STOP HELPING ICE


People also ask

Can pointer be volatile?

Yes, a pointer can be volatile if the variable that it points to can change unexpectedly even though how this might happen is not evident from the code. An example is an object that can be modified by something that is external to the controlling thread and that the compiler should not optimize.

Can we have a volatile pointer in C?

Volatile is used in C programming when we need to go and read the value stored by the pointer at the address pointed by the pointer. If you need to change anything in your code that is out of compiler reach you can use this volatile keyword before the variable for which you want to change the value.

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

The volatile keyword must be used when declaring shared memory that can be modified by threads in other blocks to guarantee that the compiler will not load the shared memory location into a register.


2 Answers

Update 18 February 2017

The answer below quotes & discusses the language in the Standard, some contradictory language in the Rationale and some comments from gnu.cc re the contradiction. There is a defect report which essentially has committee agreement (although still open) that the Standard should say, and that the intent has always been, and that implementations have always reflected, that it is not the volatility of an object that matters (per the Standard) but of the volatility of (the lvalue of) an access (per the Rationale). (Credit to Olaf for mentioning this DR.)

Defect Report Summary for C11 Version 1.10 Date: April 2016 DR 476 volatile semantics for lvalues 04/2016 Open


No. Because the object accessed is not volatile.

Object p is of type pointer to volatile int. But x is not an object of a volatile-qualified type. The qualifications on p affect what accesses can be made through it, but do not affect the type of the object that it points to. There is no restriction on accessing a non-qualified-type object via a volatile lvalue. So accessing x through p is not an access of a object of a volatile-qualified type.

(See 6.7.3 Type qualifiers for the restrictions on accessing objects of qualified types. It just says you can't access a volatile qualified object via an unqualified lvalue.)

On the other hand, this post quotes from the 6.7.3 of the Rationale for International Standard--Programming Languages--C:

A cast of a value to a qualified type has no effect; the qualification (volatile, say) can have no effect on the access since it has occurred prior to the case. If it is necessary to access a non-volatile object using volatile semantics, the technique is to cast the address of the object to the appropriate pointer-to-qualified type, then dereference that pointer.

However, I can't find language in the standard that says that the semantics is based on the lvalue type. From gnu.org:

One area of confusion is the distinction between objects defined with volatile types, and volatile lvalues. From the C standard's point of view, an object defined with a volatile type has externally visible behavior. You can think of such objects as having little oscilloscope probes attached to them, so that the user can observe some properties of accesses to them, just as the user can observe data written to output files. However, the standard does not make it clear whether users can observe accesses by volatile lvalues to ordinary objects.

[..] it is not clear from the standard whether volatile lvalues provide more guarantees in general than nonvolatile lvalues, if the underlying objects are ordinary.

No, because there are no side effects:

Even if the semantics of *p must be that of a volatile, the standard nevertheless says:

5.1.2.3 Program execution 4 In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).

Again, there is no volatile object in your code. Although a compilation unit that only sees p couldn't make that optimization.

Also keep in mind

6.7.3 Type qualifiers 7 [...] What constitutes an access to an object that has volatile-qualified type is implementation-defined.

5.1.2.3 Program execution 8 More stringent correspondences between abstract and actual semantics may be defined by each implementation.

So mere appearances of volatile lvalues does not tell you what "accesses" there are. You have no right to talk about "the single access to *p from tmp = *p" except per documented implementation behaviour.

like image 186
philipxy Avatar answered Oct 30 '22 01:10

philipxy


Not completely sure, but I think the point is the difference between the type an object has and the type an object is defined with.

From C11 (n1570) 6.3.2.1 p1 (footnote omitted, emph. mine):

An lvalue is an expression (with an object type other than void) that potentially designates an object; if an lvalue does not designate an object when it is evaluated, the behavior is undefined. When an object is said to have a particular type, the type is specified by the lvalue used to designate the object. [...]

It's the lvalue which defines the type an object has for a particular access. In contrast, *p doesn't denote an object defined volatile. For example, ibid. 6.7.3 p6 (emph. mine) reads

[...] If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined.133)

133) This applies to those objects that behave as if they were defined with qualified types, even if they are never actually defined as objects in the program (such as an object at a memory-mapped input/output address).

If the intent was to allow the code shown to be optimized out, the quote in the question would probably read An object that has [is defined with a] volatile-qualified type may be modified [...]

"Definition" of an identifier*) is defined in 6.7, paragraph 5.

Ibid. 6.7.3 p7 (What constitutes an access to an object that has volatile-qualified type is implementation-defined.) gives some leeway to implementers, but to me, the intent seems to be that the side-effect of modifying the object denoted by n should be considered observable by a conforming implementation.

*) Afaik the standard doesn't define "an object defined with (some type)" anywhere so I read it as "an object, designated by an identifier declared with (some type) by a definition".

like image 25
mafso Avatar answered Oct 30 '22 00:10

mafso