将char数组强制转换为对象指针-这是UB吗? [英] Casting a char array to an object pointer - is this UB?

查看:133
本文介绍了将char数组强制转换为对象指针-这是UB吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最近我看到一个类似的类,该类用于按需"构造对象,而由于各种原因而不必使用动态内存分配.

I recently saw a class like this that was used to construct objects "on-demand" without having to use dynamic memory allocation for various reasons.

#include <cassert>

template<typename T>
class StaticObject
{
public:
    StaticObject() : constructed_(false)
    {
    }

    ~StaticObject()
    {
        if (constructed_)
            ((T*)object_)->~T();
    }

    void construct()
    {
        assert(!constructed_);

        new ((T*)object_) T;
        constructed_ = true;
    }

    T& operator*()
    {
        assert(constructed_);

        return *((T*)object_);
    }

    const T& operator*() const
    {
        assert(constructed_);

        return *((T*)object_);
    }

private:
    bool constructed_;
    alignas(alignof(T)) char object_[sizeof(T)];
};

这是代码,即将正确对齐的char数组强制转换为对象指针,是C ++ 14标准认为未定义的行为吗?还是完全可以?

Is this code, namely the casting of a properly aligned char array to an object pointer, considered undefined behavior by the C++14 standard or is it completely fine?

推荐答案

该程序在技术上具有未定义的行为,尽管它可能适用于大多数实现.问题是,即使char*指针表示用于以下操作的第一个字节的地址,也不能保证从char*T*的强制转换会导致有效的指针指向由placement new创建的T对象. T对象的存储空间.

This program technically has undefined behavior, although it's likely to work on most implementations. The issue is that a cast from char* to T* is not guaranteed to result in a valid pointer to the T object created by placement new, even though the char* pointer represents the address of the first byte used for storage for the T object.

[basic.compound]/3 :

与布局兼容的类型的指针必须具有相同的值表示和对齐要求([basic.align]).

Pointers to layout-compatible types shall have the same value representation and alignment requirements ([basic.align]).

通常,Tcharalignas(T) char[sizeof(T)]在布局上不兼容,因此不要求指针T*与指针char*void*具有相同的值表示形式

In general, T will not be layout-compatible with char or with alignas(T) char[sizeof(T)], so there's no requirement that a pointer T* has the same value representation as a pointer char* or void*.

[basic.compound]/4 :

如果满足以下条件,则两个对象 a b 是指针可互换的:

Two objects a and b are pointer-interconvertible if:

  • 它们是同一对象,或者

  • they are the same object, or

一个是联合对象,另一个是该对象的非静态数据成员([class.union]),或者

one is a union object and the other is a non-static data member of that object ([class.union]), or

一个是标准布局类对象,另一个是该对象的第一个非静态数据成员,或者,如果该对象没有非静态数据成员,则是该对象的任何基类子对象( [class.mem]),或

one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, any base class subobject of that object ([class.mem]), or

存在一个对象 c ,这样 a c 是指针可互换的,而 c b 是指针可互换的.

there exists an object c such that a and c are pointer-interconvertible, and c and b are pointer-interconvertible.

如果两个对象是指针可互换的,则它们具有相同的地址,并且可以通过reinterpret_cast从指向另一个的指针获得指向一个的指针. [注意:数组对象及其第一个元素即使指针具有相同的地址,也不能指针可相互转换. — 尾注]

If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_cast. [ Note: An array object and its first element are not pointer-interconvertible, even though they have the same address. — end note ]

[另外: DR 2287 已更改C ++ 17发布后的第二个项目符号中,从标准布局联合"到联合".但这并不影响该程序.]

[Aside: DR 2287 changed "standard-layout union" to "union" in the second bullet after the publication of C++17. But that doesn't affect this program.]

由布局new创建的T对象不能与object_object_[0]进行指针互换.注释暗示这可能是演员的问题...

The T object created by the placement new is not pointer-interconvertible with object_ or with object_[0]. And the note hints that this might be a problem for casts...

对于C样式转换((T*)object_),我们需要查看 [expr .cast]/4 :

For the C-style cast ((T*)object_), we need to see [expr.cast]/4:

  • a const_cast

a static_cast

a static_cast,后跟const_cast

a reinterpret_cast

a reinterpret_cast后跟const_cast

可以使用显式类型转换的强制转换符号来执行....

can be performed using the cast notation of explicit type conversion....

如果可以采用以上列出的一种以上的方式来解释转换,那么即使该解释产生的转换格式不正确,也要使用列表中最先出现的解释.

If a conversion can be interpreted in more than one of the ways listed above, the interpretation that appears first in the list is used, even if a cast resulting from that interpretation is ill-formed.

除非Tchar或具有cv资格的char,否则它实际上是reinterpret_cast,因此接下来我们看一下

Unless T is char or cv-qualified char, this will effectively be a reinterpret_cast, so next we look at [expr.reinterpret.cast]/7:

可以将对象指针显式转换为其他类型的对象指针.将对象指针类型的prvalue v转换为对象指针类型指向 cv T的指针"时,结果为static_­cast< cv T*>(static_­cast< cv void*>(v)).

An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue v of object pointer type is converted to the object pointer type "pointer to cv T", the result is static_­cast<cvT*>(static_­cast<cvvoid*>(v)).

所以首先我们有一个static_castchar*void*,它执行

So first we have a static_cast from char* to void*, which does the standard conversion described in [conv.ptr]/2:

" cv T的指针"类型的prvalue,其中T是对象类型,可以转换为" cv void".指针值([basic.compound])在此转换后保持不变.

A prvalue of type "pointer to cv T", where T is an object type, can be converted to a prvalue of type "pointer to cv void". The pointer value ([basic.compound]) is unchanged by this conversion.

其后是从void*T*static_cast,如 [expr.static.cast]/13 :

可以将指向 cv1 void的指针"的prvalue转换为指向 cv2 T的指针"的prvalue,其中T是对象类型,并且 cv2 cv1 具有相同的cv限定,或具有更高的cv限定.如果原始指针值表示内存中字节的地址A,并且A不满足T的对齐要求,则未指定结果指针值.否则,如果原始指针值指向对象 a ,并且存在类型为T的对象 b (忽略cv限定),则该指针可与 a ,结果是指向 b 的指针.否则,转换后指针值将保持不变.

A prvalue of type "pointer to cv1 void" can be converted to a prvalue of type "pointer to cv2 T", where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. If the original pointer value represents the address A of a byte in memory and A does not satisfy the alignment requirement of T, then the resulting pointer value is unspecified. Otherwise, if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible with a, the result is a pointer to b. Otherwise, the pointer value is unchanged by the conversion.

如前所述,类型T的对象不能与object_[0]进行指针互换,因此该语句不适用,并且不能保证结果T*指向T对象!我们只剩下说指针值未更改"的句子,但是如果char*T*指针的值表示形式太不同,这可能不是我们想要的结果.

As already noted, the object of type T is not pointer-interconvertible with object_[0], so that sentence does not apply, and there's no guarantee that the result T* points at the T object! We're left with the sentence saying "the pointer value is unchanged", but this might not be the result we want if the value representations for char* and T* pointers are too different.

可以使用union实现该类的标准兼容版本:

A Standard-compliant version of this class could be implemented using a union:

template<typename T>
class StaticObject
{
public:
    StaticObject() : constructed_(false), dummy_(0) {}
    ~StaticObject()
    {
        if (constructed_)
            object_.~T();
    }
    StaticObject(const StaticObject&) = delete; // or implement
    StaticObject& operator=(const StaticObject&) = delete; // or implement

    void construct()
    {
        assert(!constructed_);

        new(&object_) T;
        constructed_ = true;
    }

    T& operator*()
    {
        assert(constructed_);

        return object_;
    }

    const T& operator*() const
    {
        assert(constructed_);

        return object_;
    }

private:
    bool constructed_;
    union {
        unsigned char dummy_;
        T object_;
    }
};

甚至更好,因为此类实际上是在尝试实现optional,因此只需使用 std::optional (如果有)或

Or even better, since this class is essentially attempting to implement an optional, just use std::optional if you have it or boost::optional if you don't.

这篇关于将char数组强制转换为对象指针-这是UB吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆