【发布时间】:2022-01-05 12:12:50
【问题描述】:
我一直试图弄清楚这是否是一个优化错误,因为它似乎只影响堆栈变量,我想知道是否做出了一些不正确的假设。我有这种类型可以在相对偏移量之间进行转换,并且在使用reinterpret_cast 时工作正常,但现在我正在转向static_cast,它开始在优化构建中引起问题。出于安全认证的原因,我需要离开reinterpret_cast,所以我没有选择保持原样。
#include <iostream>
template <typename T>
class Ptr
{
public:
Ptr(const T* ptr = nullptr) : m_offset(GetOffset(ptr)) {}
T& operator*() const noexcept { return *GetPtr(); }
T* get() const noexcept { return GetPtr(); }
bool operator==(const T *ptr) const {
// comment this back in and it stops failing
//std::cout << "{op==" << get() << " == " << ptr << "}";
return get() == ptr;
}
private:
std::ptrdiff_t m_offset = 0;
inline T* GetPtr() const
{
auto offset = m_offset;
auto const_void_address = static_cast<const void*>(&m_offset);
auto const_char_address = static_cast<const char*>(const_void_address);
auto offset_address = const_cast<char*>(const_char_address);
auto final_address = static_cast<void*>(offset_address - offset);
return static_cast<T*>(final_address);
}
std::ptrdiff_t GetOffset(const void* ptr) const
{
auto void_address = static_cast<const void*>(&m_offset);
auto offset_address = static_cast<const char*>(void_address);
auto ptr_address = static_cast<const char*>(ptr);
return offset_address - ptr_address;
}
};
std::ostream& operator<<(std::ostream &stream, const Ptr<int> &rp) {
stream << rp.get();
return stream;
}
int main() {
int data = 123;
Ptr<int> rp(&data);
std::cout << "data " << data << " @ " << &data << std::endl;
std::cout << "rp " << *rp << " get " << rp.get() << std::endl;
std::cout << (rp == &data) << std::endl;
std::cout << "(rp.get() == &data) = " << (rp.get() == &data) << std::endl;
std::cout << "(rp == &data) = " << (rp == &data) << std::endl;
return 0;
}
启用优化后,我得到如下输出:
data 123 @ 0x7ffe79544a34
rp 123 get 0x7ffe79544a34
0
(rp.get() == &data) = 0
(rp == &data) = 0
其中包括一些与自身明显不一致的输出。
我已经在 GCC 8,9 和 11.2 上对此进行了测试。
- 只要我回到
-O0,就可以了。 - 如果我取消注释
operator==中的std::cout,就可以了。 - 如果我回到
reinterpret_cast(return reinterpret_cast<T*>(reinterpret_cast<std::ptrdiff_t>(&m_offset) - offset);) 就好了。 - 如果我将数据分配为指针并从中初始化
rp,也可以。 - 它似乎在 clang 下表现得如我所料(8 到 13 看起来不错)
这感觉就像我不明白 UB 来自哪里,或者这里有一个编译器优化错误。
编辑/更新:
在查看了更多详细信息后,我认为唯一的解决方案是以半安全的方式进行类型双关语,所以我尝试了这个解决方案,它似乎有效。 (看来,我现在做的是 C++20 的一部分,叫做bit_cast,所以也许这是有效的?)
inline T* GetPtr() const
{
auto offset = m_offset;
intptr_t realAddress;
auto address_of_m_offset = &m_offset;
std::memcpy(&realAddress, &address_of_m_offset, sizeof( realAddress));
realAddress -= m_offset;
T *outValue;
std::memcpy(&outValue, &realAddress, sizeof( outValue));
return outValue;
}
std::ptrdiff_t GetOffset(const void* ptr) const
{
auto address_of_m_offset = &m_offset;
intptr_t myAddress;
std::memcpy(&myAddress, &address_of_m_offset, sizeof(myAddress));
intptr_t realAddress;
std::memcpy(&realAddress, &ptr, sizeof(realAddress));
return static_cast<ptrdiff_t>(myAddress - realAddress);
}
这似乎不再导致 GCC 出现问题。我听说 std::memcpy 旨在用于相同类型的对象,否则我们会使用 reinterpret_cast,所以这对我来说很有意义。
【问题讨论】:
-
只是出于好奇:为什么要以这种方式存储指针?另外,只是猜测,可能是有符号值的溢出,这将是 UB?最后,我会添加“language-lawyer”标签,也许会替换“*-cast”标签。
-
更简单的方法是比较优化和非优化“组装”的输出:)
-
同一块物理内存在不同的虚拟地址空间同时使用时,不能使用绝对指针。这就是为什么我需要一个相对指针。这是共享内存问题的解决方案。
标签: c++ optimization language-lawyer undefined-behavior