I am designing a very minimal offset pointer class. It shows optimization level dependent output, suggesting undefined behaviour. But why, and how could it be fixed? See for example
#include <cstddef>
#include <iostream>
template<typename T>
class NullableOffsetPtr
{
public:
NullableOffsetPtr(T const* ptr) :
offset_{
ptr == nullptr ? std::ptrdiff_t{1} : reinterpret_cast<std::byte const*>(ptr) - reinterpret_cast<std::byte const*>(this)}
{
}
T* get()
{
return offset_ == 1 ? nullptr : reinterpret_cast<T*>(reinterpret_cast<std::byte*>(this) + offset_);
}
private:
std::ptrdiff_t offset_; // Using units of bytes, as alignments of *this and *ptr may not match.
};
class Sample
{
};
int main(int argc, char* argv[])
{
Sample sample;
NullableOffsetPtr<Sample> ptr{&sample};
std::cout << "(ptr.get() == &sample): " << (ptr.get() == &sample) << std::endl;
std::cout << "ptr.get(): " << ptr.get() << std::endl;
std::cout << "&sample : " << &sample << std::endl;
return 0;
}
With all optimization levels -O1 and above, the output of gcc compiled executables is
(ptr.get() == &sample): 0
ptr.get(): 0x7ffe503cd42f
&sample : 0x7ffe503cd42f
with the pointer comparison evaluating to false, despite individual values (varying with each run) are shown to be the same!
Of course, reinterpret_cast
immediately is ringing the UB bell, but from my understanding should be within the limits of Type aliasing in reinterpret_cast.
The following modifications would “fix” this behaviour, but are not satisfying solutions and don’t explain what’s wrong above:
- Disable optimization
- Use clang++
- Remove the nullptr handling
See also godbolt example