请考虑以下代码段作为示例:
*pInt = 0xFFFF;
*pFloat = 5.0;
由于它们是int
和float
指针,因此编译器会假设它们不是别名并且可以交换它们。例如。
现在让我们假设我们用这个来加强它:
*pInt = 0xFFFF;
*pChar = 'X';
*pFloat = 5.0;
由于允许char*
为任何内容添加别名,因此可能会指向*pInt
,因此*pInt
的分配不能超出*pChar
的分配范围,因为它可能合法地指向*pInt
并将其第一个字节设置为' X'。
类似地,pChar
可能指向*pFloat
,在{c}分配之前无法移动*pFloat
的赋值,因为代码可能打算通过重新分配{{1}来取消先前字节设置的效果。 }。
这是否意味着我可以通过*pFloat
进行编写和阅读,以便为重新排列和其他严格的别名相关优化创建障碍?
答案 0 :(得分:5)
当编译器无法知道指针变量是否为另一个指针别名时,指针别名最有意义。与编译位于与调用者不同的翻译单元中的函数的情况一样。
void func (char* pChar, float* pFloat)
{
*pChar = 'X';
*pFloat = 5.0;
}
此处pFloat
分配确实无法在pChar
之前排序,因为编译器无法推断pChar
未指向与{{pFloat
相同的位置1}}。
但是,在面对这种情况时,编译器可以(并且可能会)添加运行时检查以查看地址是否指向重叠内存。如果他们这样做,那么代码必须按给定的顺序排序。如果没有,那么可以重新组织和优化代码。
意味着只有在指针实际上为别名/指向重叠内存的情况下才会获得类似内存屏障的行为。如果没有,那么关于指令排序的所有赌注都将被取消。所以这可能不是你应该依赖的机制。
答案 1 :(得分:3)
我认为一般来说,你不能将其作为一种排序障碍。原因是编译器可以对代码进行某种版本化
if (pInt == pChar || pFloat == pChar) {
// be careful
} else {
// no aliasing
}
显然,对于你提出的这个简单的情况根本没有任何优势,但如果你的指针在代码的大部分内没有改变,那么这将是有益的。
如果您只是通过使用虚拟pChar
将其作为“障碍”的手段,那么else
部分将永远获胜。但是编译器可以假设没有出现混叠,并且总是可以重新排序分配。
C标准提供重新排序保证的唯一不相关的数据是以顺序一致性操作的原子对象。
答案 2 :(得分:0)
如果一个程序需要使用基于指针的类型双关语,那么确保它可以与gcc一起使用的唯一可靠方法,也可能是使用clang,就是使用`-fno-strict-aliasing'。 “现代”编译器将积极地剥离不能改变对象所拥有的位的代码,然后使用由此产生的缺乏这种代码来“证明”那些本来不合法的优化。例如,
struct s1 {unsigned short x;};
struct s2 {unsigned short x;};
int test(struct s1 *p1, struct s2 *p2)
{
if (p1->x)
{
p2->x = 12;
unsigned char *cp = (unsigned char*)p1;
unsigned char c0=cp[0] ^ 1,c1=cp[1] ^ 2;
cp[0]=c0 ^ 1; cp[1]=c1 ^ 2;
}
return p1->x;
}
clang和gcc都会生成代码,返回执行“if”语句时p1所具有的值。我在标准中没有看到任何可以证明这一点的内容(如果p1 == p2,* p2的全部内容将通过字符类型读入类型为“char”的离散对象,这是定义的行为,以及这些离散对象的内容将用于覆盖* p1的整个内容,这也是定义的行为)但gcc和clang都会决定,因为写入cp [0]和cp [1]的值将匹配已存在的值,它应该省略那些操作。