Утверждение goto не может пересекать определение переменной?

Предположим, что этот код скомпилирован в 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?


Update:

goto может пересекать переменную (любой тип переменной, не ограничиваясь указателем), но за исключением тех, которые получили значение инициализации. Если мы удалим назначение NULL выше, g++ сейчас молчать. Поэтому, если вы хотите объявить переменные между goto -cross-area, не инициализируйте их (и по-прежнему нарушайте некоторые принципы).

Ответ 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++).

Ответ 2

Существует простая процедура для тех примитивных типов, как int:

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

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