【C++】setjmp,longjmp的详细使用说明

我们在写C、C++语言的时候,代码在不同逻辑间的跳转是肯定的,一般情况最常用的就是if,switch这些,但是,有些时候,逻辑变得复杂的时候,比如嵌套多层循环,在最内层循环得到结果后,事实上,上面的循环就不重要了,就需要跳出整个循环体,去执行其他业务逻辑,这个时候,如果使用goto语句,去实现就方便很多,比如下例子

int main()
{
  for (int i = 48; i < 50; i++)
  {
    for (int j = 0; j < 50; j++)
    {
      if (j == i) {
        goto label;
      }
      printf("%d\n", j);
    }
  }
label:
  printf("end");
}

虽然这么写方便了很多,但是goto语句的使用,尤其是如果大量大使用,会给程序的可读性和流程控制带来很大的负担,使程序难以理解和维护。所以大部分时候,是不建议使用的。

但是如果有程序使用goto语句可以带来很大的方便,这时候,可以使用setjmp,longjmp来替代goto

setjmp,longjmp的具体使用

举例如下

#include <stdio.h>
#include <setjmp.h>
jmp_buf buf;
int main()
{
  setjmp(buf);
  printf("执行");
  longjmp(buf,0);
}

执行如下图
在这里插入图片描述
可以看到,程序不停执行setjmp,longjmp中间的printf代码,这个执行方式就好像执行goto一样,但是看上去却没有goto那么乱

setjmp就类似于goto中需要跳转到的那个lable,longjmp就相当于goto关键字。

还有一点,goto只能在函数内部执行,但setjmp,longjmp可以在不同函数中实现跳转,例如

#include <stdio.h>
#include <setjmp.h>
jmp_buf buf;
void fun1()
{
  longjmp(buf,0);
}
int main()
{
  setjmp(buf);
  printf("执行\n");
  fun1();
}

执行结果和上面一样

setjmp,longjmp一般在使用中虽然用的少,但它的使用方式比较简单。它们有个共同的参数jmp_buffer,它是个16个元素的int数组

setjmp只有一个参数就是jmp_buf

longjmp有两个参数,第一个是jmp_buf,第二个参数是下次执行setjmp时候,要返回的值,例如

#include <stdio.h>
#include <setjmp.h>
jmp_buf buf;
void fun1()
{
  longjmp(buf,9);
}
int main()
{
  int x = setjmp(buf);
  printf("%d\n",x);
  fun1();
}

在这里插入图片描述
以上就是setjmp,longjmp的使用方式

setjmp,longjmp的实现原理

setjmp是实现了一个对当前寄存器中的值保存到jmp_buf的一个功能,而longjmp就是对jmp_buf的还原。

jmp_buf数组中不同的元素保存了不同的寄存器的值,从目前的分析来看,主要的几个寄存器在jmp_buf中的存储如下

jmp_buf[0]=ebp
jmp_buf[1]=ebx
jmp_buf[2]=edi
jmp_buf[3]=esi
jmp_buf[4]=esp
jmp_buf[5]=eip

控制一个程序流程的寄存器就是ip寄存器,它存入的就是下一条要执行的指令,同时在控制流程使用中对bp,和sp进行正确的赋值,就能控制代码执行跳转

知道了基本原理,其实自己也能实现一个最简单的setjmp,longjmp功能

实现一个简单的setjmp,longjmp

以下代码在vs2015 win32 debug模式下测试

1.首先创建一个c++默认控制台项目

2.在项目中添加一个t.asm文件,作为编写自己的setjmp,longjmp的文件,创建好文件后,在项目名称上,点击右键->生成依赖项->自定生成,在弹出的对话框中,把masm选项勾上
在这里插入图片描述
3.然后选择t.asm文件,点击右键->常规->项类型,选择Microsoft Macro Assembler
在这里插入图片描述
这样,一个可以编译汇编的项目就好了,

然后在cpp文件中,首先定义寄存器存储对象regbuff,只保存两个寄存器bp和ip

int regbuff[2];

这两个寄存器是控制函数执行以后,恢复栈、和跳转执行最重要的两个寄存器,函数执行完以后,需要恢复bp和sp,把它们恢复到执行函数前的值
定义两个函数

extern "C" int svreg(int* buffer);
extern "C" int resetreg(int* buffer);

svreg是把bp,ip保存到regbuff

resetreg是把regbuff内的值恢复到寄存器,因为ip寄存器是无法直接操作的,但是在执行一个函数时候,也就是执行

call xxx

时,在win32中,要执行的指令地址会存在sp+4的那个位置

整个cpp代码如下


#include <stdlib.h>
#include <string>
#include <locale.h>

int regbuff[2];
extern "C" int svreg(int* buffer);
extern "C" int resetreg(int* buffer);

void abc()
{
  resetreg(regbuff);
}

int main(){
  svreg(regbuff);
  printf("执行");
  abc();
}

然后在t.asm中输入函数,svreg对应的是setjmp,resetreg对应longjmp

.386
.model flat,c

.CODE 

svreg proc buffer:DWORD
  mov eax,buffer  
  mov edx, [esp+4]
  mov [eax], edx
  mov [eax+4], ebp   
  ret
svreg endp

resetreg proc buffer:DWORD
  mov ecx,buffer  
  mov ebp,[ecx+4]
  mov esp, [ecx+4] 
  jmp dword ptr [ecx]
resetreg endp

end

然后执行,可以看到,也基本实现了跳转功能

setjmp,longjmp使用,可以使得程序控制上更加方便,虽然简化了goto在流程上的混乱,但过多的使用,依然会使得程序不好控制,所以,程序还要按逻辑顺序来控制流程,这种流程控制的方式只在无法用逻辑顺序控制时,或者必须要改变逻辑时再使用

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值