for循环中的奇怪行为-一个bug?

4

我正在使用Windows 7机器上的Visual Studio 2012,在尝试运行以下代码片段时(使用默认的VC11 C ++编译器以x64模式编译),断言失败,这意味着内部循环从未执行:

void loopTest1()
{
    const unsigned int k = 1;

    for (int m=0; m<3; ++m)
    {
        int acc = 0;

        for (int n=m-k; n<=m+k; ++n)
        {
            if (n<0 || n>=3) continue;

            ++acc;
        }

        assert (acc>0);

        cout << "acc: " << acc << endl;
    }
}

现在我改变内部循环的结束条件:

void loopTest2()
{
    const unsigned int k = 1;

    for (int m=0; m<3; ++m)
    {
        int acc = 0;

        int l = m+k; // this line was added
        for (int n=m-k; n<=l; ++n) // m+k was replaced by l
        {
            if (n<0 || n>=3) continue;

            ++acc;
        }

        assert (acc>0);

        cout << "acc: " << acc << endl;
    }
}

然后我得到了正确的结果:

acc: 2
acc: 3
acc: 2

当我把const unsigned int k替换为硬编码的1时,它也可以工作:

void loopTest3()
{
    //const unsigned int k = 1;

    for (int m=0; m<3; ++m)
    {
        int acc = 0;

        for (int n=m-1; n<=m+1; ++n) //replaced k with 1
        {
            if (n<0 || n>=3) continue;

            ++acc;
        }

        assert (acc>0);

        cout << "acc: " << acc << endl;
    }
}

编译器是否进行了一些错误的优化? 或者有没有特定的原因,导致第一种情况的行为至少是出人意料的?


2
你可以尝试检查反汇编以查看生成的代码。 - sharptooth
6
编译器可能会发出关于无符号/有符号不匹配的警告,因为您从有符号整数m中减去了无符号整数k - 也许修复这个问题就能解决了。 - Niko
@Niko,应该可以的。 - chris
Niko,我刚刚使用int k = 1进行了测试,它起作用了!你能否把你的评论转化成一篇回答? - Ben
在函数开头设置一个断点,一旦触发它,调用Debug->Windows->Disassembly。 - sharptooth
1个回答

3
您的int m将会被提升为一个unsigned int。在第一次循环中,这意味着m-k作为无符号值等于-1,即最大无符号值,并且显然大于m + k(当比较n时,它会被提升)。简单来说,您最终会得到n作为-1的无符号表示,m+k作为1。当然,将这个-1无符号存储到有符号整数中时,它会溢出并且在技术上是未定义行为。它很可能保持其-1表示,然后再次被提升为最大无符号值。
以下是第一次迭代的摘要:
迭代 1: m: 0 k: 1u n=m-k: -1u = max uint,存储在已签名整数中 m+k: 1u n<=m+k --> max uint <= 1u
在您的第二个示例中,与另一个已签名整数相比时,不会提升n的值,并且它会比较两个已签名整数。在第三个示例中,没有使用无符号数。

那么如果我想保持k为无符号,我就必须在for循环中写int n = m - static_cast<int>(k),对吗? - Ben
@Ben,如果你想坚持第一种方法,那似乎是最好的选择,但你还需要 n <= m + static_cast<int>(k),这样 n 就不会在那里被提升为无符号整数。如果你想避免未定义的行为并减少循环头的大小,你也可以采用第二种方法,并制作一个临时的 int 来保存 m - static_cast<int>(k)。关于有符号溢出的问题是,在几乎所有情况下,它们只会绕过并不会引起任何奇怪的行为,所以你可以自己决定是否要远离它。只需记住它是正式未定义的。 - chris
是的,我也刚意识到。谢谢你那个好答案!我完全没有看到这个问题,因为我期望 k 被提升为一个 int - Ben
@Ben,如果你没有预料到C++中的促销和标准算术转换,它们可能会对你造成很大的困扰。STL实际上有一个视频讨论了由此引起的MSVC中的一个错误。这是他的Core C++系列中的第7个视频。 - chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接