C++编译错误?
C++ compilation bug?
我有以下代码:
#include <iostream>
#include <complex>
using namespace std;
int main() {
complex<int> delta;
complex<int> mc[4] = {0};
for(int di = 0; di < 4; di++, delta = mc[di]) {
cout << di << endl;
}
return 0;
}
我希望它输出“0, 1, 2, 3”然后停止,但它输出了一个无穷无尽的系列“0, 1, 2, 3, 4, 5, .....”
看起来比较 di<4
效果不佳并且总是 returns 正确。
如果我只是注释掉 ,delta=mc[di]
,我会正常得到“0、1、2、3”。无辜的分配有什么问题?
我正在使用 Ideone.com g++ C++14 和 -O2 选项。
由于在使用它索引 mc
之前递增 di
,第四次通过循环时,您将引用 mc[4],它已超出数组的末尾,这反过来可能会导致麻烦的行为。
这是由于未定义的行为,您在循环的最后一次迭代中越界访问数组 mc
。一些编译器可能会围绕没有未定义行为的假设执行积极的循环优化。逻辑类似于以下内容:
- 越界访问
mc
是未定义的行为
- 假设没有未定义的行为
- 因此
di < 4
始终为真,否则 mc[di]
将调用未定义的行为
打开优化并使用 -fno-aggressive-loop-optimizations
标志的 gcc 会导致无限循环行为消失(see it live). While a live example with optimization but without -fno-aggressive-loop-optimizations 表现出您观察到的无限循环行为。
A godbolt live example of the code 显示 di < 4
检查已删除并替换为无条件 jmp:
jmp .L6
这与 GCC pre-4.8 Breaks Broken SPEC 2006 Benchmarks. The comments to this article are excellent and well worth the read. It notes that clang caught the case in the article using -fsanitize=undefined
which I can not reproduce for this case but gcc using -fsanitize=undefined
does (see it live). Probably the most infamous bug around an optimizer making an inference around undefined behavior is the Linux kernel null pointer check removal 中概述的情况几乎相同。
虽然这是一个积极的优化,但重要的是要注意,正如 C++ 标准所说的未定义行为是:
behavior for which this International Standard imposes no requirements
这基本上意味着一切皆有可能,它指出(强调我的):
[...]Permissible undefined behavior
ranges from ignoring the situation completely with unpredictable results, to behaving during translation or
program execution in a documented manner characteristic of the environment (with or without the issuance of
a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).[...]
为了从 gcc 得到警告,我们需要将 cout
移到循环外,然后我们会看到以下警告 (see it live) :
warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
for(di=0; di<4;di++,delta=mc[di]){ }
^
这可能足以为 OP 提供足够的信息来弄清楚发生了什么。像这样的不一致是我们在未定义行为中可以看到的典型行为类型。为了更好地理解为什么面对未定义的行为时这种警告会不一致 Why can't you warn when optimizing based on undefined behavior? 是一本好书。
注意,-fno-aggressive-loop-optimizations
记录在 gcc 4.8 release notes。
你有这个:
for(int di=0; di<4; di++, delta=mc[di]) {
cout<<di<<endl;
}
试试这个:
for(int di=0; di<4; delta=mc[di++]) {
cout<<di<<endl;
}
编辑:
为了弄清楚发生了什么,让我们分解一下 For 循环的迭代:
第 1 次迭代:最初 di 设置为 0。
比较检查:di 是否小于 4?是的,可以继续。
将 di 增加 1。现在 di = 1。获取 mc[] 的 "nth" 元素并将其设置为 delta。这次我们获取第二个元素,因为这个索引值是 1 而不是 0。最后在 for 循环中执行代码 block/s。
第二次迭代:现在 di 设置为 1。
比较检查:di 是否小于 4?是的,继续。
将 di 增加 1。现在 di = 2。获取 mc[] 的 "nth" 元素并将其设置为 delta。这次我们抓取第 3 个元素,因为这个索引值为 2。最后在 for 循环中执行代码 block/s。
第三次迭代:现在 di 设置为 2。
比较检查:di 是否小于 4?是的,继续。
将 di 增加 1。现在 di = 3。获取 mc[] 的 "nth" 元素并将其设置为 delta。这次我们获取第 4 个元素,因为这个索引值为 3。
最后在for循环中执行代码block/s。
第 4 次迭代:现在 di 设置为 3。
比较检查:di 是否小于 4?是的,继续。
将 di 增加 1。现在 di = 4。(你能看出这是怎么回事吗?)抓住 mc[] 的 "nth" 元素并将其设置为 delta。这次我们要获取第 5 个元素,因为这个索引值为 4。呃哦,我们有问题;我们的数组大小只有 4。Delta 现在有垃圾,这是未定义的行为或损坏。最后使用 "garbage delta" 在 for 循环中执行代码 block/s。
第 5 次迭代。现在 di 设置为 4。
比较检查:di 是否小于 4?不,跳出循环。
由于超出连续内存(数组)的界限而损坏。
因为di++是在循环的最后运行处执行的。
例如;
int di = 0;
for(; di < 4; di++);
// after the loop di == 4
// (inside the loop we see 0,1,2,3)
// (inside the for statement, after di++, we see 1,2,3,4)
当 di == 4 时您正在访问 mc[],因此这是一个越界问题,可能会破坏堆栈的一部分并破坏变量 di。
一个解决方案是:
for(int di = 0; di < 4; di++) {
cout << di << endl;
delta = mc[di];
}
我有以下代码:
#include <iostream>
#include <complex>
using namespace std;
int main() {
complex<int> delta;
complex<int> mc[4] = {0};
for(int di = 0; di < 4; di++, delta = mc[di]) {
cout << di << endl;
}
return 0;
}
我希望它输出“0, 1, 2, 3”然后停止,但它输出了一个无穷无尽的系列“0, 1, 2, 3, 4, 5, .....”
看起来比较 di<4
效果不佳并且总是 returns 正确。
如果我只是注释掉 ,delta=mc[di]
,我会正常得到“0、1、2、3”。无辜的分配有什么问题?
我正在使用 Ideone.com g++ C++14 和 -O2 选项。
由于在使用它索引 mc
之前递增 di
,第四次通过循环时,您将引用 mc[4],它已超出数组的末尾,这反过来可能会导致麻烦的行为。
这是由于未定义的行为,您在循环的最后一次迭代中越界访问数组 mc
。一些编译器可能会围绕没有未定义行为的假设执行积极的循环优化。逻辑类似于以下内容:
- 越界访问
mc
是未定义的行为 - 假设没有未定义的行为
- 因此
di < 4
始终为真,否则mc[di]
将调用未定义的行为
打开优化并使用 -fno-aggressive-loop-optimizations
标志的 gcc 会导致无限循环行为消失(see it live). While a live example with optimization but without -fno-aggressive-loop-optimizations 表现出您观察到的无限循环行为。
A godbolt live example of the code 显示 di < 4
检查已删除并替换为无条件 jmp:
jmp .L6
这与 GCC pre-4.8 Breaks Broken SPEC 2006 Benchmarks. The comments to this article are excellent and well worth the read. It notes that clang caught the case in the article using -fsanitize=undefined
which I can not reproduce for this case but gcc using -fsanitize=undefined
does (see it live). Probably the most infamous bug around an optimizer making an inference around undefined behavior is the Linux kernel null pointer check removal 中概述的情况几乎相同。
虽然这是一个积极的优化,但重要的是要注意,正如 C++ 标准所说的未定义行为是:
behavior for which this International Standard imposes no requirements
这基本上意味着一切皆有可能,它指出(强调我的):
[...]Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).[...]
为了从 gcc 得到警告,我们需要将 cout
移到循环外,然后我们会看到以下警告 (see it live) :
warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
for(di=0; di<4;di++,delta=mc[di]){ }
^
这可能足以为 OP 提供足够的信息来弄清楚发生了什么。像这样的不一致是我们在未定义行为中可以看到的典型行为类型。为了更好地理解为什么面对未定义的行为时这种警告会不一致 Why can't you warn when optimizing based on undefined behavior? 是一本好书。
注意,-fno-aggressive-loop-optimizations
记录在 gcc 4.8 release notes。
你有这个:
for(int di=0; di<4; di++, delta=mc[di]) {
cout<<di<<endl;
}
试试这个:
for(int di=0; di<4; delta=mc[di++]) {
cout<<di<<endl;
}
编辑:
为了弄清楚发生了什么,让我们分解一下 For 循环的迭代:
第 1 次迭代:最初 di 设置为 0。 比较检查:di 是否小于 4?是的,可以继续。 将 di 增加 1。现在 di = 1。获取 mc[] 的 "nth" 元素并将其设置为 delta。这次我们获取第二个元素,因为这个索引值是 1 而不是 0。最后在 for 循环中执行代码 block/s。
第二次迭代:现在 di 设置为 1。 比较检查:di 是否小于 4?是的,继续。 将 di 增加 1。现在 di = 2。获取 mc[] 的 "nth" 元素并将其设置为 delta。这次我们抓取第 3 个元素,因为这个索引值为 2。最后在 for 循环中执行代码 block/s。
第三次迭代:现在 di 设置为 2。 比较检查:di 是否小于 4?是的,继续。 将 di 增加 1。现在 di = 3。获取 mc[] 的 "nth" 元素并将其设置为 delta。这次我们获取第 4 个元素,因为这个索引值为 3。 最后在for循环中执行代码block/s。
第 4 次迭代:现在 di 设置为 3。 比较检查:di 是否小于 4?是的,继续。 将 di 增加 1。现在 di = 4。(你能看出这是怎么回事吗?)抓住 mc[] 的 "nth" 元素并将其设置为 delta。这次我们要获取第 5 个元素,因为这个索引值为 4。呃哦,我们有问题;我们的数组大小只有 4。Delta 现在有垃圾,这是未定义的行为或损坏。最后使用 "garbage delta" 在 for 循环中执行代码 block/s。
第 5 次迭代。现在 di 设置为 4。 比较检查:di 是否小于 4?不,跳出循环。
由于超出连续内存(数组)的界限而损坏。
因为di++是在循环的最后运行处执行的。
例如;
int di = 0;
for(; di < 4; di++);
// after the loop di == 4
// (inside the loop we see 0,1,2,3)
// (inside the for statement, after di++, we see 1,2,3,4)
当 di == 4 时您正在访问 mc[],因此这是一个越界问题,可能会破坏堆栈的一部分并破坏变量 di。
一个解决方案是:
for(int di = 0; di < 4; di++) {
cout << di << endl;
delta = mc[di];
}