如果我不初始化sum变量,为什么这个简单的总和会被编译器优化打破?

时间:2014-09-21 17:26:08

标签: c compiler-optimization

我正在将C代码编译成Matlab MEX文件。 Matlab默认启用优化(-O2)。我有一个简单的规范化程序,由此打破。到这里使用sum时,当它应该是1.000时,该值仅为0.995:

int N = 10000;
double *w, sum;

for(i=0; i<N; i++) {
    w[i] = 1.0/N;
}

...a couple unrelated operations...

for(k=0; k<N; k++) {
    sum += w[k];
}
for(k=0; k<N; k++) {
    w[k] = w[k]/sum;
}

当我用-g编译时,一切都很好。所以我认为这一定不能达到ISO标准。然后我发现了我所缺少的东西:

sum = 0.0;

这解决了它。所以我想编译器决定我不关心那个变量的确切值,因为我没有打算正确地初始化它?有人会解释吗?

编辑:是的,我知道它未定义,但这并不能解释为什么以及它如何影响该总和的优化。 如何做出明确的决定,说明,&#34;即使他正在阅读总和,他也不必关心价值。&#34;是否以某种方式跟踪未定义的值?

1 个答案:

答案 0 :(得分:1)

让自己置身于编译器的位置。您正在实施C标准的要求和保证。

所以你编译代码,没有优化,得到这样的东西,逐行编译代码而不进行任何分析:

- add stack space for `sum`
...
- initialize k to 0
beginning_of_for:
- if k < N is not met, goto after_for
-   add w[k] to sum
-   increment k
-   goto beginning_of_for
after_for:
- ...
- (for example) print sum

运行且最初sum碰巧偶然包含值0.您没有看到任何奇怪的行为,因为您很幸运(或者说不幸运)。

现在他们告诉你,编译器,优化代码。您需要环顾四周并挤出任何不必要的操作以节省时间和/或空间。你到处走走,发现这笔钱是没有初始化的。按照标准,这意味着您可以保证以后赢得从该变量中读取(即使程序员已经这样做了,但是您并不关心,因为标准说你不需要照顾)。此外,如果您将一个未初始化的值添加到某个值,您将获得另一个未初始化的值,再次确保您赢得以后读取它。

所以这是你作为编译器的假设:

- variable X is defined but not initialized
- some operations that don't read from X
- operation that writes to X a value that is not uninitialized
- operations that read from X

从您的角度来看,直到X使用不依赖于其他未初始化值的值进行初始化,然后从X读取任何值都可以给出任意值,因此您可以仅使用0来代替实际读取从那个价值。更重要的是,任何基于未初始化值的写入都可以被丢弃,因为结果仍然是未初始化的值,因此它可以是任何值。

换句话说,您之前未优化过的代码:

- add stack space for `sum`
...
- initialize k to 0
beginning_of_for:
- if k < N is not met, goto after_for
-   add w[k] to sum
-   increment k
-   goto beginning_of_for
after_for:
- ...
- (for example) print sum

分析如下:

Pass 1:

- add stack space for `sum`            [sum uninitialized]
...
- initialize k to 0                    [keep this as is]
beginning_of_for:
- if k < N is not met, goto after_for  [keep this as is]
-   add w[k] to sum                    [remove this line: sum is still uninitialized]
-   increment k                        [keep this as is]
-   goto beginning_of_for              [keep this as is]
after_for:
- ...
- (for example) print sum              [use 0 or whatever instead of sum]

给出了这个:

- add stack space for `sum`
...
- initialize k to 0
beginning_of_for:
- if k < N is not met, goto after_for
-   increment k
-   goto beginning_of_for
after_for:
- ...
- (for example) print whatever

优化的下一步是:

Pass 2:

- add stack space for `sum`            [sum uninitialized]
...
- initialize k to 0                    [replace 0 with N because of (1)]
beginning_of_for:
- if k < N is not met, goto after_for  [remove because of (1)]
-   increment k                        [remove because of (1)]
-   goto beginning_of_for              [remove because of (1)]
after_for:
- ...
- (for example) print whatever         [keep this as is]

(1) the for loop is empty.  `k` is `int` so it is guaranteed it will not overflow
    (Note: signed integer overflow is **undefined behavior** according to
    the standard), so the loop terminates with a single side effect: `k` reaches
    `N`.  So there is no point in actually looping.

现在您的代码变为:

- add stack space for `sum`
...
- initialize k to N
- ...
- (for example) print whatever

在最后一遍中,你会得到:

 Pass 3:

- add stack space for `sum`            [remove because sum is unused]
...
- initialize k to N                    [remove because k is unused]
- ...
- (for example) print whatever         [keep this as is]

这意味着你最终会留下:

- (for example) print whatever