C程序有趣的行为表现

6
我发现了一个生成有趣结果的代码片段,当我在调试别人的程序时。为了说明这种行为,我编写了一个小程序:
#include <stdio.h>

int main()
{
        char* word = "foobar"; int i, iterator = 0;
        for (i = 0; i < 6; i++ && iterator++)
          printf("%c", word[iterator]);
        return 0;
}

我知道这不是打印字符串的正确方法,这只是为了演示目的。

在这里,我期望的输出是“foobar”,但实际输出却是“ffooba”。基本上它会将第一个字符读取两次,好像第一次执行iterator++时什么也没发生。

有人能解释一下为什么会出现这种情况吗?


6
i++ && iterator++ -> i++, iterator++ - Mysticial
2
这个问题可能很基础,但是提问得非常完美。 - Flexo
就像我说的那样,我知道如何正确地做,我只是想弄清楚为什么上面的代码没有按照我预期的方式工作。 - Ionut Hulub
4个回答

11

问题在于 iterator++ 实际上并没有在第一次执行。 ++ 操作符返回一个变量的当前值,然后增加它,所以第一次循环时,i++ 的值将等于 0&& 会短路,所以第一次不会执行 iterator++

要解决这个问题,可以使用逗号运算符来无条件地同时执行两个操作,而不是使用短路的 && 运算符。


很有趣,我知道&&运算符如果第一个表达式已经为false,则不会评估第二个部分,但我没有预料到它适用于循环语句的条件部分之外的位置。谢谢你的答案。10分钟后我会批准它。 - Ionut Hulub
您可以了解更多关于短路评估的内容。对于&&、||等,有各种规则。 - fkl

5
i++ 的结果是当前的值,第一次迭代时为零。这意味着由于短路(只有左侧为“true”时才执行右侧),iterator++ 在第一次迭代时不会被执行。
要修复该问题,您可以使用逗号运算符(如已建议)或使用++i,它将在增量后返回i的值(尽管逗号运算符更明显,因为必须始终评估两者)。

3
你真的应该学习使用调试器,例如 gdb,并使用警告和调试信息编译,例如 gcc -Wall -g(假设是 Linux 系统)。使用最近版本的 gcc-Wall 可以让你在 && 操作之前得到计算值未使用的警告。
for 循环的增量部分有点奇怪。它是 i++ && iterator++ (但应该改成 i++, iterator++)。
i 为 0 时(第一次迭代),i++ 的结果为 0,因此为 false,所以 iterator++ 不会被执行。

我长期使用Visual Studio,但最近转向Linux,我还没有学会如何使用控制台调试器,但我会研究一下的。 - Ionut Hulub
1
故事的寓意是始终向gcc传递“-Wall”,并改进代码直到没有警告。你甚至可以向gcc传递“-Wall -Wextra -g”。 - Basile Starynkevitch
@lonut Hulub,你仍然可以在Linux中使用Eclipse进行图形调试。然而,gdb(控制台调试器)仍然是一项有价值的技能,特别是当你在没有UI的平台上工作时。 - fkl
据我所知,Eclipse C调试器只是gdb的GUI界面(就像ddd一样)。因此学习gdb仍然是有用的。 - Basile Starynkevitch

2

我正在阅读关于逻辑运算符的K&R书籍,让我引用原文来解释您的问题。

"由&&或||连接的表达式从左到右进行评估,并且一旦结果的真实性或虚假性已知,则停止评估。"

对这些内容有很好的理解,输出将不会令人困惑。


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