局部变量之前的GOTO

Pas*_*eli 24 c goto declaration undefined-behavior

以下代码是否构成未定义的行为,因为我在变量声明之前跳转并通过指针使用它?如果是这样,标准之间是否存在差异?

int main() {
  int *p = 0;
label1: 
  if (p) {
    printf("%d\n", *p);
    return 0;
  }
  int i = 999;
  p = &i;
  goto label1;
  return -1;
}
Run Code Online (Sandbox Code Playgroud)

oua*_*uah 16

程序中没有未定义的行为.

goto 语句有两个约束:

(c11,6.8.6.1p1)"goto语句中的标识符应命名位于封闭函数中某处的标签.goto语句不得从具有可变修改类型的标识符范围之外跳转到该范围内.标识符".

您没有违反,并且没有其他要求超出限制.

请注意,它在c99和c90中是相同的(在某种意义上没有额外的要求).当然在c90中,由于声明和声明的混合,该程序无效.

关于在语句i之后访问对象的生命周期goto,C表示(请参阅我的重点,以下段落中的其他复制句子对于更棘手的程序将是有趣的):

(c11,6.2.4p6)" 对于没有可变长度数组类型的对象,其生命周期从入口延伸到与之关联的块,直到该块的执行以任何方式结束. [...]如果以递归方式输入块,则每次都会创建一个新的对象实例.[...]如果为对象指定了初始化,则每次执行块时都会执行声明或复合文字. ;否则,每次达到声明时,该值就变得不确定."

这意味着,阅读i时仍然活着*p; 在其生命周期之外不访问任何对象.

  • @Kay,`i`的生命周期结束于`}`或返回函数. (4认同)
  • @MattMcNabb Derek M. Jones 的新 C 标准也提到了这句话*“使用 goto 语句跳回到块的递归开头,不是对该块的递归调用。”* (2认同)

Kei*_*son 8

我会尽力回答你可能想问的问题.

您的程序的行为已明确定义.(这return -1;是有问题的;只有0,EXIT_SUCCESS并且EXIT_FAILURE被定义为从中返回的值main.但这不是你要问的.)

这个程序:

#include <stdio.h>
int main(void) {
    goto LABEL;
    int *p = 0;
    LABEL:
    if (p) {
        printf("%d\n", *p);
    }
}
Run Code Online (Sandbox Code Playgroud)

确实有未定义的行为.在goto将控制转移到的范围内的一个点p,但绕过它的初始化,所以p在当具有一个不确定的值if (p),执行测试.

在您的程序中,值p始终是明确定义的.在之前到达的声明goto设置p0(空指针).该if (p)测试是假的,所以身体if不执行语句中的第一次.的goto是之后执行p已被赋予了良好限定的非空值.之后goto,if (p)测试为真,并printf执行调用.

在你的程序中,寿命两者pi开始开放时{main到达,期末结束时}达到或return正在执行的语句.每个的范围(即其名称可见的程序文本区域)从其声明延伸到结束}.当goto转移控制向后时,变量名称i超出范围,但该名称引用的int 对象仍然存在.该名称p在范围内(因为它是在前面声明的),并且指针对象仍然指向同一个int对象(i如果该名称可见,则其名称将是如此).

请记住,范围是指程序文本的一个区域,其中名称是可见的,而生命周期是指程序执行期间保证对象存在的时间跨度.

通常,如果对象的声明具有初始值设定项,则只要其名称可见,它就会保证它具有有效值(除非稍后为其分配了一些无效值).这可以用a gotoswitch(但如果小心使用的话)绕过.


Sha*_*our 6

此代码没有未定义的行为.我们可以在国际标准编程语言的基本原理中找到一个很好的例子-6.2.4 对象的存储持续时间中它说:

[...]有一个简单的经验法则:当输入块时,声明的变量是使用未指定的值创建的,但是初始化程序被评估,并且在正常过程中达到声明时将值放在变量中执行.因此,超过声明的跳跃使其未初始化,而向后跳跃将导致它不止一次初始化.如果声明未初始化变量,则将其设置为未指定的值,即使这不是第一次达到声明.

变量的范围从其声明开始.因此,尽管变量在输入块后立即存在,但在达到其声明之前,不能通过名称引用变量.

并提供以下示例:

int j = 42;
{
   int i = 0;
 loop:
   printf("I = %4d, ", i);
   printf("J1 = %4d, ", ++j);
   int j = i;
   printf("J2 = %4d, ", ++j);
   int k;
   printf("K1 = %4d, ", k);
   k = i * 10;
   printf("K2 = %4d, ", k);
   if (i % 2 == 0) goto skip;
    int m = i * 5;
skip:
  printf("M = %4d\n", m);
  if (++i < 5) goto loop;
}
Run Code Online (Sandbox Code Playgroud)

输出是:

 I = 0, J1 = 43, J2 = 1, K1 = ????, K2 = 0, M = ????
 I = 1, J1 = 44, J2 = 2, K1 = ????, K2 = 10, M = 5
 I = 2, J1 = 45, J2 = 3, K1 = ????, K2 = 20, M = 5
 I = 3, J1 = 46, J2 = 4, K1 = ????, K2 = 30, M = 15
 I = 4, J1 = 47, J2 = 5, K1 = ????, K2 = 40, M = 15
Run Code Online (Sandbox Code Playgroud)

它说:

其中"????"表示不确定的值(并且对不确定值的任何使用都是未定义的行为).

此示例与C99标准部分草案第56.2.4 的对象存储持续时间一致,其中说:

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