Reputation: 175
So I recently started a bit of a crusade to clear out some warnings that I get when I enable all the compiler warnings in GCC (-Wall -Wextra -Wpedantic) but I'm getting a rather confusing warning about uninitialised values. Here is a (slightly simplified) view of what is happening:
#include <cstdint>
#include <iostream>
constexpr uint64_t MASK = 0xFFFFFFFFFFFFFFFF;
double do_something(const double& rhs) {
double tmp(rhs);
reinterpret_cast<uint64_t&>(tmp) &= MASK;
return tmp;
}
int main() {
std::cout << do_something(3.14159);
}
Compiled with
g++ -std=c++17 -O3 -Wall -Wextra -Wpedantic
Basically I apply a bit mask MASK
to the value of the double and then perform some other operation on the original data and the masked data. The other operation involves extra state that isn't relevant here, and the bit mask is stored elsewhere but is properly defined. Strangely, I got the following compiler warning
<source>: In function 'double do_something(const double&)':
<source>:8:32: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
8 | reinterpret_cast<uint64_t&>(tmp) &= MASK;
| ^~~
<source>:8:37: warning: 'tmp' is used uninitialized [-Wuninitialized]
8 | reinterpret_cast<uint64_t&>(tmp) &= MASK;
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
<source>:7:11: note: 'tmp' declared here
7 | double tmp(rhs);
although the context of the warning points to the &= operation on the reinterpreted data. I presume this is because the compiler can't see through the reinterpret cast to see the valid data initialised on the line above held in the (reinterpreted) reference to uint64_t. Is this correct? And if not, can anyone enlighten me?
Disclaimer: I know that some of you will be put off by the reinterpret cast, but this is crucial to the way this particular piece of the code functions and (warnings aside) it is a "safe" operation in this context.
Upvotes: 0
Views: 235
Reputation: 141020
Use memcpy
. Do not type-punne pointers.
#include <cstring>
#include <cstdint>
#include <iostream>
constexpr uint64_t MASK = 0xFFFFFFFFFFFFFFFF;
double do_something2(const double& rhs) {
double tmp(rhs);
uint64_t tmp64 = 0;
static_assert(sizeof(uint64_t) >= sizeof(double));
memcpy(&tmp64, &tmp, sizeof(double));
tmp64 &= MASK;
memcpy(&tmp, &tmp64, sizeof(double));
return tmp;
}
int main() {
std::cout << do_something2(3.14159);
}
You can also use unsigned char
to inspect anything:
double do_something3(const double& rhs) {
double tmp(rhs);
unsigned char *tmpu = reinterpret_cast<unsigned char*>(&tmp);
uint64_t mask = MASK; // TODO: handle endianess
for(int i = 0; i < sizeof(double) / CHAR_BIT; ++i) {
const unsigned char tmpmask = mask;
mask >>= CHAR_BIT;
*tmpu &= tmpmask;
}
return tmp;
}
Upvotes: 1