这个指针构建是否会破坏严格的别名规则?
这是Quake III Arena的快速反平方根实现:
float Q_rsqrt( float number ) { long i; float x2, y; const float threehalfs = 1.5F; x2 = number * 0.5F; y = number; i = * ( long * ) &y; // evil floating point bit level hacking i = 0x5f3759df - ( i >> 1 ); // what? y = * ( float * ) &i; y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration // y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed return y; }
我注意到long int i
在float y
的地址(转换为long *
)中取消引用的值。 然后代码在i
上执行操作,然后将解除引用的值存储在i
的地址(转换为float *
)中。
这是否会破坏严格的别名规则,因为i
与y
类型不同?
我想也许它不会,因为价值被解除引用和复制 ; 所以操作是在副本而不是原件上执行的。
是的,这段代码严重破坏并调用未定义的行为。 特别注意这两行:
y = number; i = * ( long * ) &y; // evil floating point bit level hacking
由于对象*(long *)&y
类型为long
,编译器可以自由地假设它不能为float
类型的对象设置别名; 因此,编译器可以相对于彼此重新排序这两个操作。
要修复它,应该使用联合。
是的,它打破了别名规则。
在现代C中,你可以改变i = * (long *) &y;
至:
i = (union { float f; long l; }) {y} .l;
和y = * (float *) &i;
至:
y = (union { long l; float f; }) {i} .f;
如果您保证在使用的C实现中, long
和float
具有合适的大小和表示,则行为由C标准定义:一种类型的对象的字节将被重新解释为另一种类型。
是的,它打破了别名规则。
对于像i = * ( long * ) &y;
这样的东西最干净的修复i = * ( long * ) &y;
会是这样的:
memcpy(&i, &y, sizeof(i)); // assuming sizeof(i) == sizeof(y)
它避免了对齐和锯齿问题。 在启用优化的情况下,对memcpy()
的调用通常应该只用几条指令替换。
正如任何其他方法所建议的那样,此方法不会解决与陷阱表示相关的任何问题。 但是,在大多数平台上,整数中没有陷阱表示,如果您知道浮点格式,则可以避免浮点格式陷阱表示(如果有)。
i = * ( long * ) &y;
这会破坏别名规则,因此会调用未定义的行为。
您正在访问对象y
,其类型不同于float
,或者是有符号/无符号的char
变体。
y = * ( float * ) &i;
上面的这个陈述也打破了别名规则。