我有一个类offset_ptr
,它的工作原理类似于指针,但是存储它指向的内存地址作为其自身地址this
的偏移量。这是一个已删除所有内容的版本,不需要演示该问题:
template <typename T>
struct offset_ptr {
using offset_t = int64_t;
static constexpr auto const NULLPTR_OFFSET =
std::numeric_limits<offset_t>::max();
offset_ptr(T const* p)
: offset_{p == nullptr ? NULLPTR_OFFSET
: static_cast<offset_t>(
reinterpret_cast<uint8_t const*>(p) -
reinterpret_cast<uint8_t const*>(this))} {}
T* get() {
return
offset_ == NULLPTR_OFFSET
? nullptr
: reinterpret_cast<T*>(reinterpret_cast<uint8_t*>(this) + offset_);
}
offset_t offset_;
};
此代码不适用于GCC -O2
和-O3
:
int* get() {
offset_ptr<int> ptr = static_cast<int*>(malloc(sizeof(int)));
auto p = ptr.get();
*p = 110; // WOW - please do not optimize me away :-(
return p;
}
(为了简化起见,故意省略了内存管理和错误检查!)
这在生成的程序集中也可见: https://godbolt.org/z/PfZEJM
任务丢失了。
如上方的Godbolt编译器资源管理器链接所示,它在以下情况下有效
offset_ptr
位于堆上,而不是堆栈上offset_ptr
它适用于:
-O0
和-O1
(但对于-O2
和-O3
来说否)GCC和Clang地址以及UB清理程序版本在执行时不表示任何问题(除了泄漏的内存)。
有人可以指出C ++标准文档中的一段,指出该代码中包含UB(这可能是GCC积极优化分配的原因)吗?还是GCC中的错误?
编辑:
删除nullptr
中的offset_ptr
检查会有所帮助(https://godbolt.org/z/5HjcLY)。但是我需要那些空检查。
答案 0 :(得分:2)
当两个指针表达式P和Q相减时,结果的类型为实现定义的有符号整数类型; [...]
- 如果P和Q都得出空指针值,则结果为0。
- 否则,如果P和Q分别指向同一数组对象
x[i]
的元素x[j]
和x
,则表达式P-Q的值为i-j
。- 否则,该行为是不确定的。
成员初始值设定项列表中的减法落在第三点,因此您拥有UB。
如果您删除了nullptr
检查,则它“有效”,因为gcc无法证明第一个条件没有发生。
答案 1 :(得分:1)
如果您使用reinterpret_cast
到uintptr_t
而不是uint8_t *
,则可以使此工作有效。通过这种方式,您可以将UB换成实现定义的行为。