文章目录
一、函数调用
1、面向问题
函数调用符合我们人类的一种思维方式,即解决一个问题需要几个步骤呢?首先,其次,再次,最后…以及每一步骤是不是正确工作了?
与之相对的编程语言形式就是:
#include <iostream>
using namespace std;
/*返回0 表示函数工作没有差错*/
int first(void)
{
return 0;
}
int second(void)
{
return 0;
}
int last(void)
{
return 0;
}
/*main函数 用来解决一个问题*/
int main(void)
{
/*res 用来检查每一步是否无差错的完成*/
int res = 0;
/*第一步*/
res = first();
/*第一步出现差错,不要继续啦*/
if(res)
return -1;
/*第二步*/
res = second();
/*第二步出现差错,不要继续啦*/
if(res)
return -1;
/*第三步*/
res = last();
/*第三步出现差错,不要继续啦*/
if(res)
return -1;
/*表示main解决了问题*/
return 0;
}
2、调用嵌套
实际,一个函数调用可能比上面例子更复杂:函数可以调用子函数,被调用子函数内部还能继续调用子函数呢!
这就形成了调用嵌套,相伴的问题就是函数栈帧…
假设:函数main调用了do_line函数,do_line 函数又调用了cmd_add函数
- 对于C/C++语言,进程栈上的数据应当如下图;
通常来讲,栈帧中存储的数据可能有:局部变量 、部分寄存器、函数参数、返回值、程序计数器等…
说明!!!关于函数栈的布局完全可以作为一个单独的专题,读者可以参考《程序员的自我修养—链接、装载与库》一书
二、Goto语句(短跳转)
1、面向问题
小编这里突然想到一个笑话,如果有一个函数调用不能解决的问题,一定可以用多个函数调用解决
设想,当把一个问题的解决方案分成第一步,第二步,第三步的时候,万一执行到第二步的时候出错并且需要重头从第一步做起呢? 要不我们在第二步失败的地方再调用第一步函数?(想想都觉得不靠谱)
答案就是goto语句。当然出去异常处理外,goto语句也可以用作它用,比如优化代码可读性
/*main函数 用来解决一个问题*/
int main(void)
{
label:
/*res 用来检查每一步是否无差错的完成*/
int res = 0;
/*第一步*/
res = first();
/*第一步出现差错,怎么从新从第一步开始呢?*/
goto label;
/*第二步*/
res = second();
/*第二步出现差错,怎样从第一步开始呢?*/
goto label;
/*第三步*/
res = last();
/*第三步出现差错,怎么样重头从第一步开始呢?*/
goto label;
/*表示main解决了问题*/
return 0;
}
2、基本原理
goto 语句不能跨函数调用:
goto语句简单的改变了程序计数器的指针(指针在函数体表示的范围内),从而实现程序的跳转
3、代码示例
代码示例读者可以参考另一篇文章C/C++ goto 语句
三、longjmp/setjmp(长跳转)
1、面向问题
函数错误并不是出现在一个函数体内部,而是出现在多个函数调用层次之间,这个时候goto语句是不能完成异常处理的…
比如本节的代码示例,level_two函数调用时出错,这个错误信息是没有办法通过函数返回传递给main函数的,这个时候不得不使用longjmp与setjump
2、基本原理
#include <setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
- setjmp 通过全局结构体记录这一刻栈帧状态:程序计数器,栈指针,部分自动变量等
- longjmp 通过全局结构体,强行恢复程序计数器,栈指针(部分自动变量)等从而实现程序跳转
3、代码演示
#include <iostream>
using namespace std;
#include <csetjmp>
jmp_buf jmp_buff_01;
void level_two(void)
{
cout << "level_two function" << endl;
longjmp(jmp_buff_01,1);
}
void level_one(void)
{
level_two();
}
int main(void)
{
if(!setjmp(jmp_buff_01))
{
}
else
{
cout << "error" << endl;
return -1;
}
level_one();
return 0;
}
4、引入问题
首先:
longjump 跳过的函数栈可能忘记释放动态内存!!!
其次:
通过longjmp函数,程序重新开始执行(位于进程栈中的程序计数器值回复到setjmp的状态)
但是那些位于进程栈中的自动变量是否也会自动恢复呢?答案是未定义…读者肯定会大骂 What!?
读者可以参考《UNIX环境高级编程_第二版中文》一书有关setjmp 回滚问题一书:为了避免回滚带来的局部变量不可控风险,程序作者可以尽量谨慎的设计回滚后的处理代码
四、参考书籍
《程序员的自我修养—链接、装载与库》
《UNIX环境高级编程_第二版中文》