7.10 setjmp和longjmp函数

7.10 setjmplongjmp

C中,goto语句是不能跨越。而种跳功能的是函setjmplongjmp这两个数对生在很深的嵌套函数调用中的非常有用

一下程序7 – 4的骨干部分。其主循从标1行,然后do_line理每一入行。然后get_token从该输入行中取下一个记号。一行中的第一个记号假定是某种形式的一命令,于是switch句就实现命令选择。我的程序只理一命令,此命令cmd_add

程序7.4 进行命令处理的典型程序骨架

#include <stdio.h>

#include <stdlib.h>

#include <setjmp.h>



#define TOK_ADD 5

#define MAXLINE 128



void do_line(char *);

void cmd_add(void);

int get_token(void);



int main(void)

{

char line[MAXLINE];



while (fgets(line, MAXLINE, stdin) != NULL)

do_line(line);

exit(0);

}



char *tok_ptr; //global pointer for get_token



void do_line(char *ptr) //process one line of input

{

int cmd;



tok_ptr = ptr;

while ((cmd = get_token()) > 0) {

switch (cmd) {

case TOK_ADD:

cmd_add();

break;

}

}

}



void cmd_add(void)

{

int token;



token = get_token();

//reset of processing for this command

}



int get_token(void)

{

//fetch next token from line pointer to by tok_ptr

}

程序7 – 4命令、确定命令的型、然后用相数处理每一命令这类程序中是非常典型的。7 – 4示了用了cmd_add之后的大致使用情

动变量的存储单元在每栈桢数组linemain栈帧中,整型cmddo_line栈帧中,整型tokencmd_add栈帧中。

如上所述,种形式的安排是非常典型的,但并不要求非如此不可。并不一定要向低地址方向充。某些系统对栈有提供特殊的硬件支持,此C实现可能要用接表实现栈帧

编写如程序7 – 4这样的程序中遇到的一个问题是,如何理非致命性的错误。例如,若cmd_add数发现个错误,譬如个无效的,那么它可能先打印一消息,然后希望忽略入行的余下部分,返回main下一入行。用C言比较难做到(在本例中, cmd_add只比main两个层次,在有些程序中往往低五或更多次。)如果我不得不以查返回值的方法逐返回,那就会变得很麻

决这问题的方法就是使用非局部goto—— setjmplongjmp。非局部表示不是在数内的普通的Cg o t o句,而是在上跳若干,返回到前函数调用路上的一

#include <setjmp.h>

int setjmp(jmp_buf env) ;

返回:若直接则为0,若longjmp返回则为0

void longjmp(jmp_buf env, int val) ;

希望返回到的位置setjmp,在本例中,此位置在main中。因直接,所以其返回值0setjmp参数env是一特殊j m p _ b u f型是某种形式数组,其中存放在longjmp能用恢复栈状态的所有信息。一般, env量是全局,因另一中引用它

当检查到一个错误时,例如在cmd_add中,两个参数调longjmp。第一是在setjmp所用的env;第二v a l,是0值,它成为从setjmp返回的值。使用第二个参数的原因是于一setjmp可以有多longjmp。例如,可以在cmd_add中以v a l1longjmp,也可在get_token中以v a l2longjmp。在main中,setjmp的返回值就12,通过测试返回值就可判cmd_addget_tokenlongjmp

再回到程序例中,程序7 – 5出了修改后的maincmd_add(其他两个do_lineget_token更改)

#include <stdio.h>

#include <stdlib.h>

#include <setjmp.h>



#define TOK_ADD 5

#define MAXLINE 128



jmp_buf jmpbuffer;



void do_line(char *);

void cmd_add(void);

int get_token(void);



int main(void)

{

char line[MAXLINE];



if (setjmp(jmpbuffer) != 0)

printf("error");



while (fgets(line, MAXLINE, stdin) != NULL)

do_line(line);

exit(0);

}



char *tok_ptr; //global pointer for get_token



void do_line(char *ptr) //process one line of input

{

int cmd;



tok_ptr = ptr;

while ((cmd = get_token()) > 0) {

switch (cmd) {

case TOK_ADD:

cmd_add();

break;

}

}

}



void cmd_add(void)

{

int token;



token = get_token();

//reset of processing for this command



if (token < 0) //an error has occurred

longjmp(jmpbuffer, 1);

//reset of processing for this command

}



int get_token(void)

{

//fetch next token from line pointer to by tok_ptr

}

mainsetjmp,它所需的信息j m p b u ff e r中并返回0。然后do_line,它又c m _ a d d,假定在其中检测到一个错误。在cmd_addlongjmp之前,的形式如7 – 4中所示。但是longjmp使main数时的情,也就是抛弃cmd_adddo_line栈帧longjmp造成mainsetjmp的返回,但是,一次的返回值是1 ( longjmp的第二个参数)

      那么此时的栈帧结构就是只有main的栈帧,其他函数的都已经被抛弃了。

7.10.1 、寄存器和易失

下一个问题是:“在main中,自动变量和寄存器量的状态如何?longjmp返回到main数时量的值是否能恢复到以前setjmp的值(即回原先值),或者量的值保持为调do_line的值( do_linecmd_addcmd_addlongjmp ) ?不幸的是,问题的回答是“看情”。大多数实现并不些自动变量和寄存器量的值,而所有则说的值是不确定的。如果你有一动变量,而又不想使其值回,可定具有volatile性。全局和静态变量的值在longjmp保持不

下面我程序7 – 6明在longjmp后,自动变量、寄存器量和易失量的不同情。如果以不优化和优化此程序分别进编译,然后行它得到的果是不同的:

$ cc testjmp.c 行任何优化的编译

$ a . o u t

in f1(): count = 97, val = 98, sum = 99

after longjmp: count = 97, val = 98, sum = 99

$ cc -O testjmp.c 行全部优化的编译

$ a . o u t

in f1(): count = 97, val = 98, sum = 99

after longjmp: count = 2, val = 3, sum = 99

注意,易失( s u m )不受优化的影,在longjmp之后的值,是它在f 1的值。在我所使用的setjmp ( 3 )册页明存放在存器中的具有longjmp的值,而在CPU和浮寄存器中的恢复为调setjmp的值。就是在行程序7 – 5察到的值。

行优化,所有个变量都存放在存器中(亦即v a l的寄存器存储类被忽略)。而优化c o u n tv a l都存放在寄存器中(即使c o u n t并末register ) , volatile仍存放在存器中。通过这一例子要理解的是,如果要编写使用非局部跳的可移植程序,使volatile

程序7-6 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;

static int globval;





int main(void)

{



int autoval;

register int regival;

volatile int volaval;

static int statval;



globval = 1;

autoval = 2;

regival = 3;

volaval = 4;

statval = 5;



if (setjmp(jmpbuffer) != 0) {

printf("after longjmp : n");

printf("globval = %d, autoval = %d, regival = %d,"

"volaval = %d, statval = %dn", globval, autoval,

regival, volaval, statval);

exit(0);

}

//change varivables after setjmp ,but before longjmp

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("globval = %d, autoval = %d, regival = %d,"

"volaval = %d, statval = %dn", globval, i, j, k, l);

f2();

}



static void f2(void)

{

longjmp(jmpbuffer, 1);

}


 

注意,全局、静态和易失变量不受优化的影响。在调用longjmp后,他们的值是最近所呈现的值。存放在存储器中的变量将具有longjmp时的值,而在CPU和浮点数寄存器中的变量则恢复为调用setjmp时的值。

      在程序中,某些printf的格式字符串可能不适宜安排在程序文本的一行中,我们没有将其分成多个printf调用,而是使用了ISO C的字符串连接功能,于是两个字符串序列:

“string1” “string2”

等价于

string1string2

7.10.2 自动变量的潜在问题

前面已经说明了处理栈帧的一般方式,与此相关我们现在可以分析一下自动变量的一个潜在出错情况。基本规则是说明自动变量的函数已经返回后,就不能再引用这些自动变量。在整个UNIX手册中,关于这一点有很多警告。

程序7 – 7是一个名为open_data的函数,它打开了一个标准I / O流,然后为该流设置缓存。程序7-6 自动变量的不正确使用

#include <stdio.h>



#define DATAFILE "datafile"



FILE *open_data(void)

{

FILE *fp;

char databuf[BUFSIZ]; //setvbuf makes this the stdio buffer



if ((fp = fopen(DATAFILE, "r")) == NULL)

return NULL;

if (setvbuf(fp, databuf, _IOLBF, BUFSIZ) != 0)

return NULL;

return fp;

}



int main(int argc, char *argv[])

{

FILE *fp;

fp = open_data();

return 0;

}

问题是:当open_data返回时,它在栈上所使用的空间将由下一个被调用函数的栈帧使用。但是,标准I / O库函数仍将使用原先为databuf在栈上分配的存储空间作为该流的缓存。这就产生了冲突和混乱。为了改正这一问题,应在全局存储空间静态地(staticextern ),或者动态地(使用一种a l l o c函数)为数组databuf分配空间。

评论已关闭。