Introduction to non-local jump functions setjmp and longjmp

    The goto statement in C language cannot cross functions, it is a local jump. To perform cross-function jumps, you can use the setjmp and longjmp functions, which are very useful for dealing with errors that occur in deeply nested function calls. it works.
#include <setjmp.h>
int setjmp(jmp_buf env);
                    /* Return value: if called directly, return 0; if returned from longjmp, it is non-0 */
void longjmp(jmp_buf env, int val);

    The parameter env of setjmp is a special type jmp_buf, which is some form of array that holds all the information that can be used to restore the stack state when calling longjmp. Because the env variable needs to be referenced in another function, env is usually defined as a global variable.
    The first parameter of longjmp is the env used when calling setjmp, and the second val should be a non-zero value, which will be the value returned from setjmp. The second parameter is used because there can be multiple longjmps for a setjmp, so by testing the return value, you can determine which function the returned longjmp is located in.
    Imagine there are several function calls like this: the main function calls the do_line function, and the do_line function calls a cmd_add function. The following figure shows the usual general usage of the stack after calling cmd_add (although the stack does not necessarily expand in the low-address direction, For example, on some systems that do not provide special hardware support for stacks, stack frames may be implemented as linked lists, but this is a typical stack arrangement).

    If at some point a non-fatal error is encountered in the cmd_add function, it may become cumbersome to have to step back to main with a method that checks the return value. And if you use the setjmp and longjmp functions, you can skip several call frames on the stack and return directly to a function on the current function call path. So if setjmp is used to set the jump marker in main in advance, then when a non-fatal error is encountered in cmd_add, longjmp can be called to make the stack rewind to the situation when the main function is executed, that is, cmd_add and cmd_add are discarded. The stack frame of do_line, as shown in the following figure.

    But the next question is: when longjmp returns to the main function, can the values ​​of automatic variables and register variables, etc. be restored to the values ​​of the previous call to setjmp? Sadly, the answer to this question is "it depends". Most implementations do not rollback the values ​​of these automatic and register variables, and all standards call their values ​​indeterminate. If you have an automatic variable and you don't want its value to be rolled back, you can define it to have the volatile attribute. Values ​​declared as global and static variables persist across calls to longjmp.
    The following program illustrates what happens to various types of variables after a call to longjmp.
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

static void f1(int, int, int, int);
static void f2(void);

static jmp_buf	jmpbuffer;
int	exteval;
static int	globval;

int main(void){
	int autoval = 2;
	register int	regival	= 3;
	volatile int	volaval	= 4;
	static int	statval = 5;

	exteval = 0;
	globval = 1;

	if(setjmp(jmpbuffer) != 0){
		printf("after longjmp:\n");
		printf("exteval=%d, global=%d, autoval=%d, regival=%d, "
				"volaval=%d, statval=%d\n",
				exteval, globval, autoval, regival, volaval, statval);
		exit(0);
	}
	// Change variables after setjmp, buf before longjmp.
	exteval = 94; globval = 95; autoval = 96;
	regival = 97; volaval = 98; statval = 99;

	f1(autoval, regival, volaval, statval); // never returns
	exit(0);
}

static void f1(int i, int j, int k, int l){
	printf("in f1():\n");
	printf("exteval=%d, global=%d, autoval=%d, regival=%d, "
			"volaval=%d, statval=%d\n",
			exteval, globval, i, j, k, l);
	f2();
}

static void f2(void){
	longjmp(jmpbuffer, 1);
}

    If you run this program compiled with and without optimizations, you will get different results.
$ gcc longjmpDemo.c -o longjmpDemo.out # compile without any optimizations
$ ./longjmpDemo.out
in f1():
exteval=94, global=95, autoval=96, regival=97, volaval=98, statval=99
after longjmp:
exteval=94, global=95, autoval=96, regival=97, volaval=98, statval=99
$
$ gcc -O longjmpDemo.c -o longjmpDemo2.out # Compile with all optimizations
$ ./longjmpDemo2.out
in f1():
exteval=94, global=95, autoval=96, regival=97, volaval=98, statval=99
after longjmp:
exteval=94, global=95, autoval=2, regival=3, volaval=98, statval=99
$

    Thus, global variables, static variables and volatile variables are not affected by optimization, after longjmp, their value is the most recently presented value. The man page for setjmp states that variables stored in memory will have the value at the time of longjmp, while variables in the CPU and floating-point registers will be restored to the value at the time of the call to setjmp. Since when no optimization is performed, these variables are stored in memory (that is, the register storage class description is ignored), and after optimization, both autoval and regival are stored in registers (even if autoval is not specified with register), volatile The variable is still stored in memory, so the above output can be seen. So to write a portable program that uses non-local jumps, the volatile attribute should be used. But porting from one system to another, anything else can change.
    And with automatic variables, there is a potential error condition. The basic rule is that after the function in which the automatic variables are declared returns, these automatic variables can no longer be referenced. The following function illustrates the incorrect use of automatic variables: it sets up the buffer for a standard I/O stream it opens.
#include <stdio.h>

FILE *open_data(void){
	FILE *fp;
	char databuf[BUFSIZE];	// setvbuf makes this the stdio buffer.
	if((fp=fopen("datafile", "r")) == NULL)
		return NULL;
	if(setvbuf(fp, databuf, _IOLBF, BUFSIZE) != 0)
		return NULL;
	return fp;		// error
}

    The problem here is that when open_data returns, the space it used on the stack will be used by the stack frame of the next called function. However, the standard I/O library functions will still use this memory space as a buffer for the stream, which creates conflicts and confusion. So the correct approach should be to allocate space for the array databuf statically (such as static or extern) or dynamically in the global storage space.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326188591&siteId=291194637