C语⾔跳转语句(break语句,continue语句,goto语句,return语句,跳转。。
⽂章⽬录
⼀、前⾔
跳转语句(jump statement)可以中断当前程序的执⾏流程,并从另⼀个不同的点继续执⾏程序。如果程序跳转到变量的作⽤域范围之外,变量会被销毁。C 语⾔有四种语句会造成⽆条件跳转:break、continue、goto 和 return。
跳转语句的缺点是只能在函数内部之间跳转。
跳转函数setjmp和longjmp可以跨越函数跳转
⼆、跳转语句
1. break 语句
break 语句只能⽤于循环体内或 switch 语句内,并且会使得程序流跳转到该循环或该 switch 语句后⾯的
第⼀条语句。break 语句的语法如下:
break;
因此,⽆论在循环体内什么位置,break 语句都可以造成循环的结束。例如,例 1 的 while 循环可以依据⽤户的请求⽽结束(输⼊⾮数字的字符串),也可能因为数字超出程序许可范围⽽结束。
【例1】break 语句
// 读取⽤户输⼊的分数,范围在0到100之间
// 将分数存储在数组中
continue语句执行过程
// 返回值:所存储值的个数
//---------------------------------------------------------------
int getScores(short scores[],int len )
{
int i =0;
puts("Please enter scores between 0 and 100.\n"
"Press <Q> and <Return> to quit.\n");
while( i < len )
{
printf("Score No. %2d: ", i+1);
if(scanf("%hd",&scores[i])!=1)
break;// 未读到数据:结束循环
if( scores[i]<0|| scores[i]>100)
{
printf("%d: Value out of range.\n", scores[i]);
break;// 抛弃这个值,并结束循环
}
++i;
}
return i;// 已存储的数据个数
}
2. continue 语句
continue 语句只能在循环体内使⽤,并且会造成程序流跳过当前循环中尚未执⾏的代码部分。它的语法如下:
continue;
在 while 或 do…while 循环中,当遇到 continue 语句时,程序会跳转到循环的控制表达式,并进⾏下⼀次的循环条件计算。在 for 循环中,程序会跳转到循环头部的第三个表达式,并进⾏下⼀次的循环条件计算。
在例 1 中,⼀旦输⼊值超出许可范围,第⼆条 break 语句会⽴即中⽌数据输⼊循环。为了让⽤户还有机会输⼊正确的值,把第⼆条 break 语句⽤ continue 取代。那么这个程序就会跳转到 while 循环的下⼀次循环,忽略⾃增 i 的语句:
// 读取分数
// --------------------------
int getScores(short scores[],int len )
{
/* ... (同例6-7) ... */
while( i < len )
{
/* ... (同例6-7) ... */
if( scores[i]<0|| scores[i]>100)
{
printf("%d : Value out of range.\n", scores[i]);
continue;// 抛弃这个值,并读取另⼀个值
}
++i;// 已存储的数据个数加1
}
return i;// 已存储的数据个数
}
3. goto 语句
goto 语句会造成⽆条件跳转,它跳转到同⼀个函数中的另⼀条语句。跳转的⽬的地使⽤标签名称来指定,语法如下:
goto标签名称;
⼀个标签由标签名称及其后⾯的冒号组成:
标签名称:语句
标签有⾃⼰的命名空间,也就是说,标签可以使⽤与变量或类型⼀样的名称,⽽不会发⽣冲突。标签可以被放在任何语句的前⾯,并且⼀条语句也可以有多个标签。
标签的⽬的是标识 goto 语句的⽬的地,对于语句本⾝,没有任何影响,被贴上标签的语句依然可以由上⽽下顺序地执⾏。下⾯的函数在return 语句后⾯加上了标签,标记了⼀个错误处理程序的进⼊点:
// 在函数内部处理错误
// ----------------------------------
#include<stdbool.h>// 定义布尔值,true和false(C99)
#define MAX_ARR_LENGTH 1000
bool calculate(double arr[],int len,double* result )
{
bool error = false;
if( len <1|| len > MAX_ARR_LENGTH )
goto error_exit;
for(int i =0; i < len;++i )
{
/* ... ⼀些计算操作,其可能造成错误标志error被设定...
*/
if( error )
goto error_exit;
/* ... 继续计算;结果被存储到变量 *result 中...
*/
}
return true;// 如果没有错误,程序会执⾏到此处
error_exit:// 错误处理⼦程序
*result =0.0;
return false;
}
如果跳转会跨越变量的声明与初始化语句,那么就不应该利⽤goto语句从语句块外跳转到语句块内。然⽽,如果跳转跨越了对可变长度数组的定义,⽽跳到了其作⽤域的内部,那么这种跳跃是⾮法的:
static const int maxSize =1000;
double func(int n )
{
double x =0.0;
if( n >0&& n < maxSize )
{
double arr[n];// ⼀个变长度数组
again:
/* ... */
if( x ==0.0)
goto again;// 合法:在arr的作⽤域内跳转
}
if( x <0.0)
goto again;// ⾮法: 从arr的作⽤域外跳转到作⽤域内
return x;
}
如果使⽤太多 goto 语句,程序代码会变得可读性很差,因此,只有在⾮常有必要时才应该使⽤ goto 语句,⽐如从很深的嵌套循坏中跳离。实际上,在任何使⽤到 goto 语句的地⽅,都可以采⽤其他⽅式的语句进⾏改写。
goto 语句只允许进⾏局部跳转:也就是在当前所在函数的内部跳转。C 语⾔还提供了⼀个特性,允许进⾏⾮局部跳转,即可以跳转到程序的任何点,做法是利⽤标准宏 setjmp()和标准函数 longjmp()。
宏 setjmp()在程序中设置⼀个地点,将程序流的必要处理信息存储起来,这样的话,当调⽤函数 longjmp()时,就可以在任何时刻返回到该地点继续执⾏。
4. return 语句
return 语句会中⽌执⾏当前函数,跳转回到调⽤该函数的位置:
return[表达式];
这⾥的表达式会被计算,且结果会被传送给函数调⽤者,当作被调⽤函数的返回值。如有必要,返回值会被转换到被调⽤函数的返回值类型。
⼀个函数内可以有任意多个 return 语句:
// 返回两个整数类型参数中的较⼩值
int min(int a,int b )
{
if( a < b )return a;
else return b;
}
该函数体内的 if else 语句可以⽤下⾯这⼀条语句来替代:
return( a < b ? a : b );
括号不会影响 return 语句的执⾏⾏为。然⽽,复杂的 return 表达式常常被放在括号内,以提⾼代码的可阅读性。
不带任何表达式的 return 语句仅能在类型为 void 的函数中使⽤。事实上,这样的函数也根本不需要 return 语句。如果在函数内没有return 语句,程序流会在函数块尾部结束,然后返回到调⽤该函数的地⽅。
三、跳转函数
C语⾔中有⼀个goto语句,其可以结合标号实现函数内部的任意跳转(通常情况下,很多⼈都建议不要使⽤goto语句,因为采⽤goto语句后,代码维护⼯作量加⼤)。另外,C语⾔标准中还提供⼀种⾮局部跳转“no-local goto",其通过标准库<setjmp.h>中的两个标准函数setjmp和longjmp来实现。
1. C标准库<setjmp.h>
头⽂件<setjmp.h>中的说明提供了⼀种避免通常的函数调⽤和返回顺序的途径,特别的,它允许⽴即从⼀个多层嵌套的函数调⽤中返回。
1.1 etjmp
#include<setjmp.h>
int setjmp(jmp_buf env);
setjmp()宏把当前状态信息保存到env中,供以后longjmp()恢复状态信息时使⽤。如果是直接调⽤setjmp(),那么返回值为0;如果是由于调⽤longjmp()⽽调⽤setjmp(),那么返回值⾮0。setjmp()只能在某些特定情况下调⽤,如在if语句、 switch语句及循环语句的条件测试部分以及⼀些简单的关系表达式中。
1.2 longjmp
#include<setjmp.h>
void longjmp(jmp_buf env,int val);
longjmp()⽤于恢复由最近⼀次调⽤setjmp()时保存到env的状态信息。当它执⾏完时,程序就象setjmp()刚刚执⾏完并返回⾮0值val那样继续执⾏。包含setjmp()宏调⽤的函数⼀定不能已经终⽌。所有可访问的对象的值都与调⽤longjmp()时相同,唯⼀的例外是,那些调⽤setjmp()宏的函数中的⾮volatile⾃动变量如果在调⽤setjmp()后有了改变,那么就变成未定义的。
jmp_buf是setjmp.h中定义的⼀个结构类型,其⽤于保存系统状态信息。宏函数setjmp会将其所在的程序点的系统状态信息保存到某个jmp_buf的结构变量env中,⽽调⽤函数longjmp会将宏函数setjmp保存在变量env中的系统状态信息进⾏恢复,于是系统就会跳转到setjmp()宏调⽤所在的程序点继续进⾏。这样setjmp/longjmp就实现了⾮局部跳转的功能。
2. ⼀个简单的例⼦:
下⾯我们来看⼀个简单的例⼦。
#include<stdio.h>
#include<setjmp.h>
jmp_buf jump_buffer;
void func(void)
{
printf("Before calling longjmp\n");
longjmp(jump_buffer,1);
printf("After calling longjmp\n");
}
void func1(void)
{
printf("Before calling func\n");
func();
printf("After calling func\n");
}
int main()
{
if(setjmp(jump_buffer)==0){
printf("first calling set_jmp\n");
func1();
}else{
printf("second calling set_jmp\n");
}
return0;
}
代码的运⾏结果如下
lienhua34@lienhua34-laptop:~/program/test$ ./test
first calling set_jmp
Before calling func
Before calling longjmp
second calling set_jmp
通过上⾯这个简单例⼦的运⾏结果可以看出。main函数运⾏的setjmp()宏调⽤,将当前程序点的系统状态信息保存到全局变量
jump_buffer中,然后返回结果0。于是,代码打印出字符串"first calling set_jmp",然后调⽤函数func1()。在函数func1中,先打印字符串"Before calling func",然后去调⽤函数func()。现在程序控制流转到func函数中,函数func先打印字符串“Before calling longjmp",然后调⽤函数longjmp。这时候关键点到了longjmp函数将main函数中setjmp()宏调⽤设置在全局变量jump_buffer中的系统状态信息恢复到系统的相应寄存器中,导致程序的控制流跳转到了main函数中setjmp()宏调⽤所在的程序点,此时相当于第⼆次进⾏setjmp()宏调⽤,并且此时的setjmp()宏调⽤的返回不再是0,⽽是传递给函数调⽤longjmp()的第⼆个参数1。于是程序控制流转到main函数中if语句的else部分执⾏,打印字符串“second calling set_jmp“。最后,执⾏main函数中的语句“reture 0;”返回,程序运⾏结束退出。
从上⾯的运⾏过程,我们可以看出在longjmp()函数调⽤处的程序点嵌套在三层函数调⽤中:main, func1和func,但是longjmp()函数调⽤导致程序控制流跳过函数调⽤func和func1,直接回到main函数中setjmp()宏调⽤所在的程序点,然后执⾏main函数中后续的语句,从⽽忽略了函数func1和func中后续的语句部分。这就是⾮局部跳转。
3. ⾮局部跳转的实现机制
C语⾔的运⾏控制模型,是⼀个基于栈结构的指令执⾏序列,表现出来就是call/return: call调⽤⼀个函数,然后return从⼀个函数返回。在这种运⾏控制模型中,每个函数调⽤都会对应着⼀个栈帧,其中保存了这个函数的参数、返回值地址、局部变量以及控制信息等内容。当调⽤⼀个函数时,系统会创建⼀个对应的栈帧压⼊栈中,⽽从⼀个函数返回时,则系统会将该函数对应的栈帧从栈顶退出。正常的函数跳转就是这样从栈顶⼀个⼀个栈帧逐级地返回。
另外,系统内部有⼀些寄存器记录着当前系统的状态信息,其中包括当前栈顶位置、位于栈顶的栈帧位置以及其他⼀些系统信息(例如代码段,数据段等等)。这些寄存器指⽰了当前程序运⾏点的系统状态,可以称为程序点。在宏函数setjmp中就是将这些系统寄存器的内容保存到jmp_buf类型变量env中,然后在函数longjmp中将函数setjmp保存在变量env中的系统状态信息恢复,此时系统寄存器中指⽰的栈顶的栈帧就是调⽤宏函数setjmp时的栈顶的栈帧。于是,相当控制流跳过了中间的若⼲个函数调⽤对应的栈帧,到达setjmp所在那个函数的栈帧。这就是⾮局部跳转的实现机制,其不同于上⾯所说的call/return跳转机制。
正是因为这种实现机制,在上⾯的标准库说明中提到:“包含setjmp()宏调⽤的函数⼀定不能终⽌”。如果该函数终⽌的话,该函数对应的栈帧也已经从系统栈中退出,于是setjmp()宏调⽤保存在env中的内容在longjmp函数恢复时,就不再是setjmp()宏调⽤所在程序点。此时,调⽤函数longjmp()就会出现不可预测的错误。
4. ⾮局部跳转的运⽤
⾮局部跳转通常被⽤于实现将程序控制流转移到错误处理模块中;或者是通过这种⾮正常的函数返回机制,返回到之前调⽤的函数中。
最近,在我的毕业设计,我也采⽤了这种⾮局部跳转⽅式来实现错误处理机制。我的毕业设计是⽤C语⾔实现⼀个简单的scheme解析器,在该求值器对某个表达式的求值过程中可能遇到某个错误,导致这个表达式⽆效。此时,需要跳转到求值器的主循环开头,重新读取表达式,然后求值。于是,我的主循环框架就设计为:
while(1){
if(setjmp(jump_buffer)==0){
/*读取表达式
求值表达式
打印表达式的值
*/
}else{
/* 进⾏错误处理,初始化求值环境 */
}
}
其中,jump_buffer是⼀个jmp_buf类型的全局变量。循环开始时,if语句的条件判断中,setjmp保存程序点信息到全局变量jump_buffer 中,此时setjmp()宏调⽤返回值为0,然后开始读取、求值表达式。当表达式求值遇到错误时,通过执⾏函数调⽤
longjmp(jump_buffer,1);
就可以跳转到主循环的setjmp()宏调⽤所在程序点,⽽此时setjmp()宏调⽤的返回值为1,于是进⼊else部分进⾏错误处理,初始化求值环境。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。