2

我的问题与 C99/GNU-C 和 C++ 中堆栈变量的生命周期有关,当 goto 越过它们时。这里有许多相关的问题,但没有一个真正回答了我的情况。考虑以下代码示例:

void Foo(char *ptr)
{
label1:
    if (ptr)
    {
        char string1[50];
        strcpy(string1, ptr);
        strupr(string1);
        printf("upcased string = %s\n", string1);
        return;
    }

#if CASE_1
    char string2[50] = "test";
#else
    char string2[50];
    strcpy(string2, "test");
#endif
    ptr = string2;
    goto label1;
}

我读到 goto 不会引入新的范围,因此即使在声明变量之前,该变量也应该是可访问的(理论上)。string2存在于函数范围内,但不能从声明之前的代码直接访问。另一方面,使用 goto 和指针变量可以访问它。我知道当 goto 向后越过对象初始化时,C++ 需要调用析构函数,但我没有找到有关内置/POD 类型生命周期的任何信息。使用 GCC 进行的测试表明,虽然编译器在ptr未分配给string2时重用堆栈空间,但它会在分配完成后停止重用它,就好像它“知道”它可以在 goto 之后被寻址一样。

C99/C++ 标准中是否有任何规则(甚至可能仅限于 GCC)明确说明是否允许这样做?我对 C++ 特别感兴趣。

编辑:

  • c++ 标准中处理它的部分是"3.7.3-1 Block-scope variables explicitly declared register or not explicitly declared static or extern have automatic storage duration. The storage for these entities lasts until the block in which they are created exits."虽然这似乎证明了上面的代码是正确的,但事实并非如此,因为很明显,当编译器知道它时,它会将堆栈空间重用于自动变量作为优化将不再使用。所以需要回答的问题是:是否允许编译器假设一个变量在声明之前没有在该位置使用,即使程序流会携带一个引用?
  • 我添加了一个替代案例,它似乎有不同的规则。
  • 为了回答任何问题,为什么我首先要使用这样一个丑陋的结构:这当然不是我想要编写普通代码的方式。它应该是兼容性宏的一部分,以允许在 G++ 中使用结构化异常处理
4

2 回答 2

1

虽然行为在 C++ 中未定义,但正如Andy Prowl回答告诉您的那样,在 C 中,行为已定义,6.2.4 的第 6 段(N1570,与 C99 中的第 5 段相同)指定具有自动存储持续时间的对象的生命周期没有可变长度数组类型:

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

的生命周期string2是块执行的整个时间,因此在第一次初始化之后if在分支中访问它会找到具有确定内容的对象。ptr

于 2013-05-26T14:24:01.570 回答
1

关于 C++,C++11 标准的第 6.6/1 段规定:

[...] 转移出循环、转移出块或返回具有自动存储持续时间的初始化变量涉及销毁具有自动存储持续时间的对象,这些对象在转移点的范围内但不在转移点的范围内到. [...]

然后第 3.7.3/3 段规定:

如果具有自动存储持续时间的变量具有初始化或具有副作用的析构函数,则不应在其块结束之前将其销毁,即使看起来未使用也不应作为优化消除,除非是类对象或其复制/移动可以按照 12.8 中的规定消除。

由于string2进行了初始化,因此程序具有未定义的行为。

这就是说,goto当你可以使用结构化编程时,为什么还要使用呢?Dijkstra 很久以前就告诉我们这goto是有害的

于 2013-05-26T13:54:38.367 回答