Как уже говорили другие, проблема не в gotoсамом себе; проблема в том, как люди используют goto, и как это может усложнить понимание и поддержку кода.
Предположим следующий фрагмент кода:
i = 4;
label: printf( "%d\n", i );
Какое значение печатается для i? Когда это напечатано? Пока вы не учтете каждый экземпляр goto labelвашей функции, вы не сможете знать. Простое присутствие этой метки разрушает вашу способность отлаживать код простым осмотром. Для небольших функций с одной или двумя ветвями проблем не так много. Для немалых функций ...
Еще в начале 90-х нам дали кучу кода C, который работал на трехмерном графическом дисплее и сказал, чтобы он работал быстрее. Это было всего около 5000 строк кода, но все это было в main, и автор использовал около 15 или около того gotoразветвления в обоих направлениях. С самого начала это был плохой код, но его присутствие gotoсделало его намного хуже. Моему коллеге понадобилось около 2 недель, чтобы разобраться с потоком контроля. Более того, это gotoпривело к тому, что код был так тесно связан с самим собой, что мы не смогли внести никаких изменений, не нарушив что-либо.
Мы попытались скомпилировать с оптимизацией уровня 1, и компилятор съел всю доступную оперативную память, затем всю доступную подкачку, а затем запаниковал систему (что, вероятно, не имело ничего общего с gotoсамими s, но мне нравится выкидывать этот анекдот).
В итоге мы предоставили заказчику два варианта - давайте переписать все с нуля или купим более быстрое оборудование.
Они купили более быстрое оборудование.
Правила Боде по использованию goto:
- Только ветвь вперед;
- Не обходите управляющие структуры (т. Е. Не переходите в тело оператора
ifor forили or while);
- Не используйте
gotoвместо контрольной структуры
Есть случаи, когда правильный ответ goto - a , но они редки (выход из глубоко вложенного цикла - единственное место, где я бы его использовал).
РЕДАКТИРОВАТЬ
Расширяя это последнее утверждение, вот один из немногих допустимых вариантов использования для goto. Предположим, у нас есть следующая функция:
T ***myalloc( size_t N, size_t M, size_t P )
{
size_t i, j, k;
T ***arr = malloc( sizeof *arr * N );
for ( i = 0; i < N; i ++ )
{
arr[i] = malloc( sizeof *arr[i] * M );
for ( j = 0; j < M; j++ )
{
arr[i][j] = malloc( sizeof *arr[i][j] * P );
for ( k = 0; k < P; k++ )
arr[i][j][k] = initial_value();
}
}
return arr;
}
Теперь у нас есть проблема - что делать, если один из mallocвызовов не проходит в середине? В отличие от этого события, мы не хотим возвращать частично выделенный массив и не хотим просто выйти из функции с ошибкой; мы хотим очистить себя и освободить частично выделенную память. В языке, который выдает исключение при неправильном выделении ресурсов, это довольно просто - вы просто пишете обработчик исключений, чтобы освободить то, что уже было выделено.
В C вы не имеете структурированной обработки исключений; Вы должны проверить возвращаемое значение каждого mallocзвонка и принять соответствующие меры.
T ***myalloc( size_t N, size_t M, size_t P )
{
size_t i, j, k;
T ***arr = malloc( sizeof *arr * N );
if ( arr )
{
for ( i = 0; i < N; i ++ )
{
if ( !(arr[i] = malloc( sizeof *arr[i] * M )) )
goto cleanup_1;
for ( j = 0; j < M; j++ )
{
if ( !(arr[i][j] = malloc( sizeof *arr[i][j] * P )) )
goto cleanup_2;
for ( k = 0; k < P; k++ )
arr[i][j][k] = initial_value();
}
}
}
goto done;
cleanup_2:
// We failed while allocating arr[i][j]; clean up the previously allocated arr[i][j]
while ( j-- )
free( arr[i][j] );
free( arr[i] );
// fall through
cleanup_1:
// We failed while allocating arr[i]; free up all previously allocated arr[i][j]
while ( i-- )
{
for ( j = 0; j < M; j++ )
free( arr[i][j] );
free( arr[i] );
}
free( arr );
arr = NULL;
done:
return arr;
}
Можем ли мы сделать это без использования goto? Конечно, мы можем - это просто требует немного дополнительной бухгалтерии (и на практике это путь, который я бы выбрал). Но, если вы ищете место , где использование gotoне сразу признак плохой практики или дизайна, это один из немногих.