C/C++: Casting Away Volatile Considered Harmful

Is It Safe to Cast Away volatile?

Nothing. If you don't access the volatile memory, the semantics of volatile are unaffected. If you accessed volatile memory through a casted non-volatile pointer, the compiler might optimize the reference away. If the value had changed, you'd have the wrong value. For some value of wrong. ;-)

The delete doesn't access the volatile memory, it just frees it. Sort of an uncommon thing to do with volatile memory.

Can you cast away volatile on a member from a volatile function

Yes. To cast away the volatile-ness of an object, const_cast is used:

T & t = const_cast<T&>(volatile_t); 

This is the way. But whether you should use it in your code or not, I cannot say without looking at the code. In general, casting away the const-ness as well as volatile-ness, is a dangerous idea, and should be done only after very careful examination of all cases.

How to cast away the volatile-ness?

Use const_cast.

For example,

volatile sample *pvs = new sample();
sample *ps = const_cast<sample*>(pvs); //casting away the volatile-ness

That is, const_cast is used to cast away both const-ness as well as volatile-ness. Unfortunately, its name doesn't contain the term "volatile". Maybe, that is because the keyword const is more common in use than the keyword volatile. As one of the comment says, cv_cast would have been more appropriate name!

Cast away volatile in plain old C

It's a bit tricky but you can get the preprocessor to help you here, if you only want to convert from pointer types to pointer types.

#define cv_cast(TYPE,expr) (*((TYPE)(expr)) = *(expr),(TYPE)(expr))

Usage:

typedef struct {
float f;
} Foo;

typedef struct {
char b;
} Bar;

void somefunction(void) {
Foo f; Bar b;
Foo volatile* foov = &f;
Foo* foo = 0;
Bar* bar = &b;
foo = cv_cast(Foo*,foov); // legal (no error, no warning)
foov = cv_cast(Foo*,foo); // legal (no error, no warning)
foo = cv_cast(Foo*,bar); // illegal (an error)
}

The basic trick here is that we force an assignment from Bar to Foo in a cv_cast from Bar* to Foo*. This is done in the part after the ,. It will trigger an error if and only if you are casting incompatible pointer types.

However, note that cv_casting pointers that point to invalid locations (e.g. NULL) will cause segfaults.


Note: For reference (in the comments) this post used a helper function nullfn previously that is not necessary. You can look it up in the history if you're interested.

When can a volatile variable be optimized away completely?

Writes to volatile variables (even automatic ones) count as observable behaviour.

C11 (N1570) 5.1.2.3/6:

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.

The question is: does initialization (e, f) count as an "access"? As pointed out by Sander de Dycker, 6.7.3 says:

What constitutes an access to an object that has volatile-qualified type is implementation-defined.

which means it's up to the compiler whether or not e and f can be optimized away - but this must be documented!

C26492 Don't use `const_cast` to cast away const or volatile (type.3)

The cleanest way, I believe, would be to use GetBuffer.

At some point you will find a function that really needs non-const string :)

Casting volatile variable in c

It really depends on what my_function does with its argument.

Remember that volatile prevents certain optimizations - predominantly it forces the variable to be re-read every time it is referenced. Thus this code

volatile int a;
int b;
// ...
b = a + 1;
b = a + 2;

will read a for each statement and, as a may have changed values between them, give the correct result.

When you pass a volatile into a function as a parameter, you only get one read of the variable. This may then be used multiple times within the function (effectively losing the volatile nature).

Remember that C is pass-by-value. When you invoke the function as

my_function((int)b); // b is declared volatile

The compiler generates code to read b once in the calling code, and push the value it read onto the stack (usually), then invoke my_function. This copy is then referenced within my_function as example, and no matter how often you reference example you will always get the same value (even if the original b variable has since changed many times).

That might be exactly what you want - take a snapshot of the variable and do several computations on its value.

If it's not what you want, you need to consider passing in a pointer with the appropriate volatile qualifications.

char my_function( volatile int *example);

And call it thus:

my_function(&a);
my_function(&b);

Then reference *example inside my_function.

Should I qualify pointer parameters with volatile if they may be changed during the execution of a function?

volatile was intended for things like memory-mapped device registers, where the pointed-to value could "magically" change "behind the compiler's back" due to the nature of the hardware involved. Assuming you're not writing code that deals with special hardware that might "spontaneously" change the value that bar points to, then you needn't (and shouldn't) use the volatile keyword. Simply omitting the const keyword is sufficient to let the compiler (and any programmer that might call the function) know that the pointed-to value is subject to change.

Note that if you are intending to set *bar from another thread, then the volatile keyword isn't good enough; even if you tag the pointer volatile, the compiler still won't guarantee correct handling. For that use-case to work correctly, you need to either synchronize all reads and writes to *bar with a mutex, or alternatively use a std::atomic<int> instead of a plain int.



Related Topics



Leave a reply



Submit