通过将乘法放在递归函数调用之前,编译器可以将该代码转换为可优化的尾部调用:
int factorial(int x) {
return factorial_tail_call(x, 1);
}
int factorial_tail_call(int x, int result) {
if (x > 1) return factorial_tail_call(x-1, result*x);
return result;
}
通过递归调用result*x
before的评估factorial_tail_call
,编译器可以确定x
并且result
不再需要。因此,它可以将它们从堆栈中弹出。这证明了堆栈不需要增长。
你能看到转换后的代码之间有什么相似之处吗?在1
同一个地方,条件x > 1
在同一个地方,return result;
在同一个地方。提供编译器实现尾调用优化,这只是表达相同算法的不同方式。通过将乘法表达式移动到一个参数中并将您的帖子中的代码放在注释中,您可能会看到一些功能相似之处,以及编译器如何设法进行其余的转换:
int factorial(int x) {
return factorial_tail_call(x, 1); // int result = 1;
}
int factorial_tail_call(int x, int result) {
if (x > 1) return factorial_tail_call(x-1, result*x); // while (x > 1) result *= x--;
return result; // return result;
}
n1570.pdf 的§5.1.2.3p4
在抽象机中,所有表达式都按照语义的规定进行评估。如果一个实际的实现可以推断出它的值没有被使用并且没有产生所需的副作用(包括调用函数或访问易失性对象引起的任何副作用),则它不需要评估表达式的一部分。
编译器是聪明的东西,由比我们大多数人优秀得多的程序员编写。如果编译器可以确定两段代码是等价的,那么它可以选择它希望的两者中的任何一个(有一些限制,在下面的引用中描述)。例如,它可以用一个printf
表达式替换一个计算和打印前一千个素数的循环。
n1570.pdf 的§5.1.2.3p6
对一致性实现的最低要求是:
— 对 volatile 对象的访问严格按照抽象机的规则进行评估。
— 在程序终止时,写入文件的所有数据应与根据抽象语义执行程序所产生的结果相同。
— 交互设备的输入和输出动态应按照 7.21.3 的规定进行。这些要求的目的是尽快出现无缓冲或行缓冲的输出,以确保在程序等待输入之前实际出现提示消息。
这是程序的可观察行为。
这就是为什么微优化是徒劳的原因之一。
如果另一个线程修改了 strlen 正在处理的字符串,那就是竞争条件。竞争条件是未定义的行为。您需要使用互斥锁保护字符串以确保不会发生这种情况,或者学习更好的多线程范例。你在看哪本书?
n1570.pdf 的§5.1.2.4p25
如果程序的执行包含不同线程中的两个冲突操作,则该程序的执行包含数据竞争,其中至少一个不是原子的,并且两者都不会在另一个之前发生。任何此类数据竞争都会导致未定义的行为。