【问题标题】:Recursion and static variables [duplicate]递归和静态变量
【发布时间】:2018-01-08 18:55:07
【问题描述】:

所以当我在印度准备入学考试时,我偶然发现了这段 C 代码

#include <stdio.h>
int main(void) {
    static int i = 4;
    if (--i) {
        main();
        printf("%d", i);
    }
}

我认为printf 语句永远不会被执行并且输出将是空白的。 但我看到答案是0000,这是由于static 关键字和int 造成的。

谁能解释为什么 printf 会执行或者那个人错了?

【问题讨论】:

  • 调用 main 会导致未定义的行为,至少在 C++ 中是这样。如果需要,此代码可能会使您的机器着火。
  • 我删除了 C++ 标签,因为您的代码是 C++ C。并且由于您的代码使用 printf,而您说它是 C,因此 C++ 标签是错了。
  • 在C中是允许的
  • if (--i) 在测试前递减 i,如果 i 不为零,则进入块内并调用 main,这在 C 中是合法的。进入第二个(以及随后的每个时间)main ,静态变量 i 没有重新初始化。所以,第二次真的是3。一旦 i 一直递减到零,if 测试返回 false,并且块被跳过并且最深递归的 main 返回。这就是每个printf 语句被执行的时候。
  • Unexpected output of the main() function by repeated calls 非常相似。打印不一样,而且还打印了一个额外的局部变量,但是……

标签: c recursion


【解决方案1】:

在 C* 中递归 main 是合法的(尽管你为什么要?)

此外,C11 (5.1.2.2.1) 规定:

应使用int 的返回类型定义... [或] 如果返回类型与int 不兼容,则 返回宿主环境的终止状态未指定。

(C++ 规定返回类型必须是int

所以这部分代码实际上是符合标准的,但实际返回是实现定义的(感谢@chux)

谁能解释为什么 printf 会执行或者那个人错了?

如果你用 C++ 编译器编译 C,那你就错了。否则代码将编译。

现在已经不碍事了,printf 确实会执行,因为根据 6.2.4/3,static 变量只初始化一次

一个对象,其标识符被声明为 [...] 存储类说明符static 具有静态存储持续时间。它的生命周期是整个 程序的执行及其存储的值仅在程序之前初始化一次 启动

*这在 C++ 中绝对是不合法的。来自 [basic.start.main]: “函数 main 不得在程序中使用”。这个答案中有很多 C++ 旁白,因为该问题最初被标记为 C++ 并且信息很有用

【讨论】:

    【解决方案2】:

    这段代码存在多个问题:

    • &lt;stdio.h&gt; 不包括在内,调用printf("%d", i); 具有未定义的行为。

    • 不带参数的main 的原型是int main(void)。将main 定义为void main() 在某些平台上是不可移植且不正确的。始终使用 main 的标准原型并返回有效的退出状态以获得良好的风格。

    • 在 C 中递归调用 main 函数是一种不好的风格,并且在 C++ 中具有未定义的行为。

    忽略未定义行为意味着任何事情都可能发生的事实,包括印度公司使用可怕的编程测试,程序打印0000 的原因可能是您错误输入了if (i--) 测试或错误放置了记忆中的大括号,或者这家伙是完全错误。 main 函数递归调用自身 3 次,每次递减相同的 i 变量,直到它变为 0 并且每个实例在返回给其调用者之前打印 i 的值,即 0,包括初始调用main()

    函数内部的static int i = 4; 定义了一个全局变量i,其初始值为4,只能在定义它的块内访问。 main 的所有递归实例都访问同一个变量。

    【讨论】:

    • 我看不出省略标题会如何导致调用 printf 未定义行为。在我看来,它需要一个编译错误(如果没有找到其他 printf 声明)。调用未声明的函数不是UB。
    • 我只是没有在这里添加 printf 只是因为我认为它是隐含的。让我编辑它
    • 调用未声明的函数用于为其进行隐式定义。 printf("%d", i); 将使用隐式声明为 int printf(char *, int);,这与实际定义不兼容。因此未定义的行为。
    • @FrançoisAndrieux:如果声明和代码在调用约定、参数数量、返回类型等方面不一致,那么你就有麻烦了。
    • “将 main 定义为 void main() 不正确。”根据 5.1.2.2.1 1 “或以其他一些实现定义的方式”是允许的。 ID行为也是如此。然而这是不必要的,因为int main() 毫无疑问是可以的——而且更短。
    【解决方案3】:

    从这个函数的质量和名称中抽象出来。

    所有static 变量在程序启动期间仅初始化一次,并保持其值。

    当启动例程第一次调用 main 时,变量 i 的值为 4。然后将其值减小到 3 并再次调用 main。这次i的值是3,那么你把它的值减到2……

    【讨论】:

    • 是的,但是因为我们在 printf 之前调用 main(),所以 printf 永远不会被正确执行。如果 printf 在 if 语句之外,可能就是这种情况。但是 printf 在 if 和 main() 调用之后
    【解决方案4】:

    在关于递归的问题的上下文中,它将打印出“000”(我不知道为什么答案显示 4 个零,因为在赋值之前会发生 '--i' 递减)。如果您展开您收到的电话:

    if (--i) { //i == 3
        if (--i) { //i == 2
            if (--i) { //i == 1
                if (--i) { //i == 0
                    // rest does not get evaluated since the condition is false
                }
                printf("%d", i); // i is 0 now
            }
            printf("%d", i); //i is still 0
        }
        printf("%d", i); //i is still 0
    }
    

    然而,就像大多数人提到的那样,这段代码很糟糕,我建议你永远不要考虑你编写的任何软件的这种草率。

    【讨论】:

    • 从技术上讲,这是他问题的答案,但我猜它已经消失了
    • 也许“答案”显示四个 0,因为他们不了解自己的“入学考试”... :v
    【解决方案5】:

    谁能解释为什么 printf 会执行或者那个人错了?

    因为main 返回。

    This answer 写出了逻辑,但我看到你还在 cmets 中问这个问题。所以我将以不同的方式说同样的事情,并希望它对你来说更清楚。

    因为变量是static,所以在第一次调用之前,变量被声明并设置为4。因为它是static,所以每次都会使用同一个变量。如果再次调用(或者甚至可能是第一次调用,因为可以在运行之前分配 static 变量),它只会跳过声明和初始化。

    在第一次调用中,变量递减为 3。3 是一个真值,所以我们执行if 的块。这会递归调用main

    在第二次调用中,变量递减为 2。2 在 C 中仍然是真值。我们再次递归调用 main

    在第三次调用中,变量减为 1。1 仍然是真值。我们再次递归到main

    在第四次调用中,变量减为 0。0 在 C 中为假。我们不输入if 的块。我们不递归。我们不打印。在此对main 的调用中没有什么可做的。它在最后返回。

    现在我们回到第三次通话。我们刚刚从对main 的电话中返回。我们仍在if 块内。下一条语句是printf。变量的值现在是 0。所以我们打印一个 0。没有什么可做的,我们在最后返回。

    现在我们回到第二次通话。仍然是 0,所以我们打印另一个 0 并返回。

    现在我们又回到了第一次通话中。仍然是 0,所以我们打印另一个 0 并返回。

    我们打印了 3 次 0,所以 000

    正如您所说,在下降过程中,我们不打印。我们在备份的路上打印。到那时,变量为0。我们进入if的then块3次。所以我们递归然后打印三遍。

    当你递归调用一个函数时,你并没有消除之前的调用。你把它放在堆栈上。完成后,您回到您离开的地方并继续下一条语句。

    您可能想考虑以下三段代码的不同反应:

    void recurse() {
        static int i = 4;
        if (--i) {
            recurse();
            printf("%d", i);
        }
    }
    

    第一次调用时打印 000。之后什么都不做。

    void recurse(int i) {
        if (--i) {
            recurse(i);
            printf("%d", i);
        }
    }
    

    如果调用为 recurse(4),将打印 123。作为一个参数,我们每次调用函数时都会处理不同的i

    void recurse(int i) {
        if (--i) {
            printf("%d", i);
            recurse(i);
        }
    }
    

    如果调用为 recurse(4),则打印 321。在下降的途中打印,而不是在回来时打印。

    void recurse(int i) {
        printf("%d", i);
        if (--i) {
            recurse(i);
        }
    }
    

    如果以 recurse(4) 调用,则打印 4321。它在向下打印,而不是向上打印。

    void recurse() {
        static int i = 4;
        printf("%d", i);
        if (--i) {
            recurse();
        }
    }
    

    将打印 4321。我们正在向下打印,而不是向上打印。请注意参数和static 变量如何以这种方式给出相同的结果。但是,如果这被第二次调用,它不会做任何事情。该参数将再次打印相同的内容。

    void recurse() {
        int i = 4;
        if (--i) {
            recurse();
            printf("%d", i);
        }
    }
    

    永远循环。如果没有static,我们每次都会创建一个新的i。这将一直运行,直到它使堆栈过载并崩溃。没有输出,因为它永远不会到达printf

    检查这一点的最简单方法是去 ideone.com 之类的地方并运行代码。询问为什么它打印某些东西对这个网站来说是合理的。但是问它打印的是什么,你应该自己回答。

    【讨论】:

    • 谢谢你。这清除了一切
    • 请注意:在第一个块中,在第二次运行时,确实发生了一些事情。那东西可能会炸毁堆栈。 (如果i 为0,--i 为-1。)
    猜你喜欢
    • 2020-06-18
    • 2014-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-22
    • 2017-08-01
    相关资源
    最近更新 更多