主流C编译器能否优化掉foo调用的bar中不必要的i>0比较?
好问题!主流C编译器(比如GCC、Clang、MSVC这类)完全有能力优化掉bar(int)里那个多余的i > 0比较,但这可不是无条件的——核心取决于编译器能不能通过静态分析100%确定i的取值范围,不需要留着运行时检查。
以下几种情况,编译器会放心地把i > 0的判断删掉:
传入
bar的是编译期常量
如果foo里调用bar时传的是像5、100这种固定值,编译器一眼就能确定i肯定大于0,直接把分支判断消除,甚至会把bar的代码内联到foo里。比如:void bar(int i) { if (i > 0) { printf("i is positive\n"); } } void foo() { bar(7); // 编译期常量,确定>0 }编译后,
bar里的if判断会完全消失,直接执行printf。调用上下文能明确
i的取值范围
如果foo里的代码逻辑已经把传入bar的参数限制在大于0的范围,编译器的数据流分析能追踪到这一点,就会删掉多余的判断。比如:void bar(int i) { if (i > 0) { // 一些业务操作 } } void foo(int x) { if (x <= 0) { return; // 提前返回,确保下面的x一定>0 } bar(x); }这里编译器能确定,走到
bar(x)时x必然大于0,所以bar里的i > 0判断就是冗余的。bar是静态函数且所有调用都满足i>0
如果bar被标记为static(只能在当前编译单元内调用),编译器能看到所有调用bar的地方。要是所有调用传入的参数都大于0,编译器会直接把判断去掉。比如整个.c文件里所有bar的调用都是传正数,那这个判断就没用了。
这些情况下,编译器不敢随便删掉i > 0的判断:
bar是全局可见函数,无法掌握所有调用场景
如果bar是全局函数(没加static),其他编译单元可能也会调用它,甚至传入小于等于0的参数。编译器在单独编译当前单元时,没法知道其他地方的调用情况,所以必须保留判断。就算开启链接时优化(LTO),只要发现有任何一处调用传了非正数,就不能删。比如:// bar.c(全局函数) void bar(int i) { if (i > 0) { // 操作 } } // foo.c void bar(int i); void foo() { bar(3); }编译
foo.c时,编译器不知道bar在其他文件里被怎么调用,所以不会优化bar里的判断。编译器无法推导
i的取值范围
如果i的值来自运行时输入、全局变量、或者返回值无法确定的函数,编译器没法提前知道i是正还是负,必须保留判断。比如:#include <stdio.h> void bar(int i) { if (i > 0) { printf("Positive\n"); } } void foo() { int x; scanf("%d", &x); // x的值运行时才确定 bar(x); }这种情况,编译器完全没法预判
x的取值,只能保留i > 0的判断。存在编译器无法追踪的数据流或未定义行为
如果代码里有volatile变量、指针修改参数、或者其他会让编译器无法确定i值的操作,也不能优化。比如:void bar(int i) { volatile int *p = &i; *p = -5; // 用volatile指针修改i,编译器无法追踪 if (i > 0) { // 操作 } }因为
volatile的读写必须保留,编译器没法确定i最终的值,所以必须保留判断。
内容的提问来源于stack exchange,提问作者user2856296

