这些指针转换中的未定义行为是什么? [重复]

问题描述 投票:0回答:2

我正在设计一个非常小的偏移指针类。它显示优化级别相关的输出,表明未定义的行为。但为什么会这样,又该如何解决呢?参见示例

#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;
}

使用所有优化级别 -O1 及以上,gcc 编译的可执行文件的输出为

(ptr.get() == &sample): 0
ptr.get(): 0x7ffe503cd42f
&sample  : 0x7ffe503cd42f

尽管各个值(每次运行都不同)显示为相同,但指针比较的结果为 false!

当然,

reinterpret_cast
立即敲响了UB的铃声,但根据我的理解,应该在reinterpret_cast中的类型别名的范围内。

以下修改将“修复”此行为,但不是令人满意的解决方案,并且不能解释上面的问题:

  • 禁用优化
  • 使用 clang++

另请参阅 godbolt 示例

c++ undefined-behavior
2个回答
0
投票

如果两个对象都是同一顶级对象的子对象(可能是间接的),则只能使用指针算术从另一个对象访问一个对象。 (例如,如果您将两个对象设置为同一类实例的非静态数据成员,GCC 就会开始运行。)

相关措辞如下:

  • [expr.add]/4.2
    表示
    +
    只能用于在同一数组的元素之间移动指针。同样,
    -
    只能减去同一数组元素之间的指针。
  • [basic.types.general]/4
    表示您可以将对象视为
    unsigned char
    的数组。
  • [basic.lval]/11.3
    祝福
    char
    unsigned char
    std::byte
    ,允许通过指针/引用访问任何对象,忽略严格的别名。

大家普遍认为,你不能在

uintptr_t
中进行指针运算来解决
[expr.add]/4.2
,但我现在找不到相关的措辞。

关于生命周期和别名的措辞总体来说是一团糟,如果你挖掘得太深,很多事情在技术上都是 UB (或暗示如此),而在实践中却没有得到执行。例如,有一个可以通过指针访问字节的概念。它用在 std::launder

 的描述中,据我所知,没有在其他地方使用过(该标准实际上并没有在任何地方说访问“无法访问”的字节实际上是 UB)。


0
投票

Sample

 是一个空类,由于这在您的代码中没有明显的影响,因此它可以与 
NullableOffsetPtr
 共享一个地址。
因此,打印时 
&sample
ptr.get()
 可以提供相同的输出。

但是,

NullableOffsetPtr

的构造函数中的指针减法是未定义的行为:

当两个指针表达式 P 和 Q 相减时,结果的类型是实现定义的有符号整型;该类型应与标头中定义为 std::ptrdiff_t 的类型相同([support.types.layout])。

    如果 P 和 Q 的计算结果均为空指针值,则结果为 0。
  • 否则,如果 P 和 Q 分别指向同一数组对象 x 的数组元素 i 和 j,则表达式 P - Q 的值为 i−j。
  • 否则,行为未定义。
  • [expr.add] p5
© www.soinside.com 2019 - 2024. All rights reserved.