标题:长跳转的实现
只看楼主
neverTheSame
Rank: 3Rank: 3
来 自:江西农业大学
等 级:新手上路
威 望:9
帖 子:1511
专家分:0
注 册:2006-11-24
结帖率:100%
 问题点数:0 回复次数:43 
长跳转的实现
*/ --------------------------------------------------------------------------------------
*/ 出自: 快乐编程 https://hi.bccn.net/108519
*/ 作者: neverTheSame E-mail:zhaoxufeng9997@ QQ:475818502
*/ 时间: 2008-6-1
*/ 声明: 尊重作者劳动,转载请保留本段文字
*/ --------------------------------------------------------------------------------------
 
长跳转的实现

你想知道goto语句都实现不了的跳转吗?
你想知道从一个函数跳转到另一个函数的内部的某个语句的原理吗?
你想知道怎么实现从一个函数跳转到另一个函数的内部的某个语句吗?

 

那么开始了解长跳转(long jump)吧。
长跳转(long jump)的原理是:设置一条语句的唯一标识(setjmp(jmp_buf jmpb)),再通过长跳转(longjmp(jmp_buf jmpb, int retval))
转到jmpb所标识的语句。

 

先看一下setjmp和longjmp这两个函数的用法吧。
函数名:  setjmp
函数原型:int _Cdecl setjmp(jmp_buf jmpb);
作用:    设置非本地跳转。即一个返回点,当程序调用longjmp函数(不论longjmp()和setjmp()是否在同一个函数或同一个作用域。)时,
          就可以返回到这个返回点,继续从这个点往下执行。
形参:    jmpb用来保护现场。
返回值:  首次调用返回0,当longjmp()返回时,调用返回值用longjmp()设定。

 

函数名:  longjmp
函数原型:void _Cdecl longjmp(jmp_buf jmpb, int retval);
作用:    返回到setjmp()所设置的返回点。
形参:    jmpb:用于恢复现场(由调用setjmp()时设置的)
          retval:返回到setjmp()所在的位置时,设置第二次setjmp()的返回值。
返回值:  无


 

再来看看它的使用例子吧。
#include<stdio.h>
#include<conio.h>
#include<setjmp.h>

void longjmpfun(jmp_buf jumpPointer);
int main(void)
{
    int value;
    jmp_buf jumpPointer;

    printf("Function \"setjmp\" return value: %d\n",
    (value=setjmp(jumpPointer)));      /*当使用longjmp跳回来的时侯,
                                       value为longjmp(jmp_buf jmpb, int retval)中的retval*/

    if(value==0)                       /*setjmp最初调用的时侯返回0*/
    {
        printf("Be about to call longjmp...\n");
        longjmpfun(jumpPointer);
    }
    else
    {
        printf("Return to \"setjmp\" function");
    }


    return 0;
}

void longjmpfun(jmp_buf jumpPointer)
{
    printf("Be in longjmpfun\n");
    longjmp(jumpPointer,10);
}

 

运行结果:
Function "setjmp" return value: 0
Be about to call longjmp...
Be in longjmpfun
Function "setjmp" return value: 10


从上面的例子中,我们可以看出setjmp与longjmp的功能强大。

 

作者建议:长跳转是汇编语言到C语言的一种延续,但一般情况不要用这种方法。这会造成程序的阅读性与可理解性的灾难。

 

讲解长跳转的知识,只是让大家了解有这么一种方法,希望大家只作学术上的探讨,不要用之于实际工作中。
搜索更多相关主题的帖子: 语句 jump goto long 函数 
2008-06-25 01:14
VxWorks
Rank: 3Rank: 3
来 自:WindRiver
等 级:论坛游民
威 望:6
帖 子:859
专家分:68
注 册:2007-11-24
得分:0 
原来会用某标准库函数就叫实现某函数,今天长见识了。
某些SB看不懂也敢加精华。

我贴这个是不是也能加精华:
setjmp and longjmp Functions
In C, we can't goto a label that's in another function. Instead, we must use the setjmp and longjmp functions to perform this type of branching. As we'll see, these two functions are useful for handling error conditions that occur in a deeply nested function call.

Consider the skeleton in Figure 7.9. It consists of a main loop that reads lines from standard input and calls the function do_line to process each line. This function then calls get_token to fetch the next token from the input line. The first token of a line is assumed to be a command of some form, and a switch statement selects each command. For the single command shown, the function cmd_add is called.

Figure 7.9. Typical program skeleton for command processing
  
#include <setjmp.h>

#define TOK_ADD    5

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) { /* one case for each command */
        case TOK_ADD:
                cmd_add();
                break;
        }
    }
}

void
cmd_add(void)
{
   int      token;
   
   token = get_token();
   /* rest of processing for this command */
}

int
get_token(void)
{
   /* fetch next token from line pointed to by tok_ptr */
}



The skeleton in Figure 7.9 is typical for programs that read commands, determine the command type, and then call functions to process each command. Figure 7.10 shows what the stack could look like after cmd_add has been called.


Figure 7.10. Stack frames after cmd_add has been called





Storage for the automatic variables is within the stack frame for each function. The array line is in the stack frame for main, the integer cmd is in the stack frame for do_line, and the integer token is in the stack frame for cmd_add.

As we've said, this type of arrangement of the stack is typical, but not required. Stacks do not have to grow toward lower memory addresses. On systems that don't have built-in hardware support for stacks, a C implementation might use a linked list for its stack frames.

The coding problem that's often encountered with programs like the one shown in Figure 7.9 is how to handle nonfatal errors. For example, if the cmd_add function encounters an errorsay, an invalid numberit might want to print an error, ignore the rest of the input line, and return to the main function to read the next input line. But when we're deeply nested numerous levels down from the main function, this is difficult to do in C. (In this example, in the cmd_add function, we're only two levels down from main, but it's not uncommon to be five or more levels down from where we want to return to.) It becomes messy if we have to code each function with a special return value that tells it to return one level.

The solution to this problem is to use a nonlocal goto: the setjmp and longjmp functions. The adjective nonlocal is because we're not doing a normal C goto statement within a function; instead, we're branching back through the call frames to a function that is in the call path of the current function.


  #include <setjmp.h>

  int setjmp(jmp_buf env);



 
Returns: 0 if called directly, nonzero if returning from a call to longjmp
 

  void longjmp(jmp_buf env, int val);


 




We call setjmp from the location that we want to return to, which in this example is in the main function. In this case, setjmp returns 0 because we called it directly. In the call to setjmp, the argument env is of the special type jmp_buf. This data type is some form of array that is capable of holding all the information required to restore the status of the stack to the state when we call longjmp. Normally, the env variable is a global variable, since we'll need to reference it from another function.

When we encounter an errorsay, in the cmd_add functionwe call longjmp with two arguments. The first is the same env that we used in a call to setjmp, and the second, val, is a nonzero value that becomes the return value from setjmp. The reason for the second argument is to allow us to have more than one longjmp for each setjmp. For example, we could longjmp from cmd_add with a val of 1 and also call longjmp from get_token with a val of 2. In the main function, the return value from setjmp is either 1 or 2, and we can test this value, if we want, and determine whether the longjmp was from cmd_add or get_token.

Let's return to the example. Figure 7.11 shows both the main and cmd_add functions. (The other two functions, do_line and get_token, haven't changed.)

Figure 7.11. Example of setjmp and longjmp
#include "apue.h"
#include <setjmp.h>

#define TOK_ADD    5

jmp_buf jmpbuffer;

int
main(void)
{
     char    line[MAXLINE];

     if (setjmp(jmpbuffer) != 0)
         printf("error");
     while (fgets(line, MAXLINE, stdin) != NULL)
        do_line(line);
     exit(0);
}

 ...

void
cmd_add(void)
{
    int     token;

    token = get_token();
    if (token < 0)    /* an error has occurred */
        longjmp(jmpbuffer, 1);
    /* rest of processing for this command */
}



When main is executed, we call setjmp, which records whatever information it needs to in the variable jmpbuffer and returns 0. We then call do_line, which calls cmd_add, and assume that an error of some form is detected. Before the call to longjmp in cmd_add, the stack looks like that in Figure 7.10. But longjmp causes the stack to be "unwound" back to the main function, throwing away the stack frames for cmd_add and do_line (Figure 7.12). Calling longjmp causes the setjmp in main to return, but this time it returns with a value of 1 (the second argument for longjmp).


Figure 7.12. Stack frame after longjmp has been called





Automatic, Register, and Volatile Variables
We've seen what the stack looks like after calling longjmp. The next question is, "what are the states of the automatic variables and register variables in the main function?" When main is returned to by the longjmp, do these variables have values corresponding to when the setjmp was previously called (i.e., are their values rolled back), or are their values left alone so that their values are whatever they were when do_line was called (which caused cmd_add to be called, which caused longjmp to be called)? Unfortunately, the answer is "it depends." Most implementations do not try to roll back these automatic variables and register variables, but the standards say only that their values are indeterminate. If you have an automatic variable that you don't want rolled back, define it with the volatile attribute. Variables that are declared global or static are left alone when longjmp is executed.

Example
The program in Figure 7.13 demonstrates the different behavior that can be seen with automatic, global, register, static, and volatile variables after calling longjmp.

If we compile and test the program in Figure 7.13, with and without compiler optimizations, the results are different:

    $ cc testjmp.c               compile without any optimization
    $ ./a.out
    in f1():
    globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
    after longjmp:
    globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
    $ cc -O testjmp.c            compile with full optimization
    $ ./a.out
    in f1():
    globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
    after longjmp:
    globval = 95, autoval = 2, regival = 3, volaval = 98, statval = 99



Note that the optimizations don't affect the global, static, and volatile variables; their values after the longjmp are the last values that they assumed. The setjmp(3) manual page on one system states that variables stored in memory will have values as of the time of the longjmp, whereas variables in the CPU and floating-point registers are restored to their values when setjmp was called. This is indeed what we see when we run the program in Figure 7.13. Without optimization, all five variables are stored in memory (the register hint is ignored for regival). When we enable optimization, both autoval and regival go into registers, even though the former wasn't declared register, and the volatile variable stays in memory. The thing to realize with this example is that you must use the volatile attribute if you're writing portable code that uses nonlocal jumps. Anything else can change from one system to the next.

Some printf format strings in Figure 7.13 are longer than will fit comfortably for display in a programming text. Instead of making multiple calls to printf, we rely on ISO C's string concatenation feature, where the sequence

    "string1" "string2"



is equivalent to

    "string1string2"



Figure 7.13. Effect of longjmp on various types of variables
#include "apue.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 variables 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);
}



We'll return to these two functions, setjmp and longjmp, in Chapter 10 when we discuss signal handlers and their signal versions: sigsetjmp and siglongjmp.

Potential Problem with Automatic Variables
Having looked at the way stack frames are usually handled, it is worth looking at a potential error in dealing with automatic variables. The basic rule is that an automatic variable can never be referenced after the function that declared it returns. There are numerous warnings about this throughout the UNIX System manuals.

Figure 7.14 shows a function called open_data that opens a standard I/O stream and sets the buffering for the stream.

Figure 7.14. Incorrect usage of an automatic variable
#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);     /* error */
}



The problem is that when open_data returns, the space it used on the stack will be used by the stack frame for the next function that is called. But the standard I/O library will still be using that portion of memory for its stream buffer. Chaos is sure to result. To correct this problem, the array databuf needs to be allocated from global memory, either statically (static or extern) or dynamically (one of the alloc functions).

为了防止世界被破坏,为了守护世界的和平,贯彻爱与真实的邪恶,可爱又迷人的反派角色,VxWorks!
Don't ask me any question.I'm just here to buy soy sauce.
2008-06-25 09:17
mqh21364
Rank: 1
等 级:新手上路
帖 子:642
专家分:0
注 册:2008-2-28
得分:0 
楼上的来自CSDN么??

前不见古人,后不见来者。念天地之悠悠,独怆然而涕下。
2008-06-25 09:33
爱喝牛奶的猫咪
Rank: 1
来 自:QQ群46520219
等 级:禁止访问
帖 子:513
专家分:0
注 册:2008-6-16
得分:0 
[bo][un]VxWorks[/un] 在 2008-6-25 09:17 的发言:[/bo]

原来会用某标准库函数就叫实现某函数,今天长见识了。
某些SB看不懂也敢加精华。

我贴这个是不是也能加精华:
 


赞一个~~~~~~~~~~~~


[color=white]<>
2008-06-25 09:46
StarWing83
Rank: 8Rank: 8
来 自:仙女座大星云
等 级:贵宾
威 望:19
帖 子:3951
专家分:748
注 册:2007-11-16
得分:0 
恩……很久以前就知道了……话说还用这个实现过C里面的TRY-CATCH呢……

专心编程………
飞燕算法初级群:3996098
我的Blog
2008-06-25 12:37
StarWing83
Rank: 8Rank: 8
来 自:仙女座大星云
等 级:贵宾
威 望:19
帖 子:3951
专家分:748
注 册:2007-11-16
得分:0 
哦,顺便说说,setjmp和longjmp必须在一个函数中,而且setjmp必须在longjmp之前,而且必须假设所有的寄存器值都已丢失,否则后果自负- -

[[it] 本帖最后由 StarWing83 于 2008-6-25 12:40 编辑 [/it]]

专心编程………
飞燕算法初级群:3996098
我的Blog
2008-06-25 12:38
Knocker
Rank: 8Rank: 8
等 级:贵宾
威 望:47
帖 子:10454
专家分:603
注 册:2004-6-1
得分:0 
又是一起“?”人相轻案例

九洲方除百尺冰,映秀又遭蛮牛耕。汽笛嘶鸣国旗半,哀伤尽处是重生。     -老K
治国就是治吏。礼义廉耻,国之四维。四维不张,国之不国。   -毛泽东
2008-06-25 12:44
VxWorks
Rank: 3Rank: 3
来 自:WindRiver
等 级:论坛游民
威 望:6
帖 子:859
专家分:68
注 册:2007-11-24
得分:0 
不是X人相轻,只是看不惯这玩意也标原创,原创了什么?
更鄙视把这个也加入精华的人。

为了防止世界被破坏,为了守护世界的和平,贯彻爱与真实的邪恶,可爱又迷人的反派角色,VxWorks!
Don't ask me any question.I'm just here to buy soy sauce.
2008-06-25 12:46
Knocker
Rank: 8Rank: 8
等 级:贵宾
威 望:47
帖 子:10454
专家分:603
注 册:2004-6-1
得分:0 
你什么地方看出是原创?

九洲方除百尺冰,映秀又遭蛮牛耕。汽笛嘶鸣国旗半,哀伤尽处是重生。     -老K
治国就是治吏。礼义廉耻,国之四维。四维不张,国之不国。   -毛泽东
2008-06-25 12:50
Knocker
Rank: 8Rank: 8
等 级:贵宾
威 望:47
帖 子:10454
专家分:603
注 册:2004-6-1
得分:0 
噢,偶看到了

九洲方除百尺冰,映秀又遭蛮牛耕。汽笛嘶鸣国旗半,哀伤尽处是重生。     -老K
治国就是治吏。礼义廉耻,国之四维。四维不张,国之不国。   -毛泽东
2008-06-25 12:50



参与讨论请移步原网站贴子:https://bbs.bccn.net/thread-221066-1-1.html




关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.225247 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved