【问题标题】:What's the meaning of "block is entered recursively"?“递归输入块”是什么意思?
【发布时间】:2020-11-22 13:43:11
【问题描述】:

我在 reddit 上问了一个问题。这个问题实际上是Why does the starting point of a lifetime of an automatic object(which doesn't have VLA) precede a scope of an object?
我得到的答案可以作为我的问题的例子。

代码

#include <stdio.h>

int main(void) {
    int i = 0;
back:
    printf("i = %d", i);
    int j;
    if (i < 5) {
        if (i == 0)
            j = 0;
        j += i;
        printf(", j = %d\n", j);
        i++;
        goto back;
    }
    printf("\n");
}

输出

i = 0, j = 0
i = 1, j = 1
i = 2, j = 3
i = 3, j = 6
i = 4, j = 10
i = 5

但是有人说“j 的值在达到其声明时变得不确定,......”因为 C17 6.2.4p6(用我的粗体字)

对于这样一个没有变长数组类型的对象,它的生命周期从入口开始 进入与其关联的块,直到该块的执行以任何方式结束。 (输入一个 封闭块或调用函数会挂起,但不会结束当前块的执行。)如果 递归进入块,每次都会创建一个新的对象实例。 对象的初始值是不确定的。如果为对象指定了初始化,则每次在执行块时到达声明或复合文字时都会执行它;否则,每次达到声明时,值都会变得不确定。

仅转到当前块中的标签不同于在递归函数或迭代语句中重复整个关联块。从这个角度来看,我不认为j 不受规则影响(但这只是我的想法)。

顺便说一句,我认为无论我们以哪种方式思考,都存在矛盾。
- 如果int j; 没有在每个序列中重新创建,则它是重新定义。
- 如果int j; 在每个序列中重新创建,j 将具有来自第二个序列的垃圾值。但输出显示为好像保留了“j”的值。

我一遍又一遍地阅读引用的部分文档,但无法理解。我错过了什么?

【问题讨论】:

  • goto 不是递归,关于递归的部分在这里不适用。
  • 比递归更重复
  • 你只是好奇吗?因为像这样使用 goto for 循环从来都不是很好的理由。
  • 你怎么知道它不是垃圾值?一个常见的垃圾值是“之前的任何东西”
  • 重新“如果 int j;不是在每个序列中重新创建,而是重新定义”:不,声明不是像语句一样执行的代码。如果声明存在于代码中的某个点,它会告诉编译器关于那里的标识符的一些信息。这并不意味着每次程序执行到达声明出现的位置时,编译器都会生成“定义”对象的代码。声明可能有某些方面导致可执行代码,例如自动对象的初始化,编译器会为此生成代码……

标签: c scope language-lawyer storage-duration


【解决方案1】:

重要的部分在后面:

如果为对象指定了初始化,[...];否则,每次达到声明时,值都会变得不确定。

每次达到int jj 的值就变得不确定。 j 保留以前的值也是垃圾值的一个例子,因为它可以是 any 值。如果您的代码依赖于该值,则意味着您的代码的行为未定义。

如果递归进入块,每次都会创建一个新的对象实例

“块”通常以{ 开头并以} 结尾。该段用于递归函数。

void func()
{ // block start
    int a; // new instance of object created each time, even recursively
    func(); // enter the block recursively
} // block end

您的代码中有一个块是用int main(void) { 创建的,而您的代码中的另一个块是if (i &lt; 5) { 之后的块。 if (i &lt; 5) {不是递归进入的,它是进入,然后离开,然后再次进入,然后再次离开。

如果int j;不是在每个序列中重新创建,而是重新定义。

引用那句话:

对于这样一个没有可变长度数组类型的对象[具有自动存储持续时间的对象,参见标准中的前一点],它的生命周期从进入与其关联的块直到该块的执行以任何方式结束。

j 标识的对象的生命周期从进入块开始,所以我认为它不是“重新创建”。

int main(void) { // lifetime of "object j" starts here
                 // but you can't access it, no identifier is associated with it
back:;
    int j; // the identifier `j` is associated with "object j"
           // the value of the object associated with `j` is indeterminate
           // _each time_ execution flow gets here
    goto back;
} // lifetime of "object j" ends here (or at `return`)
  // scope of identifier `j` also ends here

【讨论】:

  • 在您的第一句话中,“否则”与“如果递归输入块并且如果为对象指定初始化”您否认“如果递归输入块”(我同意这一点,你的解释it's entered, then leaved, then entered again很容易理解)。那你就不能用第一个引号来解释int j的价值了。
  • “否则”,如未为对象指定初始化时。该块不是递归输入的。如果将递归输入块,则可以。我不明白Then you can't explain the value of int j with the first quote?你的程序的行为是未定义的——nasal demons 可以产生。对于未定义的代码的任何行为都没有解释,因为......它是未定义的。
  • “就像没有为对象指定初始化时一样”,这适用于前一句“如果递归进入块”为真时。不是吗? If A { if A-1 else A-2} A = "如果递归进入块"
  • 我不关注,不是if A { ...},是if that, do that. If that, do that.,是不相关条件的列表。该函数可能会或可能不会递归输入 - 最后一句无论如何都适用。句子之间有一个句号。
  • 与下面的段落(6.2.4p7)相比,我承认你是对的。我为不必要的争论道歉。感谢这一点变得清晰。现在我同意int j 是情况otherwise, the value becomes indeterminate each time the declaration is reached. 剩下一点,问题是int j 被声明了不止一次。您同意 Eric 的评论并说“定义是一种宣言”。但这并没有用你和埃里克的评论来解释。 int main(){ int a=1; int a;}我的 gcc 不允许这样做。为什么第二个声明int a不是第一个声明?
猜你喜欢
  • 2021-08-01
  • 1970-01-01
  • 2018-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-28
  • 2018-09-19
  • 2016-12-13
相关资源
最近更新 更多