c语言中的局部跳转以及全局跳转

一、前言

在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<stdio.h>


/***********************************************************************
 *                              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 = %d\n",__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<stdio.h>


/***********************************************************************
 *                              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 = %d\n",__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)

三、进程运行时的栈帧结构

https://editor.csdn.net/md/?articleId=142054973一节中已经介绍过c程序运行时的存储空间布局,这其实是一个进程的进程空间结构。其中,栈空间用于存储程序在运行过程中使用到的临时变量,在进程空间中,系统为每个函数分配一个栈帧,结构如下:

goto只能在其调用函数的栈帧中跳转,无法从一个栈帧跳转到另一个栈帧。

四、setjmp和longjmp

使用setjmp和longjmp能够实现在栈帧上进行跳跃,其中setjmp用于设置标记,它会保存当前的执行环境(如程序计数器、栈指针等)在调用longjmp后,会恢复之前保存的执行环境,相当于跳转到setjmp的位置,。

4.1 setjmp

setjmp的函数实现如下:

头文件: #include <setjmp.h>
函数原型 : int setjmp(jmp_buf env);
返回值

如果 setjmp 是直接调用的,它将返回 0。

如果 setjmp 是通过 longjmp 调用的,它将返回 longjmp 的第二个参数(非零值)。
传入参数

jmp_buf env:这是一个用于存储执行环境的数组类型。setjmp 会将当前的执行环境保存到这个数组中。

4.2 longjmp

longjmp的函数原型如下:

头文件: #include <setjmp.h> 函数原型: 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<stdio.h>
#include<setjmp.h>

/***********************************************************************
 *                              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 < 8)
    {
        longjmp(env,i++);
    }

}


/***********************************************************************
 *                                MAIN
 **********************************************************************/
int main(int argc, char *argv[])
{
    int ret = 0;

    ret = setjmp(env);
    if(ret == 0)
    {
        printf("%s: This is the first call setjump!\n",__func__);
    }
    else
    {
        printf("%s: This is return from longjmp! ret = %d\n",__func__,ret);
    }

    func1();


    return ret;
}

运行结果如下:

(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)》

相关推荐
BoomHe1 小时前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农9 小时前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少9 小时前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker9 小时前
一杯美式搞定 Kotlin 空安全
android·kotlin
三少爷的鞋10 小时前
Android 协程时代,Handler 应该退休了吗?
android
火柴就是我1 天前
让我们实现一个更好看的内部阴影按钮
android·flutter
RuoZoe1 天前
重塑WPF辉煌?基于DirectX 12的现代.NET UI框架Jalium
c语言
砖厂小工1 天前
用 GLM + OpenClaw 打造你的 AI PR Review Agent — 让龙虾帮你审代码
android·github
张拭心1 天前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
张拭心1 天前
Android 17 来了!新特性介绍与适配建议
android·前端