Reputation: 6902
I have the following declarations in my code:
h file:
typedef struct {
bool qualified : 1;
bool running : 1;
} calibration_state_t;
calibration_state_t get_calibration_state();
cpp file:
volatile calibration_state_t calibration_state = {false ,false};
The function
calibration_state_t get_calibration_state() {
return *(calibration_state_t *)&calibration_state;
}
compiles. However if I replace the return statement with
return (calibration_state_t)calibration_state;
It fails with
dcf77.cpp: In function ‘DCF77_Frequency_Control::calibration_state_t DCF77_Frequency_Control::get_calibration_state()’:
dcf77.cpp:2923:37: error: no matching function for call to ‘DCF77_Frequency_Control::calibration_state_t::calibration_state_t(volatile DCF77_Frequency_Control::calibration_state_t&)’
dcf77.h:204:7: note: candidates are: DCF77_Frequency_Control::calibration_state_t::calibration_state_t()
dcf77.h:204:7: note: DCF77_Frequency_Control::calibration_state_t::calibration_state_t(const DCF77_Frequency_Control::calibration_state_t&)
The compiler is avr-gcc but I suspect this does not matter. Why does the compiler fail to compile the type cast? How would I get to the desired return value in a clean way?
Upvotes: 0
Views: 335
Reputation: 137425
Your code that uses a cast has undefined behavior (§7.1.6.1 [dcl.type.cv]/p6):
If an attempt is made to refer to an object defined with a volatile-qualified type through the use of a glvalue with a non-volatile-qualified type, the program behavior is undefined.
*(calibration_state_t *)&calibration_state
is a glvalue of type calibration_state_t
, a non-volatile-qualified type, and is being used to refer to calibration_state
, an object defined with a volatile-qualified type. Undefined behavior results.
Relying on undefined behavior to get the semantics you want is incredibly dangerous. While the compiler is unlikely to actually conjure nasal demons or blow your legs off (though it is allowed to), an optimizing compiler may legally assume from the undefined behavior that get_calibration_state
will never be called, and that any code path containing it is unreachable, and generate code accordingly. This kind of optimizations depending on undefined behavior can and do happen.
In reference binding, volatile
is like const
- you can't bind a const
object to a non-const
reference, and you can't bind a volatile
object to a non-volatile
reference. Give your class a copy constructor that takes a const volatile &
.
Upvotes: 3
Reputation: 48487
For me it looks that:
return *(calibration_state_t *)&calibration_state;
explicitly removes the volatile
specifier, so the implicitly defined copy constructor:
calibration_state_t(const calibration_state_t&)
is executed.
When you don't cast the calibration_state
instance first, then compiler can not call the copy constructor (volatile
remains).
Upvotes: 1
Reputation: 182829
What is the desired behavior exactly? Must ordering be preserved, for example? If something else sets qualified
and then running
, is it okay to get the old value of qualified
but the new value of running
?
Because the structure is volatile, operations on it are part of the visible behavior of the program. That is, this:
calibration_state_t get_calibration_state()
{
calibration_state_t ret;
ret.qualified = calibration_state.qualified;
ret.running = calibration_state.running;
return ret;
}
Is not the same as:
calibration_state_t get_calibration_state()
{
calibration_state_t ret;
ret.running = calibration_state.running;
ret.qualified = calibration_state.qualified;
return ret;
}
So, you have to code what you want. How can the compiler know what behavior you want? You got some behavior by lying to the compiler, but I doubt it's the behavior you want.
Upvotes: 2