Cast volatile array to non volatile array

  1. If the array is changes in interrupt you need to provide a mechanism to acces and modify it atomic way. If you don't any RW or RMW operation may be unsuccessful and the data inconsistent.

  2. You access volatile data make the f=unction parameters volatile as well. storeArray(volatile unsigned char *) and no cast will be needed. The cast only removes the warning. Even you pass non-volatile data to it, it will work as well.


You have found the correct section of the standard, this code leads to undefined behavior.

A function writing something "to hardware" should probably have a volatile-qualifier parameter, depending on what "hardware" is. If it is a memory-mapped register, a DMA buffer or non-volatile memory, then the parameter should definitely have been volatile unsigned char* (or optionally, volatile uint8_t* which also is to be regarded as a character type).


Details: C allows us to iterate through any chunk of data using a character pointer, C17 6.3.2.3/7:

When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object.

The part you quote about accessing a "lvalue" refers to accesing data through a different pointer type than what's actually stored in that location. Plainly: no matter how much you cast various pointers pointing at it, the actual data retains its original type.

Accessing the data through the wrong pointer type is normally not even allowed, but again character access is a special exception to the "strict aliasing rule", C17 6.5/7:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
...
- a character type.

So you can access any kind of data through a character pointer, but if that pointer is not volatile-qualified, you invoke undefined behavior as per the part you quoted, C17 6.7.3/5.

In practice, using a non-volatile pointer type could cause the compiler to optimize the access in unexpected ways. So this isn't just theoretical "language-lawyering", you could in practice get very strange code generated with optimizations enabled. Lots of very hard to find bugs in embedded systems originate from such a missing volatile.


Regarding your follow-up question, the cast and the buffer + 3 changes nothing: you are still dealing with a character pointer without volatile qualifier - same type. The actual data remains of type volatile unsigned char, so you can't access it from the function through a unsigned char*.


Yes, the standard quote you've posted covers precisely what you're trying to do. By doing the cast, you're pretending the objects in the array are unsigned char when they're actually volatile unsigned char, so inside the function, you're referring to volatile object through an lvalue without a volatile qualifier. Undefined Behaviour.

If you cannot change the function storeArray, you will have to copy the data from the volatile array to a non-volatile one before passing it to the function.

Regarding the second question: the pointer arithmetic is fine, it will simply convert buffer to an unsigned char* and then add 3 to the resulting pointer, pointing to buffer[3] (but with the wrong qualification).


As you found, you're relying on "undefined behavior". However, depending among other things on the separation of compilation units (and things like "whole-program-optimization" (WPO)) it will probably work. In most cases, the compiler (at least gcc) is not "smart enough" to optimize array accesses across functions in different compilation units. That said, the clean, safe and portable way would be to copy the array, making the dependency of the non-volatile array's values on the volatile ones visible to the compiler.