【问题标题】:Is a variable declared in a loop body preserved during iterations?循环体中声明的变量是否在迭代期间保留?
【发布时间】:2012-07-23 22:53:43
【问题描述】:

考虑 C 中的一个循环,它在循环体中声明一个字符数组。在每次迭代中,数组的一个字符都会被修改,直到到达末尾。最后,打印变量。描述将扩展到下一个代码:

#include <stdio.h>

int main(void) {
    int i = 0;
    for (;;) {/* same as: while(1) { */
        char x[5];
        x[i] = '0' + i;
        if (++i == 4) {
            x[i] = '\0'; /* terminate string with null byte */
            printf("%s\n", x);
            break;
        }
    }
    return 0;

许多人可能期望0123 作为输出。但由于某种原因,GCC 4.7 在启用优化(-O1 和更高版本)进行编译时不会这样做。而是将随机数据放在字符数组的第一个字节中,变为:

| 0  |  1  |  2  |  3  |  4   |
|     RANDOM     | '3' | '\0' |

我认为从语言的角度来看这是合乎逻辑的行为:块终止后自动变量消失了,因此应该预料到上述“随机”行为。

正确的行为应该是什么? 我知道将x 的声明移到循环之外“修复”它,但这并没有说明这个sn-p 的行为。现实世界的问题是bug in Netfilter。

【问题讨论】:

  • x[i] = '0' + i;当循环第一次运行时,这一行会发生什么?因为此时 i = 0 并且执行的等效行是 x[0] = '0' + 0。所以我们想将 00 存储在 x[0] 中。我很久以前读过 C,所以不记得字符数组是如何工作的。你能解释一下吗?
  • @Rain '0' + 0 只是 '0','0' + 2 是 '2',那部分很好。由于范围有问题,只是数组的未指定内容。
  • 感谢您的澄清。那么很明显,我们将为第一个 3 个字符设置随机值,因为在最后一次迭代中,将执行的等效行是 x[3] = 3,我们没有为 x[0]、x[1] 和 x[ 2]。因此,我们将始终如您在问题中所说的那样得到结果。
  • @Rain 邪恶的部分是结果很可能确实是一个数组{ '0', '1', '2', '3', '\0' } 打印。编译器在每次迭代时将数组放在同一个位置是很自然的,既然它没有初始化,为什么它不应该包含之前迭代中写入的数据呢?我为所有优化级别打印了 0123。
  • :) 是的,由于编译器的帮助,您正在获得输出。但是当我们不分配任何值时,我们应该得到垃圾。但正确的做法是在循环外声明数组。

标签: c loops scope variable-declaration


【解决方案1】:

由于数组是在循环体的范围内声明的,因此您可以将其视为在每次循环迭代的自动存储区域中分配的一个新数组。该未初始化数组的内容是未定义的,除了您在当前迭代期间分配的索引处的字符,因此您看到的值是不确定的:

C99 标准,第 6.7.8 节:如果具有自动存储持续时间的对象未显式初始化,则其值不确定

当优化关闭时,数组落在自动存储中的同一位置,所以你的程序很幸运;但是,这绝不是保证。

将数组移到循环外,如下所示:

int i = 0;
char x[5];
for (;;) {
    x[i] = '0' + i;
    if (++i == 4) {
        x[i] = '\0'; /* terminate string with null byte */
        printf("%s\n", x);
        break;
    }
}

【讨论】:

  • 不,内容不是未定义的,这不是未定义的行为,C 中的正确术语是不确定值。与未定义的行为相比,这只会给你垃圾值(char 很少有陷阱表示),但不会吃掉你的硬盘。
  • @JensGustedt 你说得对,我更正了答案并添加了对标准的引用。
【解决方案2】:

这是块范围的问题。循环体是一个块,用{} 表示,在每次循环迭代中进入和离开。每次输入块时,您都会(从概念上)获得一个新的x,其中包含未指定的内容。

由于您在调用printf 的迭代中只设置了x[3] = '3' 和x[4] = '\0',因此只有这两个数组成员具有明确定义的内容。其余的可以包含任何内容,包括'\0',因此printf 可以输出长度最多为2 的任何字符串,可以选择后跟任何其他字符和3。

【讨论】:

    【解决方案3】:

    这将按预期输出(随机字符数组),因为自动变量在声明的范围终止后被清理。要解决此问题,请将x 移出for 循环的范围。

    【讨论】:

    • 我看不出您的回答与他的问题有何关联。 printf 在 for 循环内。
    • 我认为理查德指的是观察到变量内容在迭代过程中发生变化。
    • @DavidSchwartz 是,但 x 不是。循环的每次迭代,都可以清理堆栈,在这种情况下,x 被释放。
    • x 不在for 循环内?
    • @DavidSchwartz 如我的问题所述,它位于 for 循环体中。
    【解决方案4】:

    每次迭代都会为数组分配内存,并且只有在最后一次迭代中获得的值。

    【讨论】:

    • 我看不出这如何回答我的问题,或者它如何为现有答案添加任何内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-04
    • 1970-01-01
    • 2015-04-24
    • 1970-01-01
    • 2020-08-03
    • 1970-01-01
    • 2014-12-01
    相关资源
    最近更新 更多