【问题标题】:Does encapsulated char array used as object breaks strict aliasing rule用作对象的封装字符数组是否违反了严格的别名规则
【发布时间】:2012-06-09 23:29:07
【问题描述】:

以下类是否违反了严格的别名规则:

template<typename T>
class store {
    char m_data[sizeof(T)];
    bool m_init;
public:
    store() : m_init(false) {}
    store(const T &t) : init(true) {
        new(m_data) T(t);
    }
    ~store() {
        if(m_init) {
            get()->~T();
        }
    }
    store &operator=(const store &s) {
        if(m_init) {
            get()->~T();
        }
        if(s.m_init) {
            new(m_data) T(*s.get());
        }
        m_init = s.m_init;
    }
    T *get() {
        if (m_init) {
            return reinterpret_cast<T *>(m_data);
        } else {
            return NULL;
        }
    }
}

我对标准的解读是它不正确,但我不确定(我的用法是拥有一组对象T + 这些对象的一些元数据,但无需手动分配即可控制对象构造/解构memory) 作为分配的对象,在标准中用作放置 new 的示例。

【问题讨论】:

    标签: c++ placement-new reinterpret-cast strict-aliasing explicit-destructor-call


    【解决方案1】:

    标准包含此注释:

    [ 注意:一个典型的实现会将aligned_storage定义为:

    template <std::size_t Len, std::size_t Alignment>
    struct aligned_storage {
      typedef struct {
        alignas(Alignment) unsigned char __data[Len];
      } type;
    };
    

    ——尾注]

                                                                                 ——指针修改 [meta.trans.ptr] 20.9.7.5/1

    aligned_storage 部分定义为:

    成员 typedef type 应该是一个 POD 类型,适合用作任何对象的未初始化存储,其大小最多为 Len 并且其对齐方式是 对齐

    该标准所涵盖的唯一限制可以构造对象的地址的属性是对齐。一个实现可能有一些其他的限制,但我不熟悉任何这样做。因此,只需确保在您的实施中具有正确的对齐就足够了,我认为这应该没问题。 (在 C++11 之前的编译器中,您可以使用编译器扩展来设置对齐方式,例如 __attribute__((alignment(X)))__declspec(align(X))

    我相信只要你不直接访问底层存储,别名规则甚至不会出现,因为别名规则涵盖了何时可以通过对象访问对象的值一种不同的类型。构造一个对象并仅访问该对象并不涉及通过任何其他类型的对象访问该对象的值。

    较早的答案

    别名规则特别允许 char 数组为其他对象设置别名。

    如果程序尝试通过以下方式访问对象的存储值 行为是以下类型之一以外的泛左值 未定义:

    [...]

    — char 或 unsigned char 类型。

                                                                                 —左值和右值 [basic.lval] 3.10/10

    您确实需要确保数组正确对齐类型 T。

    alignas(T) char m_data[sizeof(T)];
    

    以上是用于设置对齐的 C++11 语法,但如果您使用的是 C++03 编译器,那么您将需要编译器特定的属性来执行相同的操作。 GCC 有__attribute__((aligned(32))),MSVC 有__declspec(align(32))


    Kerrek SB 提出了一个很好的观点,别名规则规定可以通过 char 数组访问 T 对象的值,但这可能并不意味着可以通过 T 对象访问 char 数组的值.但是,如果placement new 表达式定义良好,那么它会创建一个T 对象,我认为可以按照定义将其作为T 对象访问,并且读取原始char 数组正在访问创建的T 对象的值,这将在下面介绍别名规则。

    我认为这意味着您可以将 T 对象存储在例如 int 数组中,并且只要您不通过原始 int 数组访问该 T 对象的值,那么您就不会遇到 undefined行为。

    【讨论】:

      【解决方案2】:

      允许获取T 对象并将其解释为字符数组。但是,通常不允许 获取任意字符数组并将其视为T,甚至是指向包含T 的内存区域的指针。至少,您的 char 数组需要正确对齐。

      解决这个问题的一种方法可能是使用联合:

      union storage { char buf[sizeof(T)]; T dummy; };
      

      现在您可以在storage.buf 内部构造一个T

      T * p = ::new (storage.buf) T();
      

      【讨论】:

      • @MaciejPiechotka 在 C++11 中的联合成员不需要具有琐碎的构造函数。 (但是读取最后一个成员以外的工会成员仍然是UB)
      • @bames53:我知道。问题是我使用的是 C++03 编译器。
      • 如果你想要一个指向存储在字符缓冲区中的类的指针,你也可以做 dynamic_cast。但这有各种各样的问题。
      • @bames53:我不建议访问dummy 元素。它只是为了给整个工会正确的对齐方式。你会说new (storage.buf) T 等,并且只使用buf
      • @KerrekSB:在联合中完全避免buf 不是更好吗(比如说union U { char dummy; T obj; U() : dummy(0) {}} 并使用obj 放置新的(new(&amp;obj) T(...))?
      猜你喜欢
      • 2020-04-11
      • 1970-01-01
      • 2016-10-09
      • 1970-01-01
      • 2016-02-24
      • 1970-01-01
      • 2015-01-16
      相关资源
      最近更新 更多