C++ 严格的混叠和对齐

C++ 严格的混叠和对齐,c++,c++11,unions,strict-aliasing,type-punning,C++,C++11,Unions,Strict Aliasing,Type Punning,我需要一种在任意POD类型之间使用别名的安全方法,明确考虑到n3242或更高版本的3.10/10和3.11,符合ISO-C++11。 这里有很多关于严格混叠的问题,其中大部分关于C而不是C++。我找到了一个C的“解决方案”,它使用联合,可能使用了这个部分 联合类型,其中包括上述类型之一 元素或非静态数据成员 从那我建造了这个 #include <iostream> template <typename T, typename U> T& access_as(U*

我需要一种在任意POD类型之间使用别名的安全方法,明确考虑到n3242或更高版本的3.10/10和3.11,符合ISO-C++11。 这里有很多关于严格混叠的问题,其中大部分关于C而不是C++。我找到了一个C的“解决方案”,它使用联合,可能使用了这个部分

联合类型,其中包括上述类型之一 元素或非静态数据成员

从那我建造了这个

#include <iostream>

template <typename T, typename U>
T& access_as(U* p)
{
    union dummy_union
    {
        U dummy;
        T destination;
    };

    dummy_union* u = (dummy_union*)p;

    return u->destination;
}

struct test
{
    short s;
    int i;
};

int main()
{
    int buf[2];

    static_assert(sizeof(buf) >= sizeof(double), "");
    static_assert(sizeof(buf) >= sizeof(test), "");

    access_as<double>(buf) = 42.1337;
    std::cout << access_as<double>(buf) << '\n';

    access_as<test>(buf).s = 42;
    access_as<test>(buf).i = 1234;

    std::cout << access_as<test>(buf).s << '\n';
    std::cout << access_as<test>(buf).i << '\n';
}

*编辑:如果不是的话,怎么能把它修改成合法的呢?

我认为在最基本的层面上,这是不可能的,而且违反了严格的别名。您所做的唯一一件事就是诱使编译器不注意。

除了当
sizeof(T)>sizeof(U)
时出现的错误之外,可能存在的问题是,由于
T
,联合具有比
U
更高的适当对齐方式。 如果不实例化此联合,使其内存块对齐(并且足够大!),然后获取目标类型为
t
的成员,则在最坏的情况下,它将无声地中断

例如,如果执行
U*
的C样式转换,其中
U
需要对齐4个字节,到
dummy\U union*
,其中
dummy\U union
需要对齐8个字节,因为
alignof(T)==8
。在此之后,您可能会读取类型为
T
的union成员,其对齐长度为4字节,而不是8字节


Alias cast(仅适用于吊舱的对齐和尺寸安全重新解释): 此建议确实明确违反了严格别名,但使用了静态断言:

///@brief Compile time checked reinterpret_cast where destAlign <= srcAlign && destSize <= srcSize
template<typename _TargetPtrType, typename _ArgType>
inline _TargetPtrType alias_cast(_ArgType* const ptr)
{
    //assert argument alignment at runtime in debug builds
    assert(uintptr_t(ptr) % alignof(_ArgType) == 0);

    typedef typename std::tr1::remove_pointer<_TargetPtrType>::type target_type;
    static_assert(std::tr1::is_pointer<_TargetPtrType>::value && std::tr1::is_pod<target_type>::value, "Target type must be a pointer to POD");
    static_assert(std::tr1::is_pod<_ArgType>::value, "Argument must point to POD");
    static_assert(std::tr1::is_const<_ArgType>::value ? std::tr1::is_const<target_type>::value : true, "const argument must be cast to const target type");
    static_assert(alignof(_ArgType) % alignof(target_type) == 0, "Target alignment must be <= source alignment");
    static_assert(sizeof(_ArgType) >= sizeof(target_type), "Target size must be <= source size");

    //reinterpret cast doesn't remove a const qualifier either
    return reinterpret_cast<_TargetPtrType>(ptr);
}
//@short Compile time checked reinterpret\u cast where destAlign
我的问题是,可以肯定的是,根据标准,这个项目合法吗

否。使用您提供的别名,对齐可能不自然。您编写的联合只是移动别名的点。它似乎可以工作,但当CPU选项、ABI或编译器设置更改时,该程序可能会失败

如果不是的话,怎么能把它修改成合法的呢


创建自然的临时变量,并将存储视为一个内存块(在内存块中进出临时变量),或者使用一个表示所有类型的并集(请记住,这里一次只有一个活动元素)。

这永远不会合法,无论您使用奇怪的类型转换、并集等执行何种扭曲

基本事实是:两个不同类型的对象可能永远不会在内存中使用别名,但有一些特殊的例外(请参阅下文)

例子 考虑以下代码:

void sum(double& out, float* in, int count) {
    for(int i = 0; i < count; ++i) {
        out += *in++;
    }
}
int a[2];
double d;
static_assert(sizeof(a) == sizeof(d));
for(size_t i = 0; i < sizeof(a); ++i)
   ((char*)a)[i] = ((char*)&d)[i];
权变措施 将一个对象的位重新解释为其他类型对象的位的唯一标准方法是使用等效的
memcpy
。这利用了对
char
对象进行别名的特殊规定,实际上允许您在字节级别读取和修改底层对象表示。例如,以下内容是合法的,并且不违反严格的别名规则:

int a[2];
double d;
static_assert(sizeof(a) == sizeof(d));
memcpy(a, &d, sizeof(d));
这在语义上等同于以下代码:

void sum(double& out, float* in, int count) {
    for(int i = 0; i < count; ++i) {
        out += *in++;
    }
}
int a[2];
double d;
static_assert(sizeof(a) == sizeof(d));
for(size_t i = 0; i < sizeof(a); ++i)
   ((char*)a)[i] = ((char*)&d)[i];
类似地,通过获取地址、强制转换结果指针和取消引用结果进行的访问具有未定义的行为,即使强制转换使用联合类型,例如:

新位置 (注意:我在这里使用内存,因为我现在没有访问标准的权限)。 将新对象放入存储缓冲区后,底层存储对象的生存期将隐式结束。这与您写信给工会成员时发生的情况类似:

union {
    int i;
    float f;
} u;

// No member of u is active. Neither i nor f refer to an lvalue of any type.
u.i = 5;
// The member u.i is now active, and there exists an lvalue (object)
// of type int with the value 5. No float object exists.
u.f = 5.0f;
// The member u.i is no longer active,
// as its lifetime has ended with the assignment.
// The member u.f is now active, and there exists an lvalue (object)
// of type float with the value 5.0f. No int object exists.
现在,让我们看一看与新位置类似的内容:

#define MAX_(x, y) ((x) > (y) ? (x) : (y))
// new returns suitably aligned memory
char* buffer = new char[MAX_(sizeof(int), sizeof(float))];
// Currently, only char objects exist in the buffer.
new (buffer) int(5);
// An object of type int has been constructed in the memory pointed to by buffer,
// implicitly ending the lifetime of the underlying storage objects.
new (buffer) float(5.0f);
// An object of type int has been constructed in the memory pointed to by buffer,
// implicitly ending the lifetime of the int object that previously occupied the same memory.

由于明显的原因,这种隐式生命周期结束只能发生在具有平凡构造函数和析构函数的类型上。

@KerrekSB他不读取非活动的联合成员。对联合成员的访问在整个访问过程中保持对左值的写访问。@KerrekSB它不是活动成员。但它也不读任何东西。甚至连一个联合对象都不存在。但是由于C++没有真正指定这一点,所以我们不能谈论它。这是一个黑洞。将联合体放在一边可以大大减少噪音。第3.10/10条不得涵盖第3.8/3.10条中有意义的任何写入访问。例如,如果对齐方式兼容,则这是完全合法的:
inta;浮动*b=(浮动*)&a*b=1.0f;std::cout@KerrekSB这不是别名冲突。它与
*(double*)malloc(sizeof(double))=43.1337没有区别,只是在这种情况下,我们保证了对齐条件。在这种情况下,写入之前的左值也没有引用
double
对象。@KerrekSB这一点很重要,因为循环使用旧对象存储的分配器就是这样工作的。所以你认为没有办法“在任意POD类型之间安全地进行别名”吗?这让我觉得有点尴尬。。我所知道的最自由的语言可能不允许我做我需要做的事情:(你读过评论吗?你对Johannes的建议有什么意见?)JoKY451:“这对我来说有点尴尬……那是因为你想做的事情对C++来说很尴尬。@ Cojky451:这很尴尬,因为C++的类型系统的规则不允许你这么做。@ Cojky451:“一定有办法。”C++没有精确地按照你希望的那样去做任何事情。此外,谁说你不能用<代码> char *<代码>高效地实现哈希?只要拉四个代码的序列> char *<代码> s;你就不关心对象的字节数。或者,你可以接受你的代码LIV。实现定义域中的es。它从未阻止过任何人…@cooky451:当然,在任意类型之间使用别名没有安全的方法。这实际上就是严格的别名规则的确切文本-除了
char*void sum(double& out, float* in, int count) {
    for(int i = 0; i < count; ++i) {
        out += *in++;
    }
}
void sum(double& out, float* in, int count) {
    for(int i = 0; i < count; ++i) {
        register double out_val = out; // (1)
        register double in_val = *in; // (2)
        register double tmp = out_val + in_val;
        out = tmp; // (3)
        in++;
    }
}
void sum(double& out, float* in, int count) {
    register double tmp = out; // (1)
    for(int i = 0; i < count; ++i) {
        register double in_val = *in; // (2)
        tmp = tmp + in_val;
        in++;
    }
    out = tmp; // (3)
}
union {
    double d;
    float f[2];
};
f[0] = 3.0f;
f[1] = 5.0f;
sum(d, f, 2); // UB: attempt to treat two members of
              // a union as simultaneously active
int a[2];
double d;
static_assert(sizeof(a) == sizeof(d));
memcpy(a, &d, sizeof(d));
int a[2];
double d;
static_assert(sizeof(a) == sizeof(d));
for(size_t i = 0; i < sizeof(a); ++i)
   ((char*)a)[i] = ((char*)&d)[i];
int f() {
    union a_union t;
    int* ip;
    t.d = 3.0;
    ip = &t.i;
    return *ip;
}
int f() {
    double d = 3.0;
    return ((union a_union *) &d)->i;
} 
union {
    int i;
    float f;
} u;

// No member of u is active. Neither i nor f refer to an lvalue of any type.
u.i = 5;
// The member u.i is now active, and there exists an lvalue (object)
// of type int with the value 5. No float object exists.
u.f = 5.0f;
// The member u.i is no longer active,
// as its lifetime has ended with the assignment.
// The member u.f is now active, and there exists an lvalue (object)
// of type float with the value 5.0f. No int object exists.
#define MAX_(x, y) ((x) > (y) ? (x) : (y))
// new returns suitably aligned memory
char* buffer = new char[MAX_(sizeof(int), sizeof(float))];
// Currently, only char objects exist in the buffer.
new (buffer) int(5);
// An object of type int has been constructed in the memory pointed to by buffer,
// implicitly ending the lifetime of the underlying storage objects.
new (buffer) float(5.0f);
// An object of type int has been constructed in the memory pointed to by buffer,
// implicitly ending the lifetime of the int object that previously occupied the same memory.