4

我很困惑N2346::6.5.2.5/15N2346::6.5.2.5/16哪些州(雇员。我的)

15 示例 8 每个复合文字只在给定范围内创建一个对象

struct s { int i; };
int f (void)
{
    struct s *p = 0, *q;
    int j = 0;
    again:
        q = p, p = &((struct s){ j++ });
        if (j < 2) goto again;
    return p == q && q->i == 1;
}

函数 f() 总是返回值 1。

16 请注意,如果使用迭代语句而不是显式 goto 和标记语句,则未命名对象的生命周期将仅是循环的主体,并且在下次进入时 p 将具有不确定的值,这将导致在 未定义的行为中。

在我看来,这句话与标准的另一部分相矛盾。恰恰:

N2346::6.5.2.5/5

如果复合文字出现在函数体之外,则该对象具有静态存储持续时间;否则,它具有与封闭块关联的自动存储持续时间。

这意味着使用复合文字创建的块范围对象具有自动存储持续时间。

N2346::6.8/3(雇员我的):

具有自动存储持续时间的对象的初始值设定项,以及具有块范围的普通标识符的可变长度数组声明符,每次声明时都会被评估并将值存储在对象s 中(包括在没有初始值设定项的对象中存储不确定值)是按 执行顺序到达的,就好像它是一个语句,并且在每个声明中按照声明符出现的顺序。

因此,即使goto示例中的语句N2346::6.5.2.5/15被替换为迭代语句,复合文字创建的对象也应该在每次到达时重新创建。

问题:为什么用goto迭代语句替换会产生 UB?我的推理有什么问题?

4

1 回答 1

4

即使 N2346::6.5.2.5/15 示例中的 goto 语句被替换为迭代语句,复合文字创建的对象每次到达时都应重新创建。

你是对的 - 但重要的一点是块的结束标志着对象存储持续时间的结束。未定义的行为q = p在第二次迭代中触发,当p不再有效时,并且还在return迭代语句之外的行上。


更具体地说,该标准暗指这样的代码:

struct s { int i; };
int f (void)
{
    struct s *p = 0, *q;
    int j = 0;
    for (j = 0; j < 2; j++)
    {
        q = p; // p is invalid in the second iteration
        p = &((struct s){ j++ });
    } // end of block - p is no longer valid!

    // p points to an object whose storage duration has expired, and so this is undefined behavior
    return p == q && q->i == 1;
}

您可以看到最后return一条语句引用了一个对象,该对象的存储期限在for块末尾到期,并且该q变量被分配给在第二次迭代中未定义的指针。

goto的用法和像循环这样的迭代语句之间的定义区别在于,for在循环内创建的对象for在循环范围内有效。

于 2021-09-06T06:51:59.053 回答