局部变量之前的GOTO

时间:2014-07-28 22:36:48

标签: c declaration undefined-behavior goto

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

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

3 个答案:

答案 0 :(得分:16)

您的计划中没有未定义的行为。

goto语句有两个约束:

  

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

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

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

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

  

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

这意味着,i在读取*p时仍然存在;在其生命周期之外没有任何对象被访问。

答案 1 :(得分:8)

我会尝试回答你可能想问的问题。

您的计划行为已明确定义。 (return -1;存在问题;只有0EXIT_SUCCESSEXIT_FAILURE被明确定义为main返回的值。但这不是您的意思#39;重新询问。)

这个程序:

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

确实有未定义的行为。 goto将控制转移到p范围内的某个点,但绕过其初始化,因此p在执行if (p)测试时具有不确定的值。

在您的计划中,p的值始终定义明确。在goto之前到达的声明将p设置为0(空指针)。 if (p)测试为false,因此if语句的主体不会在第一次执行。 gotop被赋予明确定义的非空值后执行。在goto之后,if (p)测试为真,并且printf调用已执行。

在您的计划中,pi生命周期在到达{的开放main时开始,并在结束时结束达到结束}或执行return语句。每个范围(即其名称可见的程序文本区域)从其声明扩展到结束}。当goto向后传输控件时,变量名i超出范围,但该名称引用的int 对象仍然存在。名称p在范围内(因为它已在前面声明),并且指针对象仍然指向相同的int对象(如果该名称可见,则其名称将为i。)< / p>

请记住,范围是指程序文本中可以看到名称的区域, lifetime 是指程序执行期间的一段时间,在此期间对象是保证存在。

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

答案 2 :(得分:6)

此代码没有未定义的行为。我们可以在6.2.4部分的 Rationale for International Standard—Programming Languages—C 中找到一个很好的示例对象的存储持续时间它说:

  

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

     

变量的范围从其声明开始。因此,虽然   一旦输入块,变量就会存在,但不能存在   在其声明到达之前按名称提及。

并提供以下示例:

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;
}

,输出为:

 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

它说:

  

其中“????”表示不确定的值(以及任何使用   不确定的值是未定义的行为)。

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

  

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