20 비-지역 탈출

때때로 당신의 프로그램이 호출한 함수의 깊게 내포된 곳에서 평소와는 다른 상황을 검출할 때, 당신은 제어수준의 밖으로 즉시 반환이 가능하게

되는 것이 좋을 것이다. 이 절은 그와같은 비-지역 탈출의 상황에서

어떻게 setjmp 와 longjmp를 사용할 수 있는지를 설명한다.


20. 1 비-지역 탈출들에 대한 안내

비-지역 탈출이 유용할 수 있는 상황에 대한 예제로서, 프롬프트 상에서 명령문으로 실행하는 "메인 루프"를 가진, 즉 상호 작용하는 프로그램이 있다고 가정하자. 그리고 "read"라는 명령문으로는 파일로부터 입력을 읽어서, 그것이 처리되는 동안 입력의 어휘와 구문을 분석한다고 가정하자. 그때 만일 저-수준 입력 에러가 검출되면, 어휘와 구문분석 각각을 만들고, 내포된 호출에 의해 검출된 에러상황을 명백하게 취급하는 모든 처리상황을 만들기보다는 "메인 루프"로 즉시 반환 가능하도록 하는 것이 유용할 것이다. ( 다른 한편으로, 만일 파일들을 닫거나, 버퍼나 다른 데이터 구조체를 해제하는 등의 그와같은 exit의 상황일 때 그들 상황에서 그들에게 할당된 실제적인 양을 소거한다면_보통 반환을 하고 그 자체가 소거되는 것이 더 적당할 수 있다, 왜냐하면 비-지역 탈출은 방해되는 상황들을 무시하고 그들에 연관된 전체 코드를 소거하기 때문이다. 선택적으로 당신은 "메인 루프"의 뒤나 또는 앞에서 명백하게 소거를 할 수 있다. )

어떤 방법에서, 비-지역 탈출은 함수로부터 반환을 하는 `return' 구문을 사용하는 것과 유사하다. 그러나 `return'은 오직 단일하게 호출된 함수로부터 빠져나와서 그것이 호출되었던 지점의 뒤로 호출을 넘기지만, 비-지역 탈출은 내포된 함수 호출의 여러 단계를 빠져나올 수 있다.

당신은 setjmp 함수를 호출해서 비-지역 탈출을 위한 반환지점을 확인하라. 이 함수는 object의 타입 jmp_buf안에 실행환경에 대한 정보를 저장한다. setjmp의 호출 후에도 프로그램의 실행은 정상적으로 계속되지만, 만일 한 exit가 그것에 부합되는 jmp_buf 오브젝트를 인수로 하여 longjmp를 호출함으로써 이 반환 지점보다 나중에 만들어졌다면 제어는 setjmp가 호출되었던 그 지점의 뒤로 넘겨진다. setjmp로 부터의 반환값은, 보통의 반환과 longjmp를 호출함으로 해서 만들어진 반환과의 구별하기 위해 사용되므로, setjmp는 보통 `if'문안에 나타난다.

위에 설명된 예제 프로그램이 어떻게 만들어지는지를 보여준다.

#include <setjmp. h>
#include <stdlib. h>
#include <stdio. h>
jmp_buf main_loop;
void
abort_to_main_loop (int status)
{
longjmp (main_loop, status);
}
int
main (void)
{
while (1) {
if (setjmp (main_loop))
puts("Back at main loop. . . . ");
else
do_command();
}
}
void
do_command(void)
{
char buffer[128];
if (fgets (buffer, 128, stdin) == NULL)
abort_to_main_loop (-1);
else
exit (EXIT_SUCCESS);
}
함수 abort_to_main_loop는 그것이 어디로부터 호출되었는지에 상관없이, 프로그램의 메인 루프로 제어를 즉시 돌려준다.
메인 함수 안의 제어의 흐름은 처음에 조금 이상한일이 발생할지도 모르지만, 그러나 그것은 실제로 setjmp에서 사용하는 보통의 관용이다. setjmp는 보통 0을 반환하므로, 그래서 조건절 "else"가 실행되어진다. 만일 abort_to_main_loop가 do_command의 실행 중에 어느 곳에서 호출되어지면, 그것은 메인에서 setjmp를 호출하는 것처럼 나타난다. 그래서 setjmp를 사용하는 일반적 양식은 다음과 같다:
if (setjmp (buffer))
/* 때이른 반환 후에 소거하려는 코드 */
. . .
else
/* 반환 지점을 설정한 후에 일반적으로 실행되어지는 코드 */
. . .


20. 2 비-지역 분기의 상세한 설명

여기에서는 비-지역 분기를 수행하기 위해 사용되는 함수들과 데이터 구조체에 대해서 설명한다. 이들 도구들은 `setjmp. h'에 선언되어 있다.

데이터 타입 : jmp__buf

오브젝트의 타입 jmp_buf는 비-지역 분기에 의해 되돌려질 상황 정보를 저장한다. jum_buf의 내용들은 반환하기 위한 지정된 장소를 알린다.

매크로 : int setjmp (jmp_buf state)

보통 호출되었을 때, setjmp는 state안에 현재 실행중인 프로그램의 상황에 대한 정보를 저장하고 0을 반환한다. 만일 longjmp가 이 state로 비-지역 분기를 수행하기 위해 나중에 사용된다면, setjmp 는 0이 아닌 값을 반환한다.

함수 : void longjmp (jmp_buf state, int value)

이 함수는 state에 저장된 상황으로 현재 실행을 되돌리고, 반환 지점을 만들었던 setjmp를 호출한 지점부터 실행을 계속한다. longjmp에 의한 setjmp로부터의 반환은 longjmp에 주어졌던 0이 아닌 인수값을 반환한다. (그러나 만일 값이 0으로 주어지면, setjmp는 0을 반환한다. )

setjmp와 longjmp의 사용에는 많이 모호하지만 중요한 제한들이 있다. 이들 제한의 대부분은 비-지역 분기가 C 컴파일 상에서 상당한 양의 신비한 힘(?)을 요구했고 이상한 방법으로 언어의 다른 부분들과 함께 영향을 끼칠 수 있기 때문에 발생한다.

setjmp함수는 함수정의가 없는 매크로이다, 그래서 당신은 `#undef를 사용하거나 또는 그것의 주소를 취하려 할 수가 없다. 그것에 더하여, setjmp를 호출하는 것은 오직 다음과 같은 경우에만 안전하다.

  • 선택이나 반복구문을 시험하기 위한 표현으로(`if' 또는 `while'와 같은)
  • 선택이나 반복구문을 시험하기 위한 표현으로 사용한 동등 또는 비교 연산자에서 사용된 피연산자의 하나로써. 그때 다른 피연산자는 정수 상수 표현이어야만 한다.
  • 선택이나 반복구문을 시험하기 위한 표현으로 사용한 단항연산자`!'의 피연산자로 사용할 때.
  • 표현식으로 자체로 사용할 때.

반환지점은 그들을 만들기 위해서 setjmp를 호출했던 그 함수가 계속 작동하고 있을때만 유용하다. 만일 당신이 이미 반환되어진 함수에서 만들었던 반환지점으로 longjmp를 호출하면, 예측할 수 없고 불행한 일들이 발생될 것이다.

당신은 longjmp의 인수로써 0이 아닌 값을 사용수도 있다. longjmp는 setjmp로부터의 반환값인 0 인수를 되돌려주기를 거부하므로, 갑자기 발생하는 실수들에 대항한 안전망으로써의 역할을 할 수도 있지만 좋은 프로그래밍 스타일은 아니다.

당신이 비-지역 분기를 수행할 때, 검색 가능한 오브젝트들은 longjmp가 호출되었을 때 그들이 가졌던 값이 무엇이든지 일반적으로 계속 유지한다. 그러나 자동 지역변수의 경우에는 setjmp가 호출된 이후에 변경된다. 즉 그들을 휘발성으로 선언하지만 않으면 그들은 변경되지 않는다.


20. 3 비-지역 분기와 신호

BSD 유닉스 시스템에서, setjmp와 longjmp는 블록된 신호들을 저장하고 반환한다; 21. 7절 [Blocking Signals] 참조. 그렇지만, POSIX. 1 표준은 setjmp와 longjmp가 블록된 신호들을 변경하지 않을 것을 요구하고, BSD처럼 행동하기 위한 함수들(sigsetjmp와siglongjmp) 두 개를 부가적으로 제공한다.

GNU 라이브러리에서 setjmp와 longjmp의 행동은 테스트 매크로에 의해 제어된다; 1. 3. 4절 [Feature Test Macros] 참조. GNU 시스템에서 디폴트로 사용하는 것은 BSD가 아니라 POSIX. 1 이다. 이 절에 있는 도구들은 헤더파일 `setjmp. h'에 선언되어 있다.

데이터 타입 : sigjmp__buf

이것은 블록된 신호들에 대한 상황 정보를 저장할 수 있다는 것을 제외하고는 jmp_buf와 유사하다.

함수 : int sigsetjmp (sigjmp_buf state, int savesigs)

이것은 setjmp와 유사하다. 만일 savesigs가 0이 아니면, 블록된 신호들의 집합은 state에 저장되고 만일 siglongjmp가 나중에 이 state를 가지고 수행되면 반환되어질 것이다

함수 : void siglingjmp (sigjmp_buf state, int value)

이것은 state인수의 타입이 다르다는 것을 제외하고는 longjmp와 유사하다. 만일 0이 아닌 savesigs 플래그를 사용했던 state를 설정하고 sigsetjmp를 호출하면, siglongjmp는 또한 블록된 신호들의 집합을 반환한다.