通用的char []基于存储和避免严格走样相关UB(Generic char[] based sto

2019-07-29 21:11发布

我试图建立一个包在一个适当大的字符数组一堆的类型,并允许访问数据作为单独的输入正确引用类模板。 现在,根据标准,这可能导致严走样违反,所以未定义的行为,正如我们所访问的char[]通过一个对象,它是与其不兼容的数据。 具体而言,标准规定:

如果一个程序试图通过的以外的以下类型的行为是未定义的一个glvalue访问对象的存储值:

  • 动态型的对象,
  • 动态对象的类型的CV-合格版本,
  • 类似类型(如在4.4定义)的动态类型的对象,
  • 一种类型,是有符号或对应的动态对象的类型无符号类型,
  • 一个类型在所述签名或对应于动态型的对象的CV-合格版本无符号类型,
  • 包括其元件或非静态数据成员之间的上述类型的一个集合体或联合类型(包括递归地,一个元件或子聚集的非静态数据成员或包含联合),
  • 一种类型是一个(可能CV修饰)基类型的动态型的对象的,
  • 一个charunsigned char类型。

由于突出圆点的措辞,我想出了以下alias_cast想法:

#include <iostream>
#include <type_traits>

template <typename T>
T alias_cast(void *p) {
    typedef typename std::remove_reference<T>::type BaseType;
    union UT {
        BaseType t;
    };
    return reinterpret_cast<UT*>(p)->t;
}

template <typename T, typename U>
class Data {
    union {
        long align_;
        char data_[sizeof(T) + sizeof(U)];
    };
public:
    Data(T t = T(), U u = U()) { first() = t; second() = u; }
    T& first() { return alias_cast<T&>(data_); }
    U& second() { return alias_cast<U&>(data_ + sizeof(T)); }
};


int main() {
    Data<int, unsigned short> test;
    test.first() = 0xdead;
    test.second() = 0xbeef;
    std::cout << test.first() << ", " << test.second() << "\n";
    return 0;
}

(以上测试代码,尤其是Data类仅仅是思想的简单化向下的示范,所以请不要指出我应该如何使用std::pairstd::tuple 。该alias_cast模板还应该扩大到处理简历合格类型和它只能如果对齐要求得到满足安全使用,但我希望这片段是足以证明这个想法。)

这一招用g沉默的警告++(编译时加入g++ -std=c++11 -Wall -Wextra -O2 -fstrict-aliasing -Wstrict-aliasing ),代码工作,但真的是这样讲的一个有效途径编译器跳过基于严格走样优化?

如果它是无效的,那么如何将一个去实现这样一个字符数组基于通用存储类不违反别名规则?

编辑:更换alias_cast用一个简单reinterpret_cast是这样的:

T& first() { return reinterpret_cast<T&>(*(data_ + 0)); }
U& second() { return reinterpret_cast<U&>(*(data_ + sizeof(T))); }

产生当与G ++编译以下警告:

aliastest那么1.cpp:在“T和数据::第一()的实例化[用T = INT; U =短无符号整型]':aliastest那么1.cpp:28:16:
从这里需要aliastest那么1.cpp:21:58:警告:提领类型-punned指针将打破严格走样规则[-Wstrict混叠]

Answer 1:

使用UNION是几乎从来没有一个好主意,如果你想坚持严格的一致性,他们有严格的规则,当谈到读书活动成员(这一项)。 虽然它必须说,实现喜欢用工会作为挂钩可靠的行为,也许这是你所追求的。 如果这是我听从谁写迈克·阿克顿的情况下, 一个漂亮的(长)的文章对别名规则,他不通过工会铸造评论。

据我所知,这是你应该如何处理字符类型作为存储阵列:

// char or unsigned char are both acceptable
alignas(alignof(T)) unsigned char storage[sizeof(T)];
::new (&storage) T;
T* p = static_cast<T*>(static_cast<void*>(&storage));

这被定义为工作的原因是, T 动态类型这里的对象。 当新的表达式中创建的存储重复使用T对象,该对象的操作结束隐含的寿命storage (恰好平凡作为unsigned char是一个,那么, 琐碎类型)。

仍然可以使用例如storage[0]到,因为这是通过的glvalue读取对象值读出的对象中的字节unsigned char类型,所列出的明确的例外之一。 如果另一方面storage是不同的,但仍微不足道的元素类型的,你仍然可以让上面的代码工作,但不会是能够做到storage[0]

最后一块,使片断明智是指针转换。 需要注意的是reinterpret_cast 适合在一般情况下。 它可以有效鉴于T为标准布局(有上排列更多的限制,也是如此),但如果是这样,然后使用情况reinterpret_cast就相当于static_cast通过荷兰国际集团void像我一样。 它更有意义,直接使用的形式摆在首位,特别是考虑到使用的存储的情况经常发生在普通的环境。 在任何情况下转换到和从void是标准转化(与良好定义的含义)中的一个,并且想要static_cast那些。

如果你是在所有担心的指针转换(这在我看来是最薄弱的环节,而不是存储重复使用的参数),那么还有一个办法是做

T* p = ::new (&storage) T;

其中,如果你想跟踪它的成本附加的指针存储。

我衷心建议使用std::aligned_storage



文章来源: Generic char[] based storage and avoiding strict-aliasing related UB