Nonlocal Jump

youngwiki
Pinkgo (토론 | 기여)님의 2025년 4월 10일 (목) 17:21 판 (Nonlocal example 1)

상위 문서: Exceptional Control Flow

개요

Nonlocal jump는 사용자 수준의 exceptional control flow을 제공한다. 이는 정상적인 호출이나 반환 순서를 거치지 않고 현재 실행 중인 한 함수에서 다른 함수로 직접 control을 전이시키는 역할을 한다.

Nolocal functions

Nonlocal jump는 setjmp(), longjmp() 함수들에 의해 구현된다.

#include <setjmp.h>
//Returns: 0 from setjmp, nonzero from longjmps
int setjmp(jmp_buf env); //세이브 포인트 찍기
int sigsetjmp(sigjmp_buf env, int savesigs);

setjmp() 함수는 현재의 호출 환경(calling environment)를 env 버퍼에 저장하여 longjmp() 함수에서 사용하도록 한 후, 0을 반환한다. 이때 호출 환경은 프로그램 카운터, 스택 포인터, 범용 레지스터(general-purpose registers)들을 포함한다. 이때, setjmp()의 반환값을 변수에 할당해서는 안된다.

#include <setjmp.h>
//never returns
void longjmp(jmp_buf env, int retval); //문제가 생겼으니 세이브 포인트로 후퇴
void siglongjmp(sigjmp_buf env, int retval);

longjmp() 함수는 env 버퍼로부터 호출 환경을 복원한 다음 해당 env를 초기화한 다음, 해당 env를 초기화한 최신의 setjmp() 함수의 return을 야기한다. 이때 setjmp() 함수는 0이 아닌 retval 값을 return하며 복귀한다.
즉, setjmp() 함수는 한 번 호출되지만 여러 번 반환된다. 한 번은 setjmp() 함수가 처음 호출되어 호출 환경이 env 버퍼에 저장될 때이고, 나머지는 각각의 longjmp() 함수 호출에 대응하여 반환될 때이다. 반면, longjmp() 함수는 한 번 호출되지만 절대 반환되지 않는다.

비유를 하자면, setjmp(), longjmp() 함수들은 닥터 스트레인지의 마법과 비슷하다. setjmp()는 닥터 스트레인지가 도르마무와 맞서기 위해서 스폰 포인트를 찍어두는 곳이고, longjmp()는 닥터 스트레인지가 도르마무에게 죽는 곳이다. 즉, 닥터 스트레인지가 도르마무에게 죽으면 특정한 시간대로 돌아 가듯이, 코드가 longjmp()에 이르면 control이 강제로 setjmp() 지점으로 이동되며, 이를 통해서 setjmp()는 두 번 반환될 수 있다.

Nonlocal jump의 중요한 용도 중 하나는, 깊이 중첩된 함수 호출 내부에서 어떤 오류 조건이 감지되었을 때 즉시 return하도록 하는 것이다. 중첩된 함수 호출 내부 깊은 곳에서 오류 조건이 발생한 경우, 호출 스택(call stack)을 힘겹게 하나하나 풀어내는 대신, nonlocal jump를 이용해 common localized error handler로 직접 return할 수 있다.
Nonlocal jump의 또 다른 중요한 용도는, 시그널 핸들러(signal handler)로부터 인터럽트(interruted)된 명령어로 복귀하는 대신, 특정 코드 위치로 분기(branch out)하는 것이다.

Nonlocal examples

Nonlocal example 1

#include "csapp.h"
jmp_buf buf; //호출 환경을 저장하는 버퍼
int error1 = 0;
int error2 = 1;

void foo(void), bar(void);
int main() { 
    switch(setjmp(buf)) { 
        case 0:  foo();  break;
        case 1:  printf("Detected an error1 condition in foo\n");  break;
        case 2:  printf("Detected an error2 condition in foo\n");  break;
        default:  printf("Unknown error condition in foo\n");
    }
    exit(0);
}
/* Deeply nested function foo */
void foo(void) {
    if (error1) longjmp(buf, 1);
    bar();
}
void bar(void) {
    if (error2) longjmp(buf, 2);
}

위는 어떻게 setjmp(), longjmp() 함수들이 어떻게 깊이 중첩된 함수 호출 내부에서 어떤 오류 조건이 감지되었을 때 즉시 return하도록 하는지 보여준다. 위 코드의 main 함수는 먼저 setjmp()를 호출하여 현재의 호출 환경을 저장하고, 이후 foo함수를 호출한다.(처음 return값은 0) foo() 함수는 bar() 함수를 다시 호출한다. 만약 foo()bar()에서 오류가 발생하면, longjmp() 함수 호출을 통해 곧바로 setjmp() 함수로 복귀하게 된다. 이때 setjmp() 함수의 반환값은 오류의 종류를 나타내며, 이를 decoding하여 하나의 코드 위치에서 처리할 수 있다.


Nonlocal example 2

각주