Страницы

Поиск по вопросам

пятница, 13 декабря 2019 г.

Что происходит при безусловном переходе при помощи оператора goto?

#c #goto


Собственно, интересует, какие проблемы могут возникнуть при использовании этого оператора.

Для меня очевидно, что goto совершенно незаменим во множестве ситуаций:


построение конечных автоматов;
выход из глубоко вложенных циклов;
обработка ошибок и пр. 


Поскольку в C нет RAII и исключений, то по сравнению с C++ диапазон возможных проблем
резко сокращается, но проблемы все же остаются.

Например:

goto A;
if (a() == 0)
{
    A:;
    b();// Вызов a() пропущен?
}


Или:

goto A;
// ...
for (int i = 0; i < 10; ++i)
{   
    A:;
    // i имеет неопределенное значение?
}


Или:

goto A;
int i;
// ...
i = a();
A:;
i = b();// Определение i пропущено, куда мы записываем результат работы b()?

    


Ответы

Ответ 1



В языке С время жизни автоматической переменной (кроме VLA) начинается в момент входа в блок, содержащий объявление этой переменной An object whose identifier is declared with no linkage and without the storage-class specifier static has automatic storage duration [...] For such an object that does not have a variable length array type, its lifetime extends from entry into the block with which it is associated until execution of that block ends in any way [...] Причем "момент входа в блок" - это чисто временнАя, а не пространственная характеристика - входить в блок можно любым способом, как через начало, так и прямо в середину. Как только управление попало внутрь блока - все автоматические переменные, объявленные в этом блоке уже существуют, независимо от того, видны ли в месте входа их имена. // Если мы попадаем в блок отсюда... { // <- ... то время жизни переменной `b` начинается уже здесь ... int b; // <- А здесь начинается лишь область видимости имени `b` ... } Таким образом для "создания" переменной (кроме VLA) совсем не обязательно, чтобы управление буквально проходило по определению этой переменной. Переменная уже существует с момента входа в блок. Поэтому передача управления при помощи goto через объявление с инициализацией в С приведет лишь к тому, что переменная не будет инициализирована. Однако на сам факт существования переменной и ее время жизни это не оказывает никакого влияния.

Ответ 2



Вызов a() пропущен? Да i имеет неопределенное значение? Да goto A; int i; A: i = b();// Определение i пропущено, куда мы записываем результат работы Не зависимо от местоположения определения, время жизни обычной переменной — целый блок. Также не важно, как произошёл вход в блок — в процессе обычного исполнения или через goto — компилятор должен обеспечить место хранения для данного объекта. Я не совсем понимаю, что происходит, если goto переводит выполнение в тело цикла или другого блока. На уровне языка это не важно. Можно, пожалуй руководствоваться сентенцией: выделять ресурсы под объекты с автоматическим временем жизни — забота компилятора, а инициализировать их — программиста. Так же мне не совсем понятно, являются ли переменные, через определение/объявление которых мы перепрыгнули, доступными для использования. Да, являются; единственное, что явно запрещено — это пересекать объявление массива переменной длины в его же области видимости: size_t n=getSize(); if (n<=0) { goto A; } // ошибка else if (n==1) { goto B; } // ок { int a[n]; A: a[n-2] = doSomething(); } B:; Также, строго говоря, когда управление проходит через объявление, значение переменной становится неопределённым: int j=0; goto assign; start: int i; j = foo(i); // здесь значение i, строго говоря, не определено. assign: i=-1; if (j==0) {goto start;} В остальном возможно практически всё, а семантика «так как оно и выглядит». Скользких мест, на вскидку, нет.

Ответ 3



Весь скомпилированный код испещрён goto. Как всё это выглядит в скомпилированном коде: /* goto A; if (a() == 0){ A:; b();// Вызов a() пропущен? } */ goto A; ax:= call a(); if ax == 0 goto A ; else goto Next ; A:; call b(); Next:; Для команды if вы привыкли, что некоторые участки кода НЕ выполняются, а тут вдруг пропущенный вызов a() у вас производит страх. /* goto A; // ... for (int i = 0; i < 10; ++i) { A:; // i имеет неопределенное значение? } */ goto A; стек[-4] := 0 ; ForBody:; if стек[-4] < 10 goto A; else goto Next; A:; ++ стек[-4]; goto ForBody; Next:; Здесь переменная не инициализирована, эта лажа уже на совести программиста. Будет неопределённое поведение. Но иногда, значение переменных не влияют на ход алгоритма, поэтому на совести программера будет: это фишка алгоритма или баг. /*goto A; int i; // ... i = a(); A:; i = b();// Определение i пропущено, куда мы записываем результат работы b()? */ goto A; stack[-4] := call a(); A:; stack[-4] := call b(); Значение переменной i хранится в стеке, и место уже выделено при старте функции заранее. Так-что фактическое исполнение программы желательно знать. Это очень помогает разобраться как работает комп.

Ответ 4



Любую программу с goto можно эквивалентными преобразованиями превратить в программу без goto, и тогда поддерживать ее встанет намного проще. Когда я вижу в чужом коде метку, я очень сильно настораживаюсь - а вдруг на эту метку прыгают из кода, который находится в другом файле, который прицеплен сюда при помощи #include? Количество WTF в секунду катастрофически возрастает, что в нашей работе и так неприемлемо. Решение - почитайте программную статью Дейкстры и ее критику. Для меня очевидно, что goto совершенно незаменим во множестве ситуаций: построение конечных автоматов; выход из глубоко вложенных циклов; обработка ошибок и пр. Очевидно как раз обратное: Использовать процедуры или классы с полиморфизмом - не нужен goto return/throw return/throw, RAII

Комментариев нет:

Отправить комментарий