7.10 setjmp和longjmp函数

本文详细介绍了C语言中用于处理非致命性错误的setjmp和longjmp函数,解释了它们如何在深嵌套的函数调用中实现非局部跳转,并讨论了栈帧、自动变量、寄存器变量和易失变量在跳转前后的行为。通过实例展示了如何在main函数中使用这些函数来恢复程序流程,并分析了优化对变量存储的影响。最后,强调了使用volatile属性的重要性以确保程序的可移植性。

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函数。非局部表示这不是在一个函数内的普通的C语言g 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 l1调用longjmp,也可在get_token中以v a l2调用longjmp。在main函数中,setjmp的返回值就会是12,通过测试返回值就可判断是从cmd_add还是从get_token来的longjmp

再回到程序实例中,程序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

}


main时,调用setjmp,它将所需的信息记入变量j m p b u ff e r中并返回0。然后调用do_line,它又调用c m _ a d d,假定在其中检测到一个错误。在cmd_add中调用longjmp之前,栈的形式如图7 - 4中所示。但是longjmp使栈反绕到执行main函数时的情况,也就是抛弃了cmd_adddo_line的栈帧。调用longjmp造成mainsetjmp的返回,但是,这一次的返回值是1 ( longjmp的第二个参数)

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

7.10.1 、寄存器和易失

下一个问题是:“在main函数中,自动变量和寄存器变量的状态如何?”当longjmp返回到main函数时,这些变量的值是否能恢复到以前调用setjmp时的值(即滚回原先值),或者这些变量的值保持为调用do_line时的值( do_line调用cmd_addcmd_add又调用longjmp ) ?不幸的是,对此问题的回答是“看情况”。大多数实现并不滚回这些自动变量和寄存器变量的值,而所有标准则说它们的值是不确定的。如果你有一个自动变量,而又不想使其值滚回,则可定义其为具有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 = %d\n", 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 = %d\n", 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分配空间。

转载于:https://www.cnblogs.com/shaoguangleo/archive/2011/10/12/2806010.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值