昨天看王垠的博客看到一个有意思的问题,代码如下
void contains_null_check(int *P) {
int dead = *P;
if (P == 0)
return;
*P = 4;
}
在老版本的Clang中开启O3优化之后,会变成
void contains_null_check_after_RNCE(int *P) {
int dead = *P;
if (P == 0) // P 在上一行被访问,所以这里 P 不可能是 null
return;
*P = 4;
}
void contains_null_check_after_RNCE_and_DCE(int *P) {
//int dead = *P; // 死代码消除
//if (P == 0) // 死代码
// return; // 死代码
*P = 4;
}
优化结束,最后剩下
void contains_null_check_after_RNCE_and_DCE(int *P) {
*P = 4;
}
我们可以发现上面这样优化代码,编译器做了两点优化
首先我觉得编译器优化应该不止是扫描优化一次,像O3这种会重复多趟进行优化消除无用代码,所以这里就存在一个优化1还是先优化2,两种不同的方式可以得到不同的结果。上面的例子毫无疑问先进行了2操作,如果我们先进行1操作呢?
void contains_null_check_after_DCE(int *P) {
//int dead = *P; // dead变量未使用,死代码消除
if (P == 0)
return;
*P = 4;
}
void contains_null_check_after_DCE_and_RNCE(int *P) {
if (P == 0) // 保留空检测
return; // 保留
*P = 4;
}
优化结束,最后变成
void contains_null_check_after_DCE_and_RNCE(int *P) {
if (P == 0)
return;
*P = 4;
}
现在Clang新版本已经修复,采用了第二种优化顺序,和GCC保持一致,得到的结果都是
void contains_null_check_after_DCE_and_RNCE(int *P) {
if (P == 0)
return;
*P = 4;
}
如果我们修改一下代码,修改为下面这样
int contains_null_check(int *P) {
int dead = *P;
if (P == 0)
return -1;
*P = 4;
return dead;
}
编译器就会进行无用NULL检测消除了
int contains_null_check(int *P) {
int dead = *P; //被用作返回值,保留
if (P == 0) //上面被解引用了,不可能为空,消除
return -1; //消除
*P = 4;
return dead;
}
最后变成了
int contains_null_check(int *P) {
int dead = *P;
*P = 4;
return dead;
}
如果用汇编来看会只剩下
mov eax, DWORD PTR [rdi]
mov DWORD PTR [rdi], 4
评论
暂无评论~~