다른 사람들이 말했듯이 문제는 goto
그 자체 가 아닙니다 . 문제는 사람들이 사용 goto
하는 방식과 코드를 이해하고 유지하기가 더 어렵게 만드는 방법에 있습니다.
다음 코드 스 니펫을 가정하십시오.
i = 4;
label: printf( "%d\n", i );
어떤 값이 인쇄 i
됩니까? 언제 인쇄됩니까? 함수 의 모든 인스턴스 를 설명 할 때까지 goto label
알 수 없습니다. 라벨의 단순한 존재는 파괴 간단한 검사하여 코드를 디버깅 할 수있는 능력을. 하나 또는 두 개의 분기가있는 작은 기능의 경우 큰 문제가되지 않습니다. 작은 기능이 아닌 경우
90 년대 초반에 우리는 3D 그래픽 디스플레이를 구동하고 더 빨리 실행하도록하는 C 코드 더미를 받았습니다. 그것은 약 5000 줄의 코드 였지만 모든 코드 는 안에 있었고 main
저자 goto
는 양방향으로 약 15 가지 정도 의 분기를 사용했습니다. 이것은 처음에는 잘못된 코드 였지만, 그 존재로 goto
인해 훨씬 더 나빠졌습니다. 제어 흐름을 해결하기 위해 동료가 약 2 주가 걸렸습니다. 더 좋은 점 goto
은 코드가 자체와 밀접하게 결합되어 무언가를 깨지 않고는 변경할 수 없었 습니다.
우리는 레벨 1 최적화로 컴파일을 시도했으며 컴파일러는 사용 가능한 모든 RAM, 사용 가능한 모든 스왑을 먹은 다음 시스템을 당황 시켰습니다 (아마도 goto
s 와는 관련이 없었지만 그 일화를 버리는 것을 좋아합니다).
결국 고객에게 두 가지 옵션을 제공했습니다. 모든 것을 처음부터 다시 작성하거나 더 빠른 하드웨어를 구입하도록하겠습니다.
그들은 더 빠른 하드웨어를 구입했습니다.
Bode의 사용 규칙 goto
:
- 분기 앞으로 만;
- 제어 구조를 우회하지 마십시오 (즉,
if
or for
또는 while
문의 본문으로 분기하지 마십시오 ).
goto
제어 구조 대신 사용하지 마십시오
a goto
가 정답 인 경우가 있지만 드문 경우입니다 (깊게 중첩 된 루프에서 벗어나는 것은 내가 사용하는 유일한 장소입니다).
편집하다
마지막 문장을 확장하여에 대한 몇 가지 유효한 사용 사례 중 하나입니다 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
아닌 바로 나쁜 관행 또는 디자인의 표시, 이것은 몇 가지 중 하나입니다.