Как уже говорили другие, проблема не в 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
:
- Только ветвь вперед;
- Не обходите управляющие структуры (т. Е. Не переходите в тело оператора
if
or 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
не сразу признак плохой практики или дизайна, это один из немногих.