鉴于以下代码,我有一些与类型双关相关的问题。我看不出这没有违反严格的别名规则,但我无法指出具体的违规行为。我最好的猜测是,将联合成员传递到函数中违反了严格的别名。
以下代码位于Compiler Explorer上。
#include <stdint.h>
union my_type
{
uint8_t m8[8];
uint16_t m16[4];
uint32_t m32[2];
uint64_t m64;
};
int func(uint16_t *x, uint32_t *y)
{
return *y += *x;
}
int main(int argc, char *argv[])
{
union my_type mine = {.m64 = 1234567890};
return func(mine.m16, mine.m32);
}
Run Code Online (Sandbox Code Playgroud)
我的观察:
func不互相别名,func则不违反严格别名。unionfor 类型双关。m16和m32进入func必须违反某些内容。我的问题:
func?违反的规则是 C 2018 6.5.16.1 3:
如果存储在一个对象中的值是从与第一个对象的存储以任何方式重叠的另一个对象读取的,则重叠应是精确的,并且两个对象应具有兼容类型的合格或不合格版本;否则,行为是未定义的。
具体来说,在 中,存储在,*y += *x指向的对象中的值是从另一个对象 读取的,该对象与 的存储重叠,但重叠并不精确,并且无论限定符如何,这些对象也不具有兼容的类型。ymine.m16mine.m32mine.m16
请注意,此规则适用于简单分配,如 中E1 = E2,而代码具有组件分配E1 += E2。然而,复合赋值E1 += E2在 6.5.16.2 3 中定义为等效于,E1 = E1 + E2但左值E1仅计算一次。
像这样的数组类型双关有效吗?
是的,C 标准允许通过联合成员使用别名;读取除最后存储的成员之外的成员将重新解释新类型中的字节。然而,如果程序的行为是由 C 标准定义的,特别是上面引用的规则,这并不能免除程序遵守其他规则的责任。
将指针传递给 func 到底违反了什么?
传递指针不会违反任何规则。正如上面所回答的,使用指针的赋值违反了规则。
在这个例子中我还遗漏了哪些其他问题?
如果我们改变func:
int func(uint16_t *x, uint32_t *y)
{
*y += 1;
*x += 1;
return *y;
}
Run Code Online (Sandbox Code Playgroud)
那么 6.5.16.1 3 中的规则不适用,因为不存在涉及重叠对象的分配。并且不违反 6.5 7 中的别名规则,定义*y为用于访问它的类型的对象,uint16_t,和*x是定义为用于访问它的类型的对象,uint32_t。然而,如果这个函数被单独翻译(没有union可见的定义),则允许编译器假设*x并且*y不重叠,因此它可以缓存*y由 生成的值并返回该缓存的值,而忽略更改*y += 1;的事实。这是C标准的一个缺陷。*x += 1;*y