【问题标题】:Is 'goto' smart for correct working with stack variables in C (not C++)在 C(不是 C++)中正确使用堆栈变量是否“goto”很聪明
【发布时间】:2012-11-11 10:00:05
【问题描述】:

(抱歉英语不好。)

问题 1。

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

当我转到inside 标签时,是否会在堆栈中为stack_var 分配一个位置? IE。我可以在... 中正确使用stack_var 变量吗?

问题 2。

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

当我转到outside 标签时,是否会在stack_var 的堆栈中被释放?例如。在... 中做return 是否正确?

换句话说,goto 是聪明地正确使用堆栈变量(当我遍历块时自动(取消)分配),还是只是一个愚蠢的跳跃?

【问题讨论】:

  • 编译时请注意 raptors。
  • 在 C++ 中,您的第二个示例定义良好(并且将为更复杂的自动变量调用正确的析构函数)。然而,第一个具有未定义的行为。我相信在 C 中,唯一会出错的是stack_var 在第一个示例中不会被正确初始化。

标签: 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 会在堆栈中释放一个位置吗?

是的,您可以期待为 stack_var 保留的内存会在变量超出范围后立即被回收。

【讨论】:

  • 我可能弄错了,但这是否意味着该变量实际上会被初始化?
  • @H2CO3 我和我的编译器都将“每次按执行顺序到达声明”解释为当没有按执行顺序到达声明时,初始化不会发生.
  • 只是为了澄清一下:编译器可能会进行初始化,但这不是被禁止的。无论何时定义行为,都不会有明显的差异;如果行为未定义,编译器可能会做任何事情。因此,启用优化后,gcc 会将上述示例正确地 (1) 转换为 movl $42, %eax; ret;。 (1) 这是一个正确的翻译。
【解决方案2】:

有两个不同的问题:

  • 词法作用域 C 代码中的变量。 C 变量仅在声明它的块内才有意义。您可以想象编译器正在将变量重命名为唯一名称,这仅在作用域块内有意义。

  • 调用框架在生成的代码中。一个好的优化编译器通常将当前函数的调用帧分配在函数开头的机器类栈上。该调用框架中的给定位置,称为 slot 可以(并且通常)被编译器重用于多个局部变量(或其他目的)。

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

您的第一个案例可能会伤害到未定义的行为。在goto inside 之后,stack_var 未初始化。

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

【讨论】:

    猜你喜欢
    • 2011-04-30
    • 1970-01-01
    • 2015-04-18
    • 1970-01-01
    • 2021-10-13
    • 2010-10-10
    • 2021-11-02
    相关资源
    最近更新 更多