(四)羽夏看C语言——循环与跳转

写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 (一)羽夏看C语言——简述 ,方便学习本教程。

if语句

  生活中,经常会有选择或者情况需要自己判断,计算机也是如此。所有的判断语句还是后面将要介绍的循环其实都是由JCC指令组成的。我们先给出如下代码示例:

#include <iostream>
using namespace std;
//如果是C,请自行将头文件包含改为 stdio.h 和 stdlib.h
//将 cout 改为 printf_s(可以 printf,但微软编译器编译会报错,自行科普)

int main()
{
    int c = 0;
    int re = 0;
    cout << "请输入数字:" << endl;
    cin >> c;

    if (c==0)
    {
        re = -c;
    }
    else if (c==1)
    {
        re = c + c;
    }
    else if (c==2)
    {
        re = 4;
    }
    else if (c==3)
    {
        re = c * c;
    }
    else if (c==4)
    {
        re = c + c + 5;
    }
    else if (c==5)
    {
        re = c;
    }
    else
    {
        re = -1;
    }

    system("pause");
    return 0;
}

  然后查看一下它的反汇编:

  是不是很简单粗暴,每次需要判断是不是,不是再跳转,虽然结构清晰,但生成了大量的汇编代码,影响效率,写起来也挺费劲。

switch语句

  switch语句在多情况判断上是用的最多的,是if语句的升级版,绝大多数情况比单纯的if-else高效的多,下面我们用代码揭开它神秘的面纱:

#include <iostream>
using namespace std;
//如果是C,请自行将头文件包含改为 stdio.h 和 stdlib.h
//将 cout 改为 printf_s(可以 printf,但微软编译器编译会报错,自行科普)
//将 cin 改为 scanf_s(可以 scanf,但微软编译器编译会报错,自行科普)

int main()
{
    int c = 0;
    int re = 0;
    cout << "请输入数字:" << endl;
    cin >> c;
    switch (c)
    {
        case 0:
            re = -c;
            break;
        case 1:
            re = c + c;
            break;
        case 2:
            re = 4;
            break;
        case 3:
            re = c * c;
            break;
        case 4:
            re = c + c + 5;
            break;
        case 5:
            re = c;
            break;
        default:
            re = -1;
            break;
    }

    system("pause");
    return 0;
}

  然后我们查看它的反汇编:

  让我们分析一下比较有意思的反汇编:
mov eax,dword ptr [ebp-0Ch]
mov dword ptr [ebp+FFFFFF20h],eax
cmp dword ptr [ebp+FFFFFF20h],5
ja 0047255F
mov ecx,dword ptr [ebp+FFFFFF20h]
jmp dword ptr [ecx*4+004725C8h]

  ebp-0Ch就是c的地址,它先比较这个东西是否大于5,如果大于直接到转到0x0047255F这个地址,也就是default语句,看来编译器还是挺“聪明的”。然而最“聪明”的不在这里,而是jmp dword ptr [ecx*4+004725C8h]这句汇编。让我们看看0x04725C8这个地址到底存储的是什么东西:

  首先打开内存窗口,输入那个地址,然后在内存窗口显示右键选中四个字节整数没有文本十六进制显示即可。得到如下图结果:

  如果你细心的话,你会发现这里面存储的都是每个case的地址,被称为地址表。我只需计算出一次结果,就可以跳转到我需要的位置。

  咱们举的例子是情况连续的时候,如果不连续但差距不算太大呢,我们尝试把case 3删掉,看看有什么情况出现。

- 反汇编 -

- 地址表 -

  可以看出表的成员个数不变,但被删除的case的地址处被填充了default语句的地址。编译器可以通过某种推断来实现地址表的构建提高运行效率,但是如果每个case没有任何规律可言的话,那会怎么样呢?

#include <iostream>
using namespace std;
//如果是C,请自行将头文件包含改为 stdio.h 和 stdlib.h
//将 cout 改为 printf_s(可以 printf,但微软编译器编译会报错,自行科普)
//将 cin 改为 scanf_s(可以 scanf,但微软编译器编译会报错,自行科普)

int main()
{
    int c = 0;
    int re = 0;
    cout << "请输入数字:" << endl;
    cin >> c;
    switch (c)
    {
        case 0:
            re = -c;
            break;
        case 15:
            re = c + c;
            break;
        case 200:
            re = 4;
            break;
        case 489:
            re = c + c + 5;
            break;
        case 542:
            re = c;
            break;
        default:
            re = -1;
            break;
    }

    system("pause");
    return 0;
}

  然后看一下反汇编:

  哈哈,这回编译器“找不到头脑了”,只能老老实实的用if-else的样式生成汇编了。

循环语句

  循环语句应该是编程中经常会用到的语句。所有的形式示例如下:

for语句

for (int i = 0; i < 5; i++)
{
    //do something
}

while语句

int i;
do
{
    //do something
} while (i<5);

do语句

int i;
while (i<5)
{
    //do something
}

  在汇编层面,所有循环到汇编的本质都是一样的,下面我们用代码进行验证:

#include <iostream>
//如果是C,请自行将头文件包含改为 stdio.h 和 stdlib.h

int main()
{
    int c = 0;

    //for循环
    for (int i = 0; i < 5; i++)
    {
        c++;
    }

    //do循环
    int i = 0;
    do
    {
        c++;
        i++;
    } while (i < 5);

    //while循环
    i = 0;
    while (i < 5)
    {
        c++;
        i++;
    }

    system("pause");
    return 0;
}

  然后编译运行,查看它的反汇编,结果如下:

- for循环 -

- do循环 -

- while循环 -

跳转语句

  breakcontinuegoto被我统称为跳转语句。breakcontinue语句经常在循环语句和switch语句出现,经常和if配套以判断是否不符合循环条件跳出而使用。翻译到汇编层面,它不过就是一条jmp指令,switch语句的已经体现了。goto语句翻译到汇编也是一条jmp指令,但如果处理不善,就会打乱程序执行流程出现不太可预测的结果,不太建议使用。那我们做一个循环语句的,其他自行探索实验,代码如下:

#include <iostream>
using namespace std;
//如果是C,请自行将头文件包含改为 stdio.h 和 stdlib.h
//将 cout 改为 printf_s(可以 printf,但微软编译器编译会报错,自行科普)

int main()
{
    for (int i = 0; i < 10; i++)
    {
label:
        if (i==2)
            continue;

        if (i == 7)
            goto label;

        if (i==8)
            break;
    }
    system("pause");
    return 0;
}

for each语句

  经查阅,这个语句仅在微软的编译器里面有。所以本人还是略微做一下实验,来看看for each语句到底为我们做了什么东西。在实验之前,需要通过项目属性页-C/C++-语言来关闭符合模式,代码如下:

#include <iostream>
using namespace std;
//如果是C,请自行将头文件包含改为 stdio.h 和 stdlib.h
//将 cout 改为 printf_s(可以 printf,但微软编译器编译会报错,自行科普)

int main()
{
    int nums[] = { 1,2,3,4,5,6 };
    int num = 0;
    for each (int var in nums)
    {
        num += var;
    }
    system("pause");
    return 0;
}

  然后看一下反汇编:

  一个简简单单的for each却为我们生成了好几行代码,剩下的还请自行探索。

下一篇

  (五)羽夏看C语言——结构体与类(C++)

posted @ 2021-09-03 13:01  寂静的羽夏  阅读(566)  评论(0编辑  收藏  举报