Today I've encountered some code that roughly looks like the following snippet. Both valgrind
and UndefinedBehaviorSanitizer
detected reads of uninitialized data.
template <typename T>
void foo(const T& x)
{
static_assert(std::is_pod_v<T> && sizeof(T) > 1);
auto p = reinterpret_cast<const char*>(&x);
std::size_t i = 1;
for(; i < sizeof(T); ++i)
{
if(p[i] != p[0]) { break; }
}
// ...
}
The aforementioned tools complained about the p[i] != p[0]
comparison when an
object containing padding bytes was passed to foo
. Example:
struct obj { char c; int* i; };
foo(obj{'b', nullptr});
Is it undefined behavior to read padding bytes from a POD type and compare them to something else? I couldn't find a definitive answer neither in the Standard nor on StackOverflow.
The behaviour of your program is implementation defined on two counts:
1) Prior to C++14: Due to the possibility of a 1's complement or signed magnitude signed
type for your char
, you might return a surprising result due to comparing +0 and -0.
The truly watertight way would be to use a const unsigned char*
pointer. This obviates any concerns with the now abolished (from C++14) 1's complement or signed magnitude char
.
Since (i) you own the memory, (ii) you are taking a pointer to x
, and (iii) an unsigned char
cannot contain a trap representation, (iv) char
, unsigned char
, and signed char
being exempted from the strict aliasing rules, the behaviour on using const unsigned char*
to read uninitialised memory is perfectly well defined.
2) But since you don't know what is contained in that uninitialised memory, the behaviour on reading it is unspecified and that means the program behaviour is implementation defined since the char types cannot contain trap representations.