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