使用goto传递POD堆栈变量时的作用域和生命周期

Scope and lifetime of POD stack variables when passing over it with a goto

本文关键字:作用域 生命 周期 变量 goto 传递 POD 堆栈 使用      更新时间:2023-10-16

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

void Foo(char *ptr)
{
label1:
    if (ptr)
    {
        char string1[50];
        strcpy(string1, ptr);
        strupr(string1);
        printf("upcased string = %sn", string1);
        return;
    }
#if CASE_1
    char string2[50] = "test";
#else
    char string2[50];
    strcpy(string2, "test");
#endif
    ptr = string2;
    goto label1;
}

我读到goto不会引入新的作用域,因此,即使在声明变量之前,变量也应该是可访问的(理论上)。string2存在于函数作用域中,但不能从声明之前的代码直接访问。另一方面,使用goto和指针变量,它可以被访问。我知道c++要求在goto与对象初始化交叉时调用析构函数,但我没有发现任何关于内置/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++
  • 中使用结构化异常处理。

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

[…转出循环,转出块,或返回经过初始化的变量自动存储持续时间涉及销毁具有自动存储持续时间的对象作用域在从转移的点,而不是转移到的点。[…]

第3.7.3/3段则规定:

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

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

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

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

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

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