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