本节必须掌握的知识点:
示例二十六
代码分析
汇编解析
示例二十七
代码分析
汇编解析
7.5.1 示例二十六
■goto语句:无条件转移语句。
语法格式:
goto label;
label :
代码;
●语法解析:
执行到goto语句时,则无条件跳转到label标记。其实就是汇编无条件跳转指令JMP,但是与JMP指令又有区别。
示例代码二十六 |
●第一步:分析需求,设计程序结构框架。
分析需求:构建一个while循环语句,当n > m时,重复执行whiler循环语句,重复语句块设了一个地址标号label:,while语句外的goto label;语句直接跳转到label地址标号处执行。
设计程序结构框架:goto语句跳入while循环结构内的重复语句。
●第二步:数据定义,定义恰当的数据结构;
int n;//定义一个int类型的整型局部变量n,并将其初始化为1。
int m;//定义一个int类型的整型局部变量m,并将其初始化为2。
●第三步:分析算法。
当执行goto语句时,直接跳过了while语句的条件判断,执行重复语句块内的n++;语句。
●第四步:编写伪代码,即用我们自己的语言来编写程序。
int main(void) {
定义一个int类型整型循环变量n=1和m=2;
goto label;跳转到label地址标号处
while (n > m){ //条件为真执行重复语句块
调用printf函数打印信息:"这句话是执行不到的"
label:
n++;
调用printf函数打印信息:("n=%d\n",n)
}
调用printf函数打印信息:"这句话可以执行到吗?\n"
system("pause");
return 0;
}
●第五步:画流程图,使用Visio、Excel或者其他绘图工具绘制算法流程和逻辑关系图; 如图7-5所示。
图7-5 示例二十六goto语句
●第六步:编写源程序,其实就是将我们的伪代码翻译成计算机语言;
/*
goto语句
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 1, m = 2;
goto label; //跳转到label地址标号处
while (n > m)
{
printf("这句话是执行不到的");
label: n++;
printf("n=%d\n", n);
}
printf("这句话可以执行到吗?\n");
system("pause");
return 0;
}
●输出结果:
n=2
这句话可以执行到吗?
7.5.2 代码分析
按照while语句的语法格式,是先判断条件再执行语句块,由于我们加了goto语句打乱了while语句的正常执行流程。
1.申请变量n、m,并赋值为n=1,m=2;
2.执行goto语句,无条件跳转到label标记的代码处;
3.执行label 标记对应的代码处,n++;【此时我们看到代码并没有执行while语句对应的表达式,也没有判断表达式的真假,而是直接跳到了while语句块内执行label标记】。
4.输出n的值,1+1=2,n=2
5.此时在判断while语句表达式是否为真,2=2,条件为假,退出循环体
6.执行printf("这句话可以执行到吗?");
7.结束程序。
结论
1.goto语句为无条件跳转语句,跳转到goto后的地址标号处。
2.goto语句破坏了循环结构,也为退出循环提供了一个便捷通道。
3.goto语句中的地址标号只能在同一个函数内,不可以是另一个函数中的地址标号。
7.5.3 反汇编解析
■汇编代码
;C标准库头文件和导入库
include vcIO.inc
.data
n sdword 1
m sdword 2
.const
szMsg1 db "这句话是执行不到的",0dh,0ah,0
szMsg2 db "n = %d",0dh,0ah,0
szMsg3 db "这句话可以执行到吗?",0dh,0ah,0
.code
start:
mov eax,m
jmp label1
.while n > eax
invoke printf,offset szMsg1
label1:
inc sdword ptr n;
invoke printf,offset szMsg2,n
.endw
invoke printf,offset szMsg3
;
invoke _getch
ret
end start
●输出结果:
n=2这句话可以执行到吗?
在上述汇编代码中,jmp label1语句对应C语言中的“goto label;”语句,跳转到地址标号label1处。紧接着.while高级汇编伪指令对应C语言中的while循环语句,不需要再多解释。
■反汇编代码
int n = 1, m = 2;
001C1046 mov dword ptr [n],1
001C104D mov dword ptr [m],2
goto label;//跳转到label地址标号处
001C1054 jmp label (01C106Bh) ;无条件跳转到label地址处
while (n > m)
001C1056 mov eax,dword ptr [n]
001C1059 cmp eax,dword ptr [m]
001C105C jle label+1Ch (01C1087h) ;n<=m时,(n=2)跳转到01C1087h地址处
{
printf("这句话是执行不到的");
001C105E push 1C3000h
{
printf("这句话是执行不到的");
001C1063 call printf (01C10B0h)
001C1068 add esp,4
label: n++;
001C106B mov ecx,dword ptr [n]
001C106E add ecx,1
001C1071 mov dword ptr [n],ecx
printf("n=%d\n", n);
001C1074 mov edx,dword ptr [n]
001C1077 push edx
001C1078 push 1C3014h
001C107D call printf (01C10B0h)
001C1082 add esp,8
}
001C1085 jmp main+16h (01C1056h) ;无条件跳转到while语句起始位置
printf("这句话可以执行到吗?\n");
001C1087 push 1C301Ch
001C108C call printf (01C10B0h)
001C1091 add esp,4
system("pause");
001C1094 push 1C3034h
001C1099 call dword ptr [__imp__system (01C2078h)]
001C109F add esp,4
return 0;
上述反汇编代码将goto语句翻译为无条件跳转指令jmp语句,while语句翻译为CMP/JCC指令+JMP无条件跳转指令。请读者参照代码注释,分析程序的执行流程。
■break语句
在循环中存在break语句,执行它相当于退出循环,跳转到循环语句块外的下一条语句地址处,详见“实验四十九for语句表现形式2的示例代码7-3-3.c”。
在6.3节switch语句中必然包含break语句。请读者回顾示例代码二十一的反汇编代码,查看break语句的跳转地址(跳出了switch语句块)。
此外,在7.1节do while语句的实验四十二中,do while语句嵌套的switch语句中也包含break语句。希望读者认真分析其反汇编代码,查看break语句的跳转地址。
基于上述break语句的示例,本节将不再重复介绍break语句的实现。
7.5.4 示例二十七
■continue语句
我们用例子来看continue语句的用法。
示例代码二十七 |
●第一步:分析需求,设计程序结构框架。
分析需求:构建一个for循环语句,当循环变量i <= 10时,重复执行for循环语句块,重复语句块嵌套了一个if条件语句块,当循环变量i%2的模不为0时,执行if语句块,当执行到continue语句时,跳出本轮for循环,i++后执行下一轮循环。
设计程序结构框架:for语句嵌套if语句块,if语句块内的continue语句打断本轮循环,直接执行下一轮循环。
●第二步:数据定义,定义恰当的数据结构;
for语句的条件表达式中定义循环变量int i=1;。
●第三步:分析算法。
当执行for循环语句时,如果if语句的条件判断为真,执行if语句块,当执行到continue语句时退出本轮for循环,进入下一轮循环。如果if语句条件表达式为假,执行printf("i = %d\n", i);。
●第四步:编写伪代码,即用我们自己的语言来编写程序。
int main(void) {
定义一个for循环语句; 表达式1:int i=1;
表达式2:i <= 10;
表达式3:i++;
for (表达式1;表达式2;表达式3){ //条件为真执行重复语句块
if(i%2)条件为真执行if语句块{
调用printf函数打印信息:("%d\n", i);
continue;// 执行到continue;时后面的printf(“■”);将不执行,被跳过。
调用printf函数打印信息:("■");
}
调用printf函数打印信息:("i = %d\n", i);
}
system("pause");
return 0;
}
●第五步:画流程图,使用Visio、Excel或者其他绘图工具绘制算法流程和逻辑关系图;如图7-6所示。
图7-6 示例二十七continue语句
●第六步:编写源程序,其实就是将我们的伪代码翻译成计算机语言;
/*
continue语句
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
for (int i = 1; i <= 10; i++)
{
if (i % 2)
{
printf("%d\n", i);
continue;//执行continue;后面的printf(“■”);将不执行,被跳过。
printf("■");
}
printf("i = %d\n", i);
}
system("pause");
return 0;
}
●输出结果:
1
i = 2
3
i = 4
5
i = 6
7
i = 8
9
i = 10
请按任意键继续. . .
7.5.5 代码分析
1.当循环变量i=1时,for循环条件表达式为真,执行for循环语句块;
2.循环变量i=1时,i%2的模为1,if语句条件表达式为真,执行if语句块;
首先执行printf("%d\n", i);,然后执行continue语句,参考continue语句的流程图,我们清晰地看到continue语句跳过了printf("■");语句,直接跳到了n++;语句,然后开始下一轮循环。
3.当循环变量i=2时,for循环条件表达式为真,执行for循环语句块;
4.循环变量i=2时,i%2的模为0,if语句条件表达式为假,执行printf("i = %d\n", i);然后执行n++;语句,开始下一轮循环。
7.5.6 汇编解析
■汇编代码
;C标准库头文件和导入库
include vcIO.inc
.data
i sdword ?
.const
szMsg1 db "%d",0dh,0ah,0
szMsg2 db "■",0
szMsg3 db "i = %d",0dh,0ah,0
.code
start:
mov sdword ptr i,1
next1:
.while i <= 10
mov eax,i
mov ebx,2
cdq
idiv ebx
.if edx
invoke printf,offset szMsg1,i
jmp next2 ;continue语句
invoke printf,offset szMsg2
.else
invoke printf,offset szMsg3,i
.endif
next2:
inc sdword ptr i;
jmp next1
.endw
;
invoke _getch
ret
end start
●输出结果:
1
i = 2
3
i = 4
5
i = 6
7
i = 8
9
i = 10
结论
上述汇编代码使用.while和.if/.else语句,等同于C语言中的for语句和if/else语句。比较有意思的是:汇编代码使用jmp无条件跳转指令实现了continue语句。对比C语言,汇编代码清晰的标注了跳转的地址标号,而C语言省略了地址标号,仅此而已。
■反汇编代码
for (int i = 1; i <= 10; i++)
01341044 mov dword ptr [ebp-4],1 ;表达式1:循环变量i初始化为1
for (int i = 1; i <= 10; i++)
0134104B jmp main+16h (01341056h) ;跳转到表达式2
0134104D mov eax,dword ptr [ebp-4] ;表达式3:i++
01341050 add eax,1
01341053 mov dword ptr [ebp-4],eax
01341056 cmp dword ptr [ebp-4],0Ah ;表达式2:条件判断语句
0134105A jg main+63h (013410A3h) ;如果变量i>10则退出循环
{
if (i % 2)
0134105C mov ecx,dword ptr [ebp-4] ;取变量i的值送入ecx寄存器
0134105F and ecx,80000001h ;这里是否还有印象?
01341065 jns main+2Ch (0134106Ch) ;正整数则跳转
01341067 dec ecx ;判断负整数
01341068 or ecx,0FFFFFFFEh
0134106B inc ecx
0134106C test ecx,ecx
0134106E je main+50h (01341090h) ;ecx为0(偶数)跳转到01341090h地址处
{
printf("%d\n", i);奇数则打印信息
01341070 mov edx,dword ptr [ebp-4]
01341073 push edx
01341074 push 1343000h
01341079 call printf (013410C0h)
0134107E add esp,8
continue;//执行到continue;时后面的printf(“■”);将不执行,被跳过。
01341081 jmp main+0Dh (0134104Dh) ;无条件跳转到循环语句的表达式3
printf("■");
01341083 push 1343004h
01341088 call printf (013410C0h)
0134108D add esp,4
}
printf("i = %d\n", i);
01341090 mov eax,dword ptr [ebp-4]
01341093 push eax
01341094 push 1343008h
01341099 call printf (013410C0h)
0134109E add esp,8
}
013410A1 jmp main+0Dh (0134104Dh) ;无条件跳转到循环语句的表达式3
system("pause");
013410A3 push 1343010h
system("pause");
013410A8 call dword ptr [__imp__system (01342078h)]
013410AE add esp,4
return 0;
请读者仔细阅读代码注释,此处不再赘述。
总结
1.如果continue语句是在for语句大括号内,当continue语句被执行时,表示结束一轮循环、直接进入下一循环。也就是说continue语句后面的语句不被执行。
2.正确使用goto、break和continue语句时,这两个语句的执行速度会比结构化程序设计技术的执行速度更快。
3.break和continue语句用于改变控制流。当while、for、do/while或switch语句中执行break语句时,break语句会造成程序从语句退出,程序会接着执行该语句之后的第一条语句。
4.break语句的常规用途时从循环语句中退出,或跳过switch语句中剩余部分。
5.continue语句的作用是跳过剩余语句,并执行循环的下一次迭代。
练习
1、使用goto语句,求 1+2+3+......+100的和。
2、请修改示例代码二十七,不使用continue语句,实现相同的功能。
3、请修改“实验四十九for语句表现形式2的示例代码7-3-3.c”,不使用break语句实现相同的功能。
本文摘自编程达人系列教材《汇编的角度——C语言》。