1. 程式人生 > >C語言——非本地跳轉

C語言——非本地跳轉

C語言的本地跳轉是指goto語句,但是這個語句最大侷限就是隻能實現函式內部的跳轉。所以非本地跳轉就是解決這個問題。非本地跳轉是由setjmp和longjmp來完成的。

setjmp函式的原型是int setjmp(jmp_buf env),longjmp函式的原型是void long_jmp(jmp_buf env, int value)。這兩個函式都是包含在標頭檔案setjmp.h中。setjmp函式主要用來儲存當前執行狀態,作為後續跳轉的目標。呼叫setjmp時,當前狀態會被存放在env指向的結構中,這個env將被 long_jmp 操作作為引數,以返回呼叫點,跳轉的結果看起來就好像剛從setjmp返回一樣,所以這個儲存狀態的jmp_buf變數env一般定義為全域性變數。setjmp第一次呼叫setjmp的時候返回值為0;而從long_jmp操作返回時,返回值是由longjmp傳入的引數value決定。通過判斷setjmp的返回值,就可以判斷當前返回的狀態。

#include <stdio.h>
#include <setjmp.h>

jmp_buf jmpbuffer;

static void print_0(int i);
static void print_1(int i);
static void print_2(int i);
static void print_3(int i);

int main(){
	int i = 0;
	int value = 0;
	int flag;

	flag = setjmp(jmpbuffer);
	if(flag == 0){
		print_0(i);
	} else if(flag == 1){
		print_1(i);
	} else if ((flag == 2)){
		print_2(i);
	} else{
		print_3(i);
	}

	return 1;
}

static void print_0(int i){
	printf("print_0 start : i = %d\n", i);
	i += 10;
	printf("print_0 end : i = %d\n", i);

	longjmp(jmpbuffer, 1);
	printf("print_0 return");
}

static void print_1(int i){
	printf("print_1 start : i = %d\n", i);
	i += 11;
	printf("print_1 end : i = %d\n", i);

	longjmp(jmpbuffer, 2);
	printf("print_1 return");
}

static void print_2(int i){
	printf("print_2 start : i = %d\n", i);
	i += 12;
	printf("print_2 end : i = %d\n", i);

	longjmp(jmpbuffer, 3);
	printf("print_2 return");
}

static void print_3(int i){
	printf("print_3 start : i = %d\n", i);
	i += 13;
	printf("print_3 end : i = %d\n", i);

	printf("print_3 return\n");
}

上面的函式進行了幾次跳轉,由最初的setjmp返回0開始,然後是longjmp返回1,2,每次longjmp之後,函式的執行狀態回到了最初setjmp儲存的那個狀態,這個可以由下面的輸出的i的值看出。

$ ./test7-10 
print_0 start : i = 0
print_0 end : i = 10
print_1 start : i = 0
print_1 end : i = 11
print_2 start : i = 0
print_2 end : i = 12
print_3 start : i = 0
print_3 end : i = 13
print_3 return

總結:setjmp和longjmp的用法其實很簡單,用通俗的話來理解這兩個函式:這兩個函式就是提供一種功能,能夠在setjmp的時候將當前的執行情況進行儲存,然後在後來的某個時間點遇到了些問題就可以通過之前儲存的狀態恢復到setjmp那個點。同時也提供通過檢查setjmp不同返回值的情況檢查當前的執行情況。我個人認為這個與函式呼叫沒有什麼區別,棧是用來儲存函式呼叫的狀態然後返回時恢復狀態,只不過setjmp和longjmp可以跨越好長的函式呼叫棧直接回去。