问题 'goto'是否适合在C(而不是C ++)中正确使用堆栈变量


(抱歉英文不好。)

问题1。

void foo(void)
{
    goto inside;
    for (;;) {
        int stack_var = 42;
inside:
        ...
    }
}

将分配给堆栈的位置 stack_var 当我转到 inside 标签?即我能正确使用吗? stack_var 内变量 ...

问题2。

void foo(void)
{
    for (;;) {
        int stack_var = 42;
        ...
        goto outside;
    }
outside:
    ...
}

将是一个堆栈的地方 stack_var 当我转到时,我解除了分配 outside 标签?例如。这样做是否正确 return 中 ...

换句话说,是 goto 智能正确使用堆栈变量(当我走过块时自动(de)分配),或者它只是一个愚蠢的跳跃?


10048
2017-11-11 10:00


起源

编译时要注意猛禽。 - Flynn1179
在C ++中,你的第二个例子定义得很好(并且将为更复杂的自动变量调用正确的析构函数)。然而,第一个具有未定义的行为。我相信在C中,唯一会出错的是 stack_var 将在第一个示例中未正确初始化。 - Alexandre C.


答案:


问题1:

我可以正确使用...内的stack_var变量吗?

...中的代码可以写入 stack_var。但是,这个变量是未初始化的,因为执行流程跳过了初始化,因此代码不应该在没有先写入的情况下从中读取。

从C99标准来看,6.8:3

每次按执行顺序达到声明时,将评估具有自动存储持续时间[...]的对象的初始化程序,并将值存储在对象中(包括在没有初始化程序的对象中存储不确定值)

我的编译器将下面的函数编译成一个程序集,有时会返回未初始化的内容 x

int f(int c){
  if (c) goto L;
  int x = 42;
 L:
  return x;
}

    cmpl    $0, %eax
    jne LBB1_2
    movl    $42, -16(%rbp)
LBB1_2:
    movl    -16(%rbp), %eax
...
    popq    %rbp
    ret

问题2:

当我转到外部标签时,将在堆栈中放置一个堆栈的地方吗?

是的,你可以期待保留的内存 stack_var 一旦变量超出范围就被收回。


10
2017-11-11 10:05



我可能会弄错,但这并不意味着变量实际上会被初始化吗?
@ H2CO3我的编译器和我都解释“每次按执行顺序到达声明”,这意味着当按照执行顺序未达到声明时,初始化不会发生。 - Pascal Cuoq
只是为了澄清:编译器 威力 尽管做了初始化,但并没有被禁止。无论何时定义行为,都不会有可观察到的差异;如果行为未定义,编译器可能会做任何事情。因此,在启用优化的情况下,gcc会正确地将上述示例(1)转换为 movl $42, %eax; ret;。 (1)那是 一 正确的翻译。 - Daniel Fischer


有两个不同的问题:

  • 词汇范围 里面的变量 C 码。一个 C 变量只在声明它的块内有意义。您可以想象编译器将变量重命名为唯一名称,这些名称仅在范围块内有意义。

  • 呼叫帧 在生成的代码中。一个好的优化编译器通常在函数开头的机器类堆栈上分配当前函数的调用帧。该调用帧中的给定位置,称为 插槽 可以(通常是)由编译器重用几个局部变量(或其他目的)。

并且局部变量只能保存在寄存器中(调用帧中没有任何插槽),并且该寄存器显然可以用于各种目的。

你可能会受伤 未定义的行为 为你的第一个案例。之后 goto inside 该 stack_var 没有初始化。

我建议你编译 gcc -Wall 并改善代码直到没有给出警告。


2
2017-11-11 10:05