【问题标题】:Statement goto can not cross variable definition?语句goto不能跨变量定义?
【发布时间】:2026-01-09 06:05:02
【问题描述】:

假设这些代码编译在g++:

#include <stdlib.h>

int main() {
    int a =0;

    goto exit;

    int *b = NULL;

exit:
    return 0;
}

g++ 会抛出错误:

goto_test.c:10:1: error: jump to label ‘exit’ [-fpermissive]
goto_test.c:6:10: error:   from here [-fpermissive]
goto_test.c:8:10: error:   crosses initialization of ‘int* b’

似乎goto 不能跨指针定义,但gcc 编译它们没问题,没有任何抱怨。

修复错误后,我们必须在任何goto语句之前声明所有指针,也就是说,即使你目前不需要这些指针,也必须声明这些指针(并且违反某些原则)。

g++ 禁止有用的 tail-goto 语句的起源设计考虑是什么?


更新:

goto 可以跨变量(任何类型的变量,不限于指针)声明,但获得初始化值的除外。如果我们删除上面的NULL 分配,g++ 现在保持沉默。所以如果你想声明goto-cross-area之间的变量,不要初始化它们(仍然违反一些原则)。

【问题讨论】:

  • 这应该是 C 题还是 C++ 题?您的文件名为 .c,但您正在使用 g++ 进行编译,这会强制使用 C++ 并生成特定于 C++ 的错误。有问题的错误是 C++ 特定的。

标签: g++ goto


【解决方案1】:

Goto 不能跳过变量的初始化,因为在跳转后相应的对象将不存在,因为具有非平凡初始化的对象的生命周期在执行初始化时开始:

C++11 §3.8/1:

[…] T 类型对象的生命周期开始于:

  • 获得了适合类型 T 的对齐方式和大小的存储,并且

  • 如果对象有非平凡的初始化,它的初始化就完成了。

C++11 §6.7/3:

可以转移到块中,但不能通过初始化绕过声明。一种 从具有自动存储持续时间的变量不在范围内的点跳转到 它在范围内的点是格式错误的,除非变量具有标量类型,类类型具有微不足道的默认值 构造函数和普通析构函数,其中一种类型的 cv 限定版本,或其中一种类型的数组 前面的类型,并且在没有初始化程序的情况下声明 (8.5)。

由于错误提及[-fpermissive],您可以通过指定该编译器标志将其变为警告。这表明两件事。过去是允许的(变量会存在,但在跳转后未初始化)并且 gcc 开发人员认为规范禁止它。

编译器只检查变量是否应该被初始化,而不检查它是否被使用,否则结果会很不一致。但是,如果您不再需要该变量,您可以自己结束它的生命周期,使“tail-goto”变得可行:

int main() {
    int a =0;
    goto exit;
    {
        int *b = NULL;
    }
exit:
    return 0;
}

完全有效。

附带说明,该文件具有扩展名.c,这表明它是 C 而不是 C++。如果您使用gcc 而不是g++ 编译它,那么原始版本应该可以编译,因为C 没有这个限制(它只有对可变长度数组的限制——这在C++ 中根本不存在)。

【讨论】:

  • 没错,但您的评论暗示了“变量”和“对象”之间的一些区别,这在 C++ 中并不真正存在。所有变量都是 C++ 中的对象。
  • 您完全误解了您的报价。它说“不允许 goto 语句跳过任何具有可变修改类型的对象声明。”最后一部分是这里的关键。在 C 中,您可以跳过 任何 声明 (!),即使它们已初始化 (!!),但只要它们不是用于 可变修改类型。我在这里简单提一下这个区别 (*.com/questions/22530363/…)
  • @AnT,嗯,你是对的。那为什么 GCC 认为代码无效呢? Gcc 从扩展名中决定语言,代码显然有 .c 的扩展名,因此应该编译为 C。在 C 中,它应该是合法的......
  • g++ 始终编译为 C++,无论扩展名如何。基于扩展的语言检测由gcc 可执行文件完成,而不是由g++ 完成。所以,这个问题可能是 C++,而不是 C。虽然不清楚为什么 OP 将他们的文件命名为 .c
【解决方案2】:

对于像int 这样的原始类型有一个简单的解决方法:

 // ---  original form, subject to cross initialization error.  ---
 // int foo = 0;

 // ---  work-around form: no more cross initialization error.  ---
 int foo;  foo = 0;

【讨论】: