Does Accessing a Declared Non-Volatile Object Through a Volatile Reference/Pointer Confer Volatile Rules Upon Said Accesses

Does accessing a declared non-volatile object through a volatile reference/pointer confer volatile rules upon said accesses?

Does accessing a declared non-volatile object through a volatile reference/pointer confer volatile rules upon said accesses?

volatile doesn't mean the same thing in C & C++. The C++ standard makes accesses through volatile lvalues observable. [1] It says that it intends this to be the same as C behaviour. And that is the behaviour described in the C Rationale. Nevertheless the C Standard says that accesses to a volatile-declared objects are observable. (Note that accessing a volatile-declared object via a non-volatile lvalue is undefined.)

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

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

Of course, what is done about observable behaviour is implementation-dependent.

There really isn't any ambiguity. It's just that people can't believe that the C Standard behaviour could be what it is, because that is not the historical usage pre-volatile (when address-literal lvalues were taken to be of volatile objects), as intended by the Rationale, as implemented by compilers before and since, as interpreted and described by the C++ Standard, as corrected in the DR. Similarly, the standard is clear in that it doesn't say that non-volatile accesses are observable, so they're not. (And "side effect" is a term used in defining the evaluation partial order.)

[1] Or at least hopefully it does now. From a comment from underscore_d:

For C++, see also P0612R0: NB comment CH 2: volatile, which was adopted this month to clean up some leftover talk about "volatile objects" in the C++ Standard, when really accesses through volatile glvalues are what it meant (as, presumably/hopefully, what C meant).

Volatile pointer points to non volatile data

int* volatile only means that the pointer itself is volatile. The pointed-at data is not volatile-qualified, meaning that the following scenario may cause optimizer bugs:

int a=10;
int* volatile ptr=&a;

int main()
{
for(;;)
{
printf("%d\n", *ptr);
}
}

void some_ISR (void)
{
a = something;
}

The compiler is careful not to assume that ptr does not point at the same address every lap in the loop, but apart from that it is free to assume: "aha, after reading ptr it is still pointing at a I see. It has not been updated and I know it is 10". In theory the compiler is free to generate machine code such as this pseudo:

val = *ptr

forever
{
ptr = (update it by reading from memory)
if ptr != previous
val =*ptr

print val
previous = ptr
}

Such optimizations may make sense if the compiler can keep the previous value stashed away in a CPU register etc. Suppose it can get rid of a lot of the printf overhead this way, that would be a major optimization.

So, no this does not give you protection against incorrect optimizations. To achieve that, use volatile int* instead. Or if the pointers themselves may also change, volatile int* volatile.


To communicate from interrupt context to task context I am passing addresses to circular queue (which is array of pointers) which will be accessed from task context.

That only makes sense if the pointer itself is changed to point elsewhere by the interrupt. Again, it doesn't mean that the pointed-at data will be updated. But as you write "The pointers in this queue are declared volatile.", you are good, assuming this means that the pointers are volatile* type and not type*volatile.

Unrelated to this whole issue, you also need a protection against non-atomic access of shared variables. volatile does not give that.

Does ARM procedure call standard allows volatile optimisation that contradict the C standard?

So I reached ARM toolchain's support group, and according to them the ARM PCS standard is an independent standard that is not bound to the C standard, such that a compiler can choose to comply to one, or both of them. In their own words:

In a way it's not really a contradiction

  • the APCS permits a compiler to respect or ignore local volatile
  • the C standard requires a compiler to respect local volatile

so a compiler that is compatible with both will respect local volatile.

Armclang has elected to follow the C standard which makes it compatible with both

So if a compiler choose to perform this non C-conforming optimization, it is still ARM PCS conforming implementation, but not a C-conforming compiler.

To conclude, a C-conforming compiler for ARM architecture which implements ARM PCS will never perform this optimization.

Why there shall be no more than one read access with volatile-qualified type within one sequence point?

The justification for the rule is:

(Required) The value of an expression
and its persistent side effects
shall be the same under all permitted
evaluation orders

If more than one volatile-qualified variable is read between sequence points, then it is unspecified which is read first. Reading a volatile variable is a side effect.

The solution is to explicitly order the reads:

void TST_MTR_Task(void)
{
if (tst_mtr_update == 1U)
{
tst_mtr_update = 0;

float32_t dutycycle = tst_mtr_dutycycle;
uint8_t direction = tst_mtr_direction;
MTR_SetDC(dutycycle, direction);
}
}

Is the compiler required to emit stores to raw addresses?

The C standard does not require an implementation to issue a store because C 2018 5.1.2.3 6 says:

The least requirements on a conforming implementation are:

— Accesses to volatile objects are evaluated strictly according to the rules of the abstract machine.

— At program termination, all data written into files shall be identical to the result that execution of the program according to the abstract semantics would have produced.

— The input and output dynamics of interactive devices shall take place as specified in 7.21.3. The intent of these requirements is that unbuffered or line-buffered output appear as soon as possible, to ensure that prompting messages actually appear prior to a program waiting for input.

This is the observable behavior of the program.

None of these includes assignment to a non-volatile lvalue.

Why is a point-to-volatile pointer, like volatile int * p, useful?

A pointer of the form

volatile int* p;

is a pointer to an int that the compiler will treat as volatile. This means that the compiler will assume that it is possible for the variable that p is pointing at to have changed even if there is nothing in the source code to suggest that this might occur. For example, if I set p to point to a regular integer, then every time I read or write *p , the compiler is aware that the value may have changed unexpectedly.

There is one more use case for a volatile int*: If you declare an int as volatile, then you should not point at it with a regular int*. For example, this is a bad idea:

volatile int myVolatileInt;
int* ptr = &myVolatileInt; // Bad idea!

The reason for this is that the C compiler no longer remembers that the variable pointed at by ptr is volatile, so it might cache the value of *ptr in a register incorrectly. In fact, in C++, the above code is an error. Instead, you should write:

volatile int myVolatileInt;
volatile int* ptr = &myVolatileInt; // Much better!

Now, the compiler remembers that ptr points at a volatile int, so it won't (or shouldn't!) try to optimize accesses through *ptr.

One last detail - the pointer you discussed is a pointer to a volatile int. You can also do this:

int* volatile ptr;

This says that the pointer itself is volatile, which means that the compiler shouldn't try to cache the pointer in memory or try to optimize the pointer value because the pointer itself might get reassigned by something else (hardware, etc.) You can combine these together if you'd like to get this beast:

volatile int* volatile ptr;

This says that both the pointer and the pointee could get changed unexpectedly.The compiler can't optimize the pointer itself, and it can't optimize what's being pointed at.

Hope this helps!



Related Topics



Leave a reply



Submit