NAME
setjmp, sigsetjmp, longjmp, siglongjmp - 비지역적 goto 수행하기
SYNOPSIS
#include <setjmp.h>
int setjmp(jmp_buf env);
int sigsetjmp(sigjmp_buf env, int savesigs);
noreturn void longjmp(jmp_buf env, int val);
noreturn void siglongjmp(sigjmp_buf env, int val);
glibc 기능 확인 매크로 요건 (feature_test_macros(7) 참고):
setjmp()
:- NOTES 참고.
sigsetjmp()
:_POSIX_C_SOURCE
DESCRIPTION
이 페이지에서 기술하는 함수들은 "비지역적 goto" 수행, 즉 한 함수에서 다른 함수 내의 미리 정해 둔 위치로 실행을 이전하는 데 쓴다. setjmp()
함수는 이후에 제어를 이전할 목표점을 동적으로 설정하며 longjmp()
는 실행 이전을 수행한다.
setjmp()
함수는 호출 환경에 대한 다양한 정보(보통은 스택 포인터와 인스트럭션 포인터, 그리고 아마도 다른 레지스터들의 값과 시그널 마스크)를 버퍼 env
에 저장하여 이후에 longjmp()
가 사용할 수 있게 한다. 이 경우에 setjmp()
는 0을 반환한다.
longjmp()
함수는 env
에 저장된 정보를 이용해 setjmp()
를 호출했던 지점으로 제어를 이전하고 setjmp()
호출 시점의 상태로 스택을 복원("되감기")한다. 더불어 구현에 따라서 (NOTES 참고) 다른 레지스터들의 값과 프로세스 시그널 마스크가 setjmp()
호출 시점의 상태로 복원될 수도 있다.
longjmp()
성공 후에는 setjmp()
가 두 번째로 반환된 것처럼 실행이 이어진다. 이 "가짜" 반환을 진짜 setjmp()
호출과 구별할 수 있는 것은 "가짜" 반환에서는 val
에 제공한 값을 반환하기 때문이다. 프로그래머가 실수로 val
에 0 값을 전달하면 "가짜" 반환에서는 그 대신 1을 반환하게 된다.
sigsetjmp()
와 siglongjmp()
sigsetjmp()
와 siglongjmp()
역시 비지역적 goto를 수행하되 프로세스 시그널 마스크를 예측 가능하게 처리해 준다.
sigsetjmp()
에게 준 savesigs
인자가 0이 아닌 경우에는, 그리고 그 경우에만 프로세스의 현재 시그널 마스크를 env
에 저장하며 이후 이 env
로 siglongjmp()
를 수행하면 다시 복원하게 된다.
RETURN VALUE
setjmp()
와 sigsetjmp()
는 직접 호출 시 0을 반환한다. longjmp()
나 siglongjmp()
후에 일어나는 "가짜" 반환에서는 val
로 지정한 0 아닌 값을 반환한다.
longjmp()
내지 siglongjmp()
함수는 반환하지 않는다.
ATTRIBUTES
이 절에서 사용하는 용어들에 대한 설명은 attributes(7)를 보라.
인터페이스 | 속성 | 값 |
---|---|---|
setjmp() , sigsetjmp() |
스레드 안전성 | MT-Safe |
longjmp() , siglongjmp() |
스레드 안전성 | MT-Safe |
CONFORMING TO
setjmp()
, longjmp()
: POSIX.1-2001, POSIX.1-2008, C89, C99.
sigsetjmp()
, siglongjmp()
: POSIX.1-2001, POSIX.1-2008.
NOTES
POSIX에서는 setjmp()
가 시그널 마스크를 저장하게 되는지 (그리고 이후 longjmp()
과정에서 복원하게 되는지) 여부를 명세하고 있지 않다. 시스템 V에서는 그렇게 되지 않는다. 4.3BSD에서는 그렇게 되며, 또 그렇게 되지 않는 _setjmp()
함수가 있다. 리눅스에서의 동작 방식은 glibc 버전과 기능 확인 매크로 설정에 따라 달라진다. 리눅스에서 glibc 버전 2.19 전까지는 setjmp()
가 기본적으로 시스템 V 동작 방식을 따르되, _BSD_SOURCE
기능 확인 매크로가 명시적으로 정의되어 있으며 _POSIX_SOURCE
, _POSIX_C_SOURCE
, _XOPEN_SOURCE
, _GNU_SOURCE
중 어느 것도 정의되어 있지 않으면 BSD 동작 방식을 제공한다. glibc 2.19부터는 <setjmp.h>
에서 시스템 V 버전의 setjmp()
만 드러낸다. BSD 동작 방식이 필요한 프로그램에서는 setjmp()
호출을 savesigs
인자가 0이 아닌 sigsetjmp()
호출로 교체해야 한다.
setjmp()
와 longjmp()
는 깊이 이어진 함수 호출 내의 오류를 다루는 데 유용할 수 있으며 시그널 핸들러에서 주 프로그램이 중단됐던 지점으로 돌아가는 대신 프로그램 내 특정 위치로 제어를 전달할 수도 있다. 후자에서 시그널 마스크를 이식성 있게 저장 및 복원하고 싶다면 sigsetjmp()
와 siglongjmp()
를 사용하면 된다. 아래의 프로그램 가독성에 대한 논의도 참고하라.
컴파일러가 변수들을 레지스터로 최적화할 수도 있는데 longjmp()
가 스택 포인터와 프로그램 카운터에 더해 다른 레지스터들의 값을 복원할 수도 있다. 따라서 다음 기준들에 모두 해당하는 경우에는 longjmp()
호출 후에 자동 변수의 값이 정해져 있지 않다.
-
변수가 대응하는
setjmp()
호출을 했던 함수에 로컬이다. -
그 값이
setjmp()
호출과longjmp()
호출 사이에서 바뀐다. -
volatile
로 선언되어 있지 않다.
유사한 내용이 siglongjmp()
에도 적용된다.
비지역적 goto와 프로그램 가독성
오용 가능성이 있기는 하지만 전통적인 C의 "goto" 문에는 적어도 언어적 신호(goto 문과 대상 레이블)를 통해 프로그래머가 실행 흐름을 쉽게 인식할 수 있다는 장점이 있다. 하지만 비지역적 goto에는 그런 신호가 없다. 여러 setjmp()
호출들이 동일한 jmp_buf
변수를 이용할 수도 있을 테고, 그러면 그 변수의 내용이 응용의 수명 동안 바뀔 수도 있다. 그래서 특정 longjmp()
호출의 동적인 대상을 알아내기 위해 프로그래머가 어쩔 수 없이 코드를 자세히 읽어야 할 수도 있다. (그 프로그래머를 도와 주려면 setjmp()
호출마다 각자의 jmp_buf
변수를 이용하는 게 좋다.)
더 난이도를 올리자면 setjmp()
호출과 longjmp()
호출이 같은 소스 코드 모듈에 있지 않을 수도 있다.
요약하자면 비지역적 goto는 프로그램 이해과 유지 보수를 어렵게 만들며 가능하다면 다른 대안을 사용하는 게 좋다.
경고
setjmp()
를 호출했던 함수가 longjmp()
호출 전에 반환하는 경우의 동작 방식은 규정되어 있지 않다. 어떤 미묘하거나 미묘하지 않은 혼란이 분명히 발생한다.
다중 스레드 프로그램에서 다른 스레드에서의 setjmp()
호출로 초기화 했던 env
버퍼를 longjmp()
호출에서 사용하는 경우 그 동작 방식은 규정되어 있지 않다.
POSIX.1-2008 기술 정오표 2에서 longjmp()
와 siglongjmp()
를 비동기 시그널 안전 함수 목록에 추가하였다. 하지만 그 표준에서는 시그널 핸들러에서 이 함수들의 사용을 피하기를 권하고 있으며 계속해서 지적하기를, 비동기 시그널 안전 아닌 함수에 대한 호출을 중단시켰던 시그널 핸들러에서 (또는 main()
에 대한 최초 호출에서 반환 시 일어나는 exit(3)에 해당하는 단계들처럼 동등한 무언가에서) 이 함수들을 호출한다면 프로그램에서 이후 비동기 시그널 안전 아닌 함수를 호출하는 경우 그 동작 방식이 규정되어 있지 않다. 정의 안 된 동작을 피할 유일한 방법은 다음 중 하나를 보장하는 것이다.
-
시그널 핸들러로부터 긴 점프를 한 후에는 프로그램에서 어떤 비동기 시그널 안전 아닌 함수도 호출하지 않고
main()
에 대한 최초 호출로부터 반환하지 않는다. -
모든 비동기 시그널 안전 아닌 함수 호출 동안 핸들러에서 긴 점프를 수행하는 시그널들을 차단해야 하며
main()
에 대한 최초 호출로부터 반환한 후에 어떤 비동기 시그널 안전 아닌 함수도 호출하지 않는다.
SEE ALSO
2021-03-22