C++ 严格的别名似乎不一致

C++ 严格的别名似乎不一致,c++,gcc,strict-aliasing,C++,Gcc,Strict Aliasing,有几个错误来自严格的别名,所以我想我会尝试修复所有这些错误。在详细了解了情况之后,GCC有时似乎不会发出警告,而且有些事情是不可能实现的。至少根据我的理解,下面的每一条都被破坏了。那么,我的理解是否有误,是否有一种正确的方法来完成所有这些事情,或者某些代码是否必须在技术上打破规则,并被系统测试很好地覆盖 错误来自一些混合了字符和无符号字符缓冲区的代码,例如: size_t Process(char *buf, char *end) { char *p = buf; Process

有几个错误来自严格的别名,所以我想我会尝试修复所有这些错误。在详细了解了情况之后,GCC有时似乎不会发出警告,而且有些事情是不可能实现的。至少根据我的理解,下面的每一条都被破坏了。那么,我的理解是否有误,是否有一种正确的方法来完成所有这些事情,或者某些代码是否必须在技术上打破规则,并被系统测试很好地覆盖

错误来自一些混合了字符和无符号字符缓冲区的代码,例如:

size_t Process(char *buf, char *end)
{
    char *p = buf;
    ProcessSome((unsigned char**)&p, (unsigned char*)end);
    //GCC decided p could not be changed by ProcessSome and so always returned 0
    return (size_t)(p - buf);
}
将此更改为以下内容似乎可以解决问题,尽管它仍然涉及演员阵容,因此我不确定为什么现在可以这样做,并且没有警告:

size_t Process(char *buf, char *end)
{
    unsigned char *buf2 = (unsigned char *)buf;
    unsigned char *p = buf2;
    unsigned char *end2 = (unsigned char*)end;
    ProcessSome(&p, end2);
    return (size_t)(p - buf2);
}
此外,还有许多其他地方似乎在没有警告的情况下工作

//contains a unsigned char* of data. Possibly from the network, disk, etc.
//the buffer contents itself is 8 byte aligned.
const Buffer *buffer = foo();
const uint16_t *utf16Text = (const uint16_t*)buffer->GetData();//const unsigned char*
//... read utf16Text. Does not even seem to ever be a warning


//also seems to work fine
size_t len = CalculateWorstCaseLength(...);
Buffer *buffer = new Buffer(len * 2);
uint16_t *utf16 = (uint16_t*)buffer->GetData();//unsigned char*
len = DoSomeProcessing(utf16, len, ...);
buffer->Truncate(len * 2);
send(buffer);
还有一些是

struct Hash128
{
    unsigned char data[16];
};
...
size_t operator ()(const Hash128 &hash)
{
    return *(size_t*)hash.data;//warning
}
非字符的情况。这没有警告,即使是坏的,我如何避免它(两种方法似乎都有效)

看看其他API,据我所知,似乎也有各种违反规则的情况(没有遇到Linux/GCC规范的情况,但肯定会有)

  • CoCreateInstance有一个void**output参数,需要显式的指针强制转换。Direct3D也有类似的功能

  • 大整数是可能对不同成员进行读/写操作的联合(例如,某些代码可能使用高/低,然后另一些代码可能读取int64)

  • 我记得CPython实现非常愉快地将PyObject*转换为一堆在开始时恰好具有相同内存布局的其他东西

  • 我见过的许多散列实现都会将输入缓冲区强制转换为uint32_t*,然后可能会使用uint8_t来处理最后的1-3个字节

  • 我所看到的几乎每个内存分配器实现都使用char*或unsigned char*,然后必须将其转换为所需的类型(可能通过返回的void*,但在分配内部至少是char)


  • char
    /
    无符号char
    指针不受严格的别名规则的约束

    从技术上讲,union技巧是一个别名错误,但主流编译器明确允许它

    因此,您的一些示例是有效的(根据语言,有些示例是UB,但编译器定义良好)


    但是,确实存在大量违反别名规则的代码。还请注意,MSVC不会基于严格的别名进行优化,因此特别是为Windows编写的代码可能会违反严格的别名规则。

    首先,指向
    char
    无符号char
    的指针非常多 免于遵守有关字符串别名的规则;你被允许 将任何类型的指针转换为
    char*
    无符号
    char*
    ,并将指向的对象看作是char的数组 或
    无符号字符
    。现在,关于您的代码:

    size_t Process(char *buf, char *end)
    {
        char *p = buf;
        ProcessSome((unsigned char**)&p, (unsigned char*)end);
        //GCC decided p could not be changed by ProcessSome and so always returned 0
        return (size_t)(p - buf);
    }
    
    这里的问题是,您试图将
    char*
    视为 它是一个
    无符号字符*
    。这不是保证。鉴于 演员阵容清晰可见,g++有点迟钝 关于不关闭严格别名分析 自动地,但从技术上讲,它被标准所涵盖

    另一方面,所有转换都涉及
    char*
    unsigned char*
    ,两者都可以别名任何内容,因此 需要编译器来完成这项工作

    至于其余的,你没有说返回类型是什么
    buffer->GetData()
    是,所以很难说。但如果是
    char*
    unsigned char*
    void*
    ,此代码完全合法 (第二次使用时丢失的铸件除外)
    buffer->GetData()
    )。只要所有演员都参与
    char*
    unsigned char*
    void*
    (忽略
    const
    限定符),则编译器需要假定 是可能的别名:当原始指针具有 对于这些类型,它可以通过从 指向目标类型的指针,该语言保证 您可以将任何指针转换为这些类型之一,然后返回到 恢复原始类型,并恢复相同的值。(当然,如果
    char*
    最初不是一个
    uint16\u t
    ,您可能会得到 对齐问题,但编译器通常不知道这一点。)

    关于最后一个示例,您没有指出
    hash.data
    ,所以很难说;如果是
    char*
    void*
    unsigned char*
    ,该语言保证您的代码 (从技术上讲,前提是char指针是由 在实践中,转换
    尺寸*
    ;前提是 指针已充分对齐,指向的字节未对齐 为
    大小\u t
    形成补漏白值

    一般来说:“类型双关”唯一真正有保证的方式是 通过
    memcpy
    。否则,指针将强制转换,例如 只要在
    void*
    中执行,就可以保证,
    char*
    unsigned char*
    ,至少就别名而言 担心的(其中一个可能导致对齐 问题,或者在取消引用时访问补漏白值。)

    请注意,您可能会从其他供应商处获得额外担保 标准。Posix需要类似于:

    void (*pf)();
    *((void**)&pf) = ...
    
    比如说工作。(一般来说,转换和取消引用 如果您不做任何事情,即使使用g++,也会立即起作用 函数中可能与别名相关的位置。)

    我所知道的所有编译器都允许对 有时输入双关语。(至少有一些,包括 g++,在其他情况下合法使用
    union
    将失败。 对于编译器编写者来说,正确处理
    联合
    是一件棘手的事情 如果size_t Process(char *buf, char *end) { unsigned char *buf2 = (unsigned char *)buf; unsigned char *p = buf2; unsigned char *end2 = (unsigned char*)end; ProcessSome(&p, end2); return (size_t)(p - buf2); }
    void (*pf)();
    *((void**)&pf) = ...