如果您想知道编译器的作用,最好的办法是查看编译器文档。对于优化,您可以查看LLVM 的 Analysis and Transform Passes例如。
1) sin(3.141592) // 是否会在编译时进行评估?
大概。IEEE 浮点计算有非常精确的语义。顺便说一句,如果您在运行时更改处理器标志,这可能会令人惊讶。
2) int a = 0; a = exp(18), cos(1.57), 2;
这取决于:
- 函数
exp
和cos
是否内联
- 如果不是,它们是否正确注释(因此编译器知道它们没有副作用)
对于取自 C 或 C++ 标准库的函数,应正确识别/注释它们。
至于消除计算:
-adce
: 积极的死代码消除
-dce
: 死代码消除
-die
: 死指令消除
-dse
: 死店消除
编译器喜欢寻找无用的代码 :)
3)
与2)
实际相似。不使用存储的结果并且表达式没有副作用。
最后:什么不对编译器进行测试?
#include <math.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
double d = sin(3.141592);
printf("%f", d);
int a = 0; a = (exp(18), cos(1.57), 2); /* need parentheses here */
printf("%d", a);
for (size_t i = 0; i < 10; ++i) {
int a = 10 + i;
}
return 0;
}
Clang 在编译过程中已经尝试提供帮助:
12814_0.c:8:28: warning: expression result unused [-Wunused-value]
int a = 0; a = (exp(18), cos(1.57), 2);
^~~ ~~~~
12814_0.c:12:9: warning: unused variable 'a' [-Wunused-variable]
int a = 10 + i;
^
以及发出的代码(LLVM IR):
@.str = private unnamed_addr constant [3 x i8] c"%f\00", align 1
@.str1 = private unnamed_addr constant [3 x i8] c"%d\00", align 1
define i32 @main(i32 %argc, i8** nocapture %argv) nounwind uwtable {
%1 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([3 x i8]* @.str, i64 0, i64 0), double 0x3EA5EE4B2791A46F) nounwind
%2 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([3 x i8]* @.str1, i64 0, i64 0), i32 2) nounwind
ret i32 0
}
我们注意到:
- 正如预测的那样,
sin
计算已在编译时解决
- 正如预测的那样,
exp
并且cos
已被完全剥离。
- 正如预测的那样,循环也被剥离了。
如果您想更深入地研究编译器优化,我鼓励您:
- 学习阅读 IR(这非常容易,真的,组装起来要简单得多)
- 使用 LLVM 试用页面来测试您的假设