通过将指针强制转换为另一种类型的未定义行为来访问其生命周期内的对象吗?

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

所以我正在阅读介绍std::get_lifetime_as

P2590R2
,其中一个部分让我质疑一些我以前认为是定义行为的东西,所以我希望有人能够澄清

如果我有这样的代码:

struct WString_header
{
    WString_header(std::wstring_view str)
        : m_size(str.size())
    {
        // this shouldn't be undefined behaviour so long as
        // the storage we're being allocated in (i.e. an array of bytes)
        // can actually fit this array?
        wchar_t* chars = new (this + 1) wchar_t[str.size()];

        std::copy(str.begin(), str.end(), chars);
    }

    size_t size() const
    {
        return m_size;
    }
    const wchar_t* chars() const
    {
         //undefined behaviour?
         return reinterpret_cast<const wchar_t*>(this + 1);
    }
    
protected:
    size_t m_size;
};

我之前认为通过使用 reinterpret_cast 检索字符不会是未定义的行为,因为我们之前在构造函数中的该地址分配了该类型的对象,但是在链接的论文中,在他们的部分解释了

start_lifetime_as
launder
,他们说:

另一方面,std::launder 从不创建新对象,但是
只能用于获取指向已存在于给定内存位置的对象的指针,
它的生命周期已经通过其他方式开始了。

事实上

std::launder
被引用这个措辞让我觉得我可能有一个不正确的对象生命周期模型。

正如我之前所理解的那样,一旦您在内存中的给定位置分配了一个对象,通过 reinterpret_cast 访问它始终是定义的行为,无论以何种方式或在何处获得被转换的指针,因为该内存地址确实包含该类型的实例(显然要注意像数组这样的问题,其中简单地计算数组之外的地址而不是过去的指针是未定义的行为)。

但是,鉴于论文中对

std::launder
的描述,这让我认为应该修改该模型,即任何取消引用通过 reinterpret_cast 获得的指针的尝试都应该首先通过 launder 传递,以防止未定义的行为(除非类型是char 或类似的类型,因为这些类型具有独特的别名规则),但是这又提出了一个问题,即如果唯一可以对结果做的事情是指针算术和与指针的比较,为什么 reinterpret_cast 对转换指针有用新型的。

到目前为止,我只是幸运地对我的代码进行了编译器优化,还是我对论文中的措辞感到困惑?

c++ lifetime
1个回答
0
投票

应该首先通过流槽以防止未定义的行为

的确如此。通常,在实际取消引用结果指针时需要这样做(一些例外情况适用于 pointer-interconvertible objects)。

他唯一可以用结果做的事情就是指针运算

那也不行。如果表达式的类型与指向对象的实际类型不匹配(或者与它similar)因为你使用了

reinterpret_cast
,那么指针算法也是UB.

只有当你有一个指向对象的指针并将其转换为指针类型

reinterpret_cast

时,指针上的
X*
才直接用于访问对象,这样在其生命周期中就有一个
X
类型的对象是pointer -可与原始对象互换。就是这种情况,例如对于联合对象及其活动成员子对象。

对于其他情况,您需要在

addition
中添加std::launder以确保同一内存地址处的
X
类型的对象在其生命周期内(否则
std::launder
也有UB)。

reinterpret_cast
仅在这些情况下更改表达式类型,这仍然有用,因为它允许您传递错误类型的指针,只要指针永远不会被取消引用。


但是,

std::launder
并不能在您的情况下拯救您:如果
std::wstring_view
对象不是
std::wstring_view
对象数组的一部分,那么
this + 1
根本不是指向对象的指针。它是一个指向对象的指针。

std::launder
有一个先决条件,即使用它不会使可访问的字节数超过通过原始指针可访问的字节数。这里“可达”基本上是指可以通过指针算法获得引用的字节和指针互转换对象之间的
reinterpret_cast

这里不是这种情况。通过

this
this + 1
可到达的唯一字节是
WString_header
对象的字节。你不能从
this
访问它后面的任何字节,无论你是否在它后面保留更多空间,也不管你是否使用
std::launder
。规则是专门编写的,因此这是不可能的,大概是为了允许对对象结构进行优化,尽管我不知道任何编译器都使用它。

(出于同样的原因,placement-new 本身也应该有未定义的行为,但目前似乎规范在这方面不一致,请参阅我的问题here。)

© www.soinside.com 2019 - 2024. All rights reserved.