icon안동민 개발노트

goto 문 (사용 주의사항 포함)


goto 문 개요

 goto 문은 프로그램의 실행 흐름을 특정 레이블로 직접 이동시키는 제어 흐름 문장입니다.

 C++에서는 여전히 지원되지만, 구조적 프로그래밍의 원칙에 위배되어 일반적으로 사용을 권장하지 않습니다.

 그러나 goto 문의 역사와 기능을 이해하는 것은 프로그래밍 언어의 발전을 이해하는 데 중요합니다.

goto 문의 기본 구조

goto label;
// ...
label:
    // 이동할 코드

 goto 문은 프로그램의 실행을 지정된 레이블로 무조건 전환합니다.

goto 문의 예제

#include <iostream>
 
int main() {
    int i = 0;
    
start:
    if (i >= 5) goto end;
    std::cout << i << " ";
    i++;
    goto start;
    
end:
    std::cout << std::endl;
    return 0;
}
출력
0 1 2 3 4

 이 예제는 goto 문을 사용하여 간단한 루프를 구현합니다. 하지만 이는 단순히 goto의 작동 방식을 보여주기 위한 것이며, 실제로는 for 루프를 사용하는 것이 더 적절합니다.

goto 문의 장단점

 장점

  1. 복잡한 중첩 루프에서 빠져나올 때 유용할 수 있습니다.
  2. 일부 저수준 시스템 프로그래밍에서 유용할 수 있습니다.
  3. 특정 상황에서 코드를 간결하게 만들 수 있습니다.

 단점

  1. 코드의 가독성과 유지보수성을 크게 저하시킵니다.
  2. 프로그램의 논리적 흐름을 파악하기 어렵게 만듭니다.
  3. 디버깅을 어렵게 만들 수 있습니다.
  4. '스파게티 코드'를 만들 위험이 높습니다.
  5. 현대적인 프로그래밍 패러다임과 맞지 않습니다.

goto 문의 대안

 대부분의 경우, goto 문은 다른 제어 구조로 대체할 수 있습니다.

 반복문 (for, while, do-while)

for (int i = 0; i < 5; i++) {
    std::cout << i << " ";
}

 조건문 (if-else)

if (condition) {
    // 코드
} else {
    // 다른 코드
}

 함수 호출

void processData() {
    // 데이터 처리 로직
}
 
int main() {
    processData();
    return 0;
}

 예외 처리

try {
    // 코드
} catch (std::exception& e) {
    // 예외 처리
}

goto 문의 실제 사용 사례

 비록 goto 문의 사용이 권장되지 않지만, 여전히 일부 상황에서는 사용될 수 있습니다.

 오류 처리

#include <iostream>
#include <cstdlib>
 
int main() {
    // 리소스 할당
    int* resource1 = new int[100];
    int* resource2 = nullptr;
    
    // 작업 수행
    resource2 = new int[200];
    if (!resource2) goto cleanup;
    
    // 정상적인 처리
    delete[] resource2;
    delete[] resource1;
    return 0;
    
cleanup:
    delete[] resource1;  // resource2는 할당 실패했으므로 해제 불필요
    std::cerr << "Error: 메모리 할당 실패" << std::endl;
    return 1;
}

 이 예제에서 goto는 오류 발생 시 리소스를 정리하는 데 사용됩니다. 하지만 현대 C++에서는 RAII(Resource Acquisition Is Initialization) 원칙과 스마트 포인터를 사용하여 이를 더 안전하게 처리할 수 있습니다.

 상태 기계 구현

#include <iostream>
 
enum State { START, PROCESS, END };
 
int main() {
    State state = START;
    
    goto START;
    
START:
    std::cout << "Starting..." << std::endl;
    state = PROCESS;
    goto PROCESS;
    
PROCESS:
    std::cout << "Processing..." << std::endl;
    state = END;
    goto END;
    
END:
    std::cout << "Ending..." << std::endl;
    return 0;
}

 이 예제는 goto를 사용하여 간단한 상태 기계를 구현합니다.

 하지만 실제로는 switch 문이나 상태 패턴을 사용하는 것이 더 좋은 방법입니다.

goto 문 사용 시 주의사항

  1. 가능한 한 goto 문 사용을 피하고 구조적 프로그래밍 기법을 사용하세요.
  2. goto 문을 사용해야 한다면, 앞으로만 점프하도록 하세요. (뒤로 점프하면 무한 루프 위험)
  3. 함수 경계를 넘어 점프하는 것은 불가능합니다.
  4. goto 문과 레이블은 같은 함수 내에 있어야 합니다.
  5. goto 문 사용 시 코드의 가독성과 유지보수성이 크게 저하될 수 있음을 항상 염두에 두세요.

현대 C++에서의 goto 문

 현대 C++에서는 goto 문의 사용이 크게 줄었습니다.

 대부분의 경우 더 나은 대안이 존재하며, goto 문 없이도 깔끔하고 효율적인 코드를 작성할 수 있습니다.

 C++ 11 이후 도입된 다양한 기능들(람다 표현식, 범위 기반 for 루프 등)은 goto 문을 사용할 필요성을 더욱 줄였습니다.

goto 문의 역사적 맥락

 goto 문은 초기 프로그래밍 언어에서 중요한 역할을 했습니다.

 그러나 1968년 Edsger W. Dijkstra의 유명한 논문 "Go To Statement Considered Harmful"이 발표된 이후, 구조적 프로그래밍이 주목받기 시작했고 goto 문의 사용은 점차 줄어들었습니다.

연습 문제

  1. goto 문을 사용하여 1부터 10까지의 숫자를 출력하는 프로그램을 작성하세요. 그 다음, 같은 기능을 하는 프로그램을 for 루프를 사용하여 작성하고 두 버전을 비교해보세요.
  2. goto 문을 사용하여 간단한 메뉴 시스템을 구현해보세요. 그 다음, 같은 기능을 switch 문을 사용하여 재구현하고 두 방식의 장단점을 논의해보세요.
  3. 다음 코드를 goto 문 없이 재작성해보세요.
void process_data(int* data, int size) {
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            if (data[i] + data[j] == 0) {
                std::cout << "Found pair: " << data[i] << ", " << data[j] << std::endl;
                goto end;
            }
        }
    }
end:
    std::cout << "Processing complete" << std::endl;
}

 참고자료