共计 5861 个字符,预计需要花费 15 分钟才能阅读完成。
一、前言
在 c 语言中,当我们在处理某些异常情况的时候,经常会使用 goto 语句来进行跳转。goto 用起来很方便,但可能很多人都不知道,goto 只能在一个函数里面跳转,并不能够跨函数跳转。本文将介绍能够跨函数跳转的接口 setjmp 和 longjmp,将围绕如下内容展开:
1.goto 的局限性
2. 进程运行时的栈帧结构
3.setjmp 和 longjmp 简介和使用方法
4. 使用了全局跳转后 main 中变量的状态
二、goto 的局限性
goto 只能在同一个函数里面跳转,无法从一个函数跳转到另一个函数中。如果试图从一个函数跳转到另一个函数,则编译会报错。参考代码如下:
c
/*************************************************************************
> File Name: goto_test.c
> Author: conbiao
> Created Time: 2024 年 09 月 13 日 星期五 10 时 47 分 41 秒
************************************************************************/
/***********************************************************************
* HEADER
**********************************************************************/
#include
/***********************************************************************
* MACRO
**********************************************************************/
/***********************************************************************
* GLOBAL VARIABLE
**********************************************************************/
/***********************************************************************
* FUNCTION DESCRIPTION
**********************************************************************/
void goto_test(void);
/***********************************************************************
* FUNCTION NAME: void goto_test()
***********************************************************************
*
* Summary:
* This function is used to test goto.
*
* Params:
* NONE.
*
* Return:
* NONE.
*
***********************************************************************/
void goto_test(void)
{
int i;
start:
i = 0;
printf("%s: start!n",__func__);
while(1)
{printf("%s: i = %dn",__func__,i++);
if(i> 5)
goto main_start;
}
}
/***********************************************************************
* MAIN
**********************************************************************/
int main(int argc, char *argv[])
{
int ret = 0;
main_start:
printf("%s: start!n",__func__);
goto_test();
return ret;
}
编译结果如下:
(2-1)
如果在同一个函数中使用 goto,则能够正常跳转,代码如下:
c
/*************************************************************************
> File Name: goto_test.c
> Author: conbiao
> Created Time: 2024 年 09 月 13 日 星期五 10 时 47 分 41 秒
************************************************************************/
/***********************************************************************
* HEADER
**********************************************************************/
#include
/***********************************************************************
* MACRO
**********************************************************************/
/***********************************************************************
* GLOBAL VARIABLE
**********************************************************************/
/***********************************************************************
* FUNCTION DESCRIPTION
**********************************************************************/
void goto_test(void);
/***********************************************************************
* FUNCTION NAME: void goto_test()
***********************************************************************
*
* Summary:
* This function is used to test goto.
*
* Params:
* NONE.
*
* Return:
* NONE.
*
***********************************************************************/
void goto_test(void)
{
int i;
start:
i = 0;
printf("%s: start!n",__func__);
while(1)
{printf("%s: i = %dn",__func__,i++);
if(i> 5)
goto start;
}
}
/***********************************************************************
* MAIN
**********************************************************************/
int main(int argc, char *argv[])
{
int ret = 0;
main_start:
printf("%s: start!n",__func__);
goto_test();
return ret;
}
运行结果如下:
(2-2)
三、进程运行时的栈帧结构
![c 语言中的局部跳转以及全局跳转 c 语言中的局部跳转以及全局跳转](https://yojack.cn/wp-content/uploads/2024/09/ce8afade8a880e4b8ade79a84e5b180e983a8e8b7b3e8bdace4bba5e58f8ae585a8e5b180e8b7b3e8bdac_66eb8f6761ed1.png)
goto 只能在其调用函数的栈帧中跳转,无法从一个栈帧跳转到另一个栈帧。
四、setjmp 和 longjmp
使用 setjmp 和 longjmp 能够实现在栈帧上进行跳跃,其中 setjmp 用于设置标记,它会保存当前的执行环境(如程序计数器、栈指针等)在调用 longjmp 后,会恢复之前保存的执行环境,相当于跳转到 setjmp 的位置,。
4.1 setjmp
setjmp 的函数实现如下:
头文件: #include
函数原型 :int setjmp(jmp_buf env);
返回值:如果 setjmp 是直接调用的,它将返回 0。
如果 setjmp 是通过 longjmp 调用的,它将返回 longjmp 的第二个参数(非零值)。
传入参数:jmp_buf env:这是一个用于存储执行环境的数组类型。setjmp 会将当前的执行环境保存到这个数组中。
4.2 longjmp
longjmp 的函数原型如下:
头文件: #include
函数原型:void longjmp(jmp_buf env, int val);
传入参数:jmp_buf env:这是之前由 setjmp 保存的执行环境。
int val:这是 longjmp 返回给 setjmp 的值。通常是一个非零值,用于区分不同的跳转点。
4.3 参考代码
c
/*************************************************************************
> File Name: jump_test.c
> Author: conbiao
> Created Time: 2024 年 09 月 13 日 星期五 14 时 24 分 11 秒
************************************************************************/
/***********************************************************************
* HEADER
**********************************************************************/
#include
#include
/***********************************************************************
* MACRO
**********************************************************************/
/***********************************************************************
* GLOBAL VARIABLE
**********************************************************************/
jmp_buf env;
static int i = 0;
/***********************************************************************
* FUNCTION DESCRIPTION
**********************************************************************/
void func1(void);
/***********************************************************************
* FUNCTION NAME:
***********************************************************************
*
* Summary:
*
* Params:
*
* Return:
*
***********************************************************************/
void func1(void)
{printf("%s: start!n",__func__);
while(i
运行结果如下:
(4.3-1)
可见,使用 setjmp 和 longjmp 实现了从 func1 的栈帧跳转到 main 函数所在的栈帧的功能。
分析一下上面代码的执行过程,在 main 函数调用 setjmp 前,该进程的栈如下:
(4.3-2)
到执行 func1 函数,调用 longjmp 前,栈空间如下:
(4.3-3)
在 setjmp 时,会将当前的执行环境信息保存到 env 中,当执行 longjmp 时,栈空间会回到 4.3- 2 的情况,func1 的栈帧就没有了。
五、使用全局跳转后 main 函数中变量的状态
上文已经说明了,使用 longjmp 会使程序回退到 setjmp 前的状态。那么,如果 main 函数中在调用 setjmp 前定义了一个变量,在调用 setjmp 后改变了这个变量的值,那么当调用 longjmp 后,该变量的值是调用 setjmp 前的还是后的呢?
c 标准表示这个状态是不确定的。
如果希望调用 longjmp 后该变量的值不变,那么可以将该变量定义为全局变量或者使用 static 修饰,亦或者使用 volatile 属性声明。
参考资料:
《UNIX 环境高级编程(第 3 版)(史蒂文斯 (W.Richard Stevens) 拉戈 (Stephen A.Rago))
(Z-Library)》
文章来源: c 语言中的局部跳转以及全局跳转