张洋铭 發表於 2023-8-24 15:31:00

5.13 汇编语言:仿写For循环语句

<p>循环语句(for)是计算机编程中的一种基本控制结构,它允许程序按照指定的次数或范围重复执行一段代码块。for循环在处理需要进行迭代操作的情况下非常有用,它使得程序可以更加方便地控制循环的次数。一般来说,for循环由三个部分组成:初始化部分、条件表达式和更新部分,以及一个需要重复执行的代码块。在每次循环迭代开始时,程序首先执行初始化部分,然后检查条件表达式的值,如果为真,则执行代码块,并在每次循环结束后执行更新部分。只要条件表达式为真,for循环就会一直重复执行;一旦条件表达式为假,循环将停止,程序继续执行循环之后的代码。</p>
<h3 id="1114-for-循环结构优化">11.14 FOR 循环结构优化</h3>
<p>For语句先初始化条件变量,然后在判断是否符合条件,符合则执行循环体,不符合则跳过执行。For循环结构的效率最低,该语句的构建往往需要三个跳转来实现,首先需要初始化变量此处要进行一次判断,其次是内部循环体需要另一个判断通常用于实现跳出循环体,最后一步则需要一个无条件跳转指令跳回到循环首地址,但在开启了O2优化时编译器也会尽可能将其转换为While语句,如果可以还会继续将While转为带有IF语句的Do循环来提高执行效率。</p>
<pre><code class="language-ASM">.386p
.model flat,stdcall
option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
count DWORD ?
.code
main PROC
    mov dword ptr ds:,0          ; 设置 int x = 0;
    jmp L2

L1:
    mov eax,dword ptr ds:      ; x = x++
    add eax,1
    mov dword ptr ds:,eax

L2:
    cmp dword ptr ds:,10         ; 比较 x &lt; 10
    jge lop_end
   
    xor eax,eax                         ; 执行循环体
    jmp L1
   
lop_end:
    int 3
    invoke ExitProcess,0
main ENDP
END main
</code></pre>
<p>虽然For语句在执行效率上来说是最低的,但该语句的使用确是最符合我们思维方式的,在高级语言中应用最为广泛,例如在Python中For循环体被简化成了<code>for x in range(2,10)</code>它可以指定一个循环范围,该语句利用汇编完全也可以被构建出来,我们接着尝试构建一下这个特别的循环体。</p>
<pre><code class="language-ASM">.386p
.model flat,stdcall
option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
start_count DWORD ?
end_count DWORD ?
.code
main PROC
    mov dword ptr ds:,2   ; 指定开始循环编号
    mov dword ptr ds:,5       ; 指定结束循环编号
   
    mov ecx,dword ptr ds:
L1:
    cmp dword ptr ds:,ecx
    jle lop_end
   
    xor eax,eax                        ; 循环体内部
   
    add ecx,1                            ; 每次递增
    mov dword ptr ds:,ecx
    jmp L1
   
lop_end:
    int 3
    invoke ExitProcess,0
main ENDP
END main
</code></pre>
<h3 id="1120-仿写for水仙花数">11.20 仿写For水仙花数</h3>
<p>该C++代码实现了水仙花数的查找算法,水仙花数是指一个三位数,它的每个位上的数字的立方和等于它本身。在循环中,遍历<code>100~999</code>之间的每一个数,将其分解为三个数(百、十、个位),再将三个数分别平方并相加,判断与原数是否相等,如果相等则输出该数即为水仙花数。</p>
<ul>
<li>例如: 153是一个水仙花数,因为153等于1的3次方加上5的3次方加上3的3次方</li>
</ul>
<pre><code class="language-C">#include &lt;stdio.h&gt;
#include &lt;Windows.h&gt;

int main(int argc, char *argv[])
{
int x, y, z, n;
for (n = 100; n &lt; 1000; n++)
{
    x = n / 100;          // 分解百位
    y = n / 10 % 10;      // 分解十位
    z = n % 10;         // 分解个位
    if (x * 100 + y * 10 + z == x*x*x + y*y*y + z*z*z)
    {
      printf("水仙花: %-5d \n", n);
    }
}

system("pause");
return 0;
}
</code></pre>
<p>尝试使用汇编实现计算逻辑,这段代码没有任何难度,因为并不会涉及到嵌套循环的问题,只是在计算四则运算时需要格外注意些。</p>
<pre><code class="language-ASM">.386p
.model flat,stdcall
option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
x DWORD ?
y DWORD ?
z DWORD ?
n DWORD ?
szFmt BYTE '水仙花: %-5d ',0dh,0ah,0

.code
main PROC
    mov dword ptr ds:,100   ; n = 100
    jmp L1
L2: mov eax,dword ptr ds:
    add eax,1                  ; n++
    mov dword ptr ds:,eax
L1: mov eax,dword ptr ds:
    cmp eax,1000               ; n &lt; 1000
    jge lop_end
   
    mov eax,dword ptr ds:
    cdq
    mov ecx,100                  ; x = n / 100;
    idiv ecx
    mov dword ptr ds:,eax
   
    mov eax,dword ptr ds:
    cdq
    mov ecx,10
    idiv ecx                     ; y = n / 10;
    cdq
    mov ecx,10
    idiv ecx                     ; y = y % 10;
    mov dword ptr ds:,edx
   
    mov eax,dword ptr ds:
    cdq
    mov ecx,10
    idiv ecx                     ; z = n % 10;
    mov dword ptr ds:,edx
   
    ; 开始执行if()比较语句
    imul eax,dword ptr ds:,100; x * 100
    imul ecx,dword ptr ds:,10   ; y * 10
    add eax,dword ptr ds:       ; + z
    add ecx,eax
   
    mov edx,dword ptr ds:
    imul edx,dword ptr ds:      ; x*x*x
    imul edx,dword ptr ds:
   
    mov eax,dword ptr ds:
    imul eax,dword ptr ds:      ; y*y*y
    imul eax,dword ptr ds:
    add edx,eax
   
    mov eax,dword ptr ds:
    imul eax,dword ptr ds:      ; z*z*z
    imul eax,dword ptr ds:
    add edx,eax
   
    cmp ecx,edx   ; (x * 100 + y * 10 + z) == (x*x*x + y*y*y + z*z*z)
    jne L2
   
    mov eax,dword ptr ds:
    invoke crt_printf,addr szFmt,eax
    jmp L2
   
lop_end:
    int 3

main ENDP
END main
</code></pre>
<h3 id="1121-for循环尝试判断">11.21 For循环尝试判断</h3>
<p>该C++代码实现了一个简单的循环,遍历数组中的所有元素并输出大于等于50的元素。在循环中,通过判断Array数组中每个元素与50的大小关系,如果元素大于等于50,则使用printf函数输出该元素的值。最终程序输出所有大于等于50的元素。</p>
<pre><code class="language-C">#include &lt;stdio.h&gt;
#include &lt;Windows.h&gt;

int main(int argc,char *argv[])
{
int Array = { 56,78,33,45,78,90,32,44,56,67 };

for (int x = 0; x &lt; 10; x++)
{
    if (Array &gt;= 50)
    {
      printf("out -&gt; %d \n", Array);
    }
}
return 0;
}
</code></pre>
<p>上述C语言代码如果通过汇编语言实现可以写成如下样子,读者可自行理解流程;</p>
<pre><code class="language-ASM">.386p
.model flat,stdcall
option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
MyArray DWORD 56,78,33,45,78,90,32,44,56,67
count DWORD ?
szFmt BYTE 'out -&gt; %d ',0dh,0ah,0

.code
main PROC
   
    mov dword ptr ds:,0      ; int x = 0
    jmp L1
L2: mov eax,dword ptr ds:
    add eax,1                     ; x ++
    mov dword ptr ds:,eax
L1:
    cmp dword ptr ds:,10   ; x &lt; 10
    jge lop_end
   
    mov eax,dword ptr ds:          ; 获取循环次数,当作因子
    lea esi,dword ptr ds:      ; 取数组基地址
    mov ebx,dword ptr ds:; 因子寻址
    cmp ebx,50
    jl L2                                 ; 如果小于50则跳转到下一次循环
   
    invoke crt_printf,addr szFmt,ebx      ; 调用系统crt
    jmp L2

lop_end:
    int 3

    invoke ExitProcess,0
main ENDP
END main
</code></pre>
<p>在读者学会了上述代码编写之后,我们继续增加代码的复杂度,如下所示代码实现了对整型数组的最大值、最小值、元素总和以及平均值的计算。在循环中,通过依次遍历数组中的每一个元素,维护一个当前最大值<code>max_result</code>和最小值<code>min_result</code>,并对元素进行累加求和,最终计算出数组中所有元素的平均值<code>avg_result</code>。代码中使用<code>printf</code>函数输出求得的四个值<code>(max、min、sum、avg)</code>,并使用<code>system</code>函数暂停程序以便观察输出结果。</p>
<pre><code class="language-C">#include &lt;stdio.h&gt;
#include &lt;Windows.h&gt;

int main(int argc, char *argv[])
{
int Array = { 56,78,33,45,78,90,32,44,56,67 };
int max_result = 0,min_result = 100,sum_result = 0,avg_result = 0;

for (int x = 0; x &lt; 10; x++)
{
    if (Array &gt;= max_result)
    {
      max_result = Array;
    }
    if (Array &lt;= min_result)
    {
      min_result = Array;
    }
    sum_result = sum_result + Array;
    avg_result = sum_result / 10;
}
printf("max = %d min = %d sum = %d avg = %d \n", max_result,min_result,sum_result,avg_result);
system("pause");
return 0;
}
</code></pre>
<p>上述代码读者可尝试使用汇编语言来实现一下,如下代码是笔者思考后编写出来的实现流程,读者可自行对照参考;</p>
<pre><code class="language-ASM">.386p
.model flat,stdcall
option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
MyArray DWORD 56,78,33,45,78,90,32,44,56,67
count DWORD ?
max_result DWORD 0
min_result DWORD 100
sum_result DWORD 0
avg_result DWORD 0

szFmt BYTE 'max = %d min= %d sum= %d avg = %d ',0dh,0ah,0

.code
main PROC
    mov dword ptr ds:,0      ; int x = 0
    jmp L1
L2: mov eax,dword ptr ds:
    add eax,1                     ; x ++
    mov dword ptr ds:,eax
L1:
    cmp dword ptr ds:,10   ; x &lt; 10
    jge lop_end

    mov eax,dword ptr ds:
    lea esi,dword ptr ds:
   
    mov ebx,dword ptr ds:
    cmp ebx,dword ptr ds:      ; Array &gt;= max_result
    jl L3
    mov dword ptr ds:,ebx      ; max_result = Array;
L3:
    mov ebx,dword ptr ds:
    cmp ebx,dword ptr ds:      ; Array &lt;= min_result
    jg L4
    mov dword ptr ds:,ebx

L4:
    mov ebx,dword ptr ds:   ; Array
    add dword ptr ds:,ebx      ; sum_result = sum_result + Array;
   
    mov eax,dword ptr ds:
    cdq                                    ; 符号扩展
    mov ecx,10                           ; / 10
    idiv ecx                               ; sum_result / 10;
    mov dword ptr ds:,eax      ; avg_result
    jmp L2
   
lop_end:
    mov eax,dword ptr ds:
    mov ebx,dword ptr ds:
    mov ecx,dword ptr ds:
    mov edx,dword ptr ds:
    invoke crt_printf,addr szFmt,eax,ebx,ecx,edx
    int 3
main ENDP
END main
</code></pre>
<h3 id="1122-for循环多重if判断">11.22 For循环多重IF判断</h3>
<p>该C++代码实现了对两个数组进行元素相加,并输出相加结果的奇偶性。在循环中,对<code>SrcArray</code>和<code>DstArray</code>两个数组中的元素相加,如果两个元素均不为0,则判断相加的结果是否为偶数,如果是,则使用printf函数输出偶数sum的形式,否则输出基数sum的形式。其中sum表示两个元素相加的结果。代码中使用system函数暂停程序以便观察输出结果。</p>
<pre><code class="language-C">#include &lt;stdio.h&gt;
#include &lt;Windows.h&gt;

int main(int argc, char *argv[])
{
int SrcArray = { 56,78,33,45,78,90,32,15,56,67 };
int DstArray = { 59,77,89,23,11,45,67,88,93,27 };
int index = 0;

for (index = 0; index &lt; 10; index++)
{
    if (SrcArray != 0 &amp;&amp; DstArray != 0)
    {
      int sum = SrcArray + DstArray;
      if (sum % 2 == 0)
      printf("偶数: %d \n", sum);
      else
      printf("基数: %d \n", sum);
    }
}
system("pause");
return 0;
}
</code></pre>
<p>上述代码片段的逻辑并不复杂,仅仅只是循环内部嵌套双层判断,笔者思考片刻后即写出了与之对应的汇编代码;</p>
<pre><code class="language-ASM">.386p
.model flat,stdcall
option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
SrcArray DWORD 56,78,33,45,78,90,32,15,56,67
DstArray DWORD 59,77,89,23,11,45,67,88,93,27
index DWORD 0
sum DWORD 0

szFmt1 BYTE '基数: %d ',0dh,0ah,0
szFmt2 BYTE '偶数: %d ',0dh,0ah,0

.code
main PROC
    mov dword ptr ds:,0      ; index = 0
   
    jmp L1
L2: mov eax,dword ptr ds:
    add eax,1                         ; index++
    mov dword ptr ds:,eax
L1:
    cmp dword ptr ds:,10       ; index &lt; 10
    jge lop_end
   
    mov eax,dword ptr ds:;
    cmp dword ptr ds:,0
    je L2                                     ; SrcArray != 0
   
    mov eax,dword ptr ds:
    cmp dword ptr ds:,0   ; DstArray != 0
    je L2
   
    ; ------------------------------------------
    ; 另类加法,通过一个SrcArray定位DstArray完成加法
   
    mov eax,dword ptr ds:               ; 获取因子
    lea esi,dword ptr ds:            ; 取数组首地址
   
    mov ebx,dword ptr ds:         ; 获取 SrcArray
    mov ecx,dword ptr ds:    ; 获取 DstArray
    add ebx,ecx                                  ; SrcArray + DstArray
    mov dword ptr ds:,ebx                   ; sum = SrcArray + DstArray
   
    mov eax,dword ptr ds:
    and eax,080000001h                           ; sum % 2 == 0
    test eax,eax
    jne L3
   
    invoke crt_printf,addr szFmt2,dword ptr ds:; 偶数输出
    jmp L2
L3:
    invoke crt_printf,addr szFmt1,dword ptr ds:; 基数输出
    jmp L2
lop_end:
    int 3

main ENDP
END main
</code></pre>
<h3 id="1123-for嵌套乘法口诀表">11.23 For嵌套乘法口诀表</h3>
<p>该C++代码实现了乘法口诀表的打印。在两个for循环中,分别对x和y进行遍历,对每一次的遍历输出一个乘法口诀表的元素。代码中使用printf函数实现输出,并使用<code>\n</code>进行换行。程序遍历打印了从11到99的所有乘积的结果,这就是乘法口诀表。</p>
<pre><code class="language-C">#include &lt;stdio.h&gt;
#include &lt;Windows.h&gt;

int main(int argc, char *argv[])
{
for (int x = 1; x &lt; 10; x++)
{
    for (int y = 1; y &lt;= x; y++)
    {
      int result = x*y;
      printf("%d*%d=%-3d", y, x, result);
    }
    printf("\n");
}
system("pause");
return 0;
}
</code></pre>
<p>乘法口诀表的实现方法只需要嵌套两层FOR循环语句,在使用汇编语言实现之前我们可以先来构建出这个双层循环体,如下代码所示;</p>
<pre><code class="language-ASM">.386p
.model flat,stdcall
option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
x DWORD ?
y DWORD ?
szFmt BYTE '内层循环: %d 外层循环: %d ',0dh,0ah,0
szPrBYTE '-----&gt;',0dh,0ah,0

.code
main PROC
    mov dword ptr ds:,1      ; int x = 1
    jmp L1
L2: mov eax,dword ptr ds:
    add eax,1                     ; x++
    mov dword ptr ds:,eax
L1:
    cmp dword ptr ds:,10       ; x &lt; 10
    jge lop_end

    mov dword ptr ds:,1      ; y = 1
    jmp L3
L5: mov eax,dword ptr ds:
    add eax,1                     ; y++
    mov dword ptr ds:,eax
L3:
    mov eax,dword ptr ds:
    cmp eax,dword ptr ds:      ; y &lt;= x
    jg L4
   
    ; 执行的是循环体内部
    mov eax,dword ptr ds:
    mov ebx,dword ptr ds:
    invoke crt_printf,addr szFmt,eax,ebx
   
    jmp L5
L4:
    ; 执行外层循环
    invoke crt_printf,addr szPr

    jmp L2
lop_end:
    int 3

main ENDP
END main
</code></pre>
<p>当有了双层循环体结构之后,我们只需要再其循环之上增加一个乘法计算功能即可,完整的计算流程如下所示;</p>
<pre><code class="language-ASM">.386p
.model flat,stdcall
option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
x DWORD ?
y DWORD ?
szFmt BYTE '%d * %d = %d ',0
szPrBYTE ' ',0dh,0ah,0
.code
main PROC
    mov dword ptr ds:,1      ; int x = 1
    jmp L1
L2: mov eax,dword ptr ds:
    add eax,1                     ; x++
    mov dword ptr ds:,eax
L1:
    cmp dword ptr ds:,10       ; x &lt; 10
    jge lop_end

    mov dword ptr ds:,1      ; y = 1
    jmp L3
L5: mov eax,dword ptr ds:
    add eax,1                     ; y++
    mov dword ptr ds:,eax
L3:
    mov eax,dword ptr ds:
    cmp eax,dword ptr ds:      ; y &lt;= x
    jg L4
   
    ; 执行的是循环体内部
    mov eax,dword ptr ds:
    imul eax,dword ptr ds:
    invoke crt_printf,addr szFmt,dword ptr ds:,dword ptr ds:,eax
   
    jmp L5
L4:
    ; 执行外层循环
    invoke crt_printf,addr szPr

    jmp L2
lop_end:
    int 3

main ENDP
END main
</code></pre>
<h3 id="1124-for语句冒泡排序">11.24 For语句冒泡排序</h3>
<p>该C++代码实现了冒泡排序算法对整型数组进行排序。在冒泡排序算法中,数组中每两个相邻的元素,如果前一个元素大于后一个元素,则交换这两个元素的位置。循环遍历数组多次,每次将未排序的最大值向数组末尾冒泡,直到数组中的所有元素都排好序。代码中使用两层for循环实现排序,内层循环从数组末尾开始,逐步向前遍历,交换相邻的两个元素。外层循环控制排序的遍历次数,只有在当前相邻两个数未排序时才进行交换。程序最终输出排序后的数组。</p>
<pre><code class="language-C">#include &lt;stdio.h&gt;
#include &lt;Windows.h&gt;

int main(int argc, char *argv[])
{
int Array = { 34,78,65,77,89,43,23,55,67,8 };
int x, y, temporary, ArraySize=10;

for (x = 0; x &lt; ArraySize - 1; x++)
{
    for (y = ArraySize - 1; y &gt; x; y--)
    {
      if (Array &gt; Array)
      {
      temporary = Array;
      Array = Array;
      Array = temporary;
      }
    }
}

for (int x = 0; x &lt; 10; x++)
{
    printf("%d \n", Array);

system("pause");
return 0;
}
</code></pre>
<p>由于冒泡排序牵扯到了数据交换所以汇编版本可能稍显负责,不过大体框架还是没有脱离二层循环,仅仅只是在二层循环内部增加了一个判断流程而已,其实如果认真构建相信读者也可以很容易的写出来。</p>
<pre><code class="language-ASM">.386p
.model flat,stdcall
option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
Array DWORD 34,78,65,77,89,43,23,55,67,8
x DWORD ?
y DWORD ?
Temporary DWORD ?
ArraySize DWORD ?
szFmt BYTE '%d --&gt; %d ',0dh,0ah,0

.code
main PROC
    ; 初始化的部分
    mov dword ptr ds:,0            ; x=0
    mov dword ptr ds:,10   ; ArraySize=10
   
    ; 外层循环体
    jmp L1
L2: mov eax,dword ptr ds:
    add eax,1                        ; x++
    mov dword ptr ds:,eax
   
L1: mov eax,dword ptr ds:
    sub eax,1                        ; ArraySize - 1
    cmp dword ptr ds:,eax         ; x &lt; ArraySize
    jge lop_end
   
    ; 内层循环体内容
    mov eax,dword ptr ds:
    sub eax,1
    mov dword ptr ds:,eax
   
    jmp L3
L4: mov eax,dword ptr ds:
    sub eax,1                           ; y--
    mov dword ptr ds:,eax

L3: mov eax,dword ptr ds:
    cmp eax,dword ptr ds:            ; Array &gt; Array
    jle L2
   
    ; 寻址y和y-1的位置
    mov esi,dword ptr ds:

    mov ebx,dword ptr ds:         ; Array
    mov edx,dword ptr ds:   ; Array
    cmp edx,ebx
    jle L4
   
    ; 数据交换
    mov dword ptr ds:,edx         ; Array = Array
    mov dword ptr ds:,ebx   ; Array = Array
    ; invoke crt_printf,addr szFmt,ebx,edx
   
    jmp L4
    jmp L2

lop_end:
    nop

    ; 执行打印函数
    mov dword ptr ds:,0

    jmp L5
L7: mov eax,dword ptr ds:
    add eax,1
    mov dword ptr ds:,eax
L5:
    mov eax,dword ptr ds:
    cmp eax,10
    jge L6
   
    lea esi,dword ptr ds:                ; 取数组基地址
    mov esi,dword ptr ds:      ; 比例因子寻址
    invoke crt_printf,addr szFmt,esi,esi
    jmp L7
L6:
    int 3

main ENDP
END main
</code></pre>
<p>至此,汇编中的循环结构仿写就告一段落了,笔者提醒大家,由于汇编难度较大,且代码都是线性的,所以在编写之前要分析好主次关系,当有了主次关系之后,我们就需要静下心来,一个个构建,由外到内步步为营,其实汇编也并不是那么可怕。</p>


</div>
<div id="MySignature" role="contentinfo">
    <b>文章出处:</b>https://www.cnblogs.com/LyShark/p/17654269.html
<br>
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!<br><br>
来源:https://www.cnblogs.com/LyShark/p/17654269.html
頁: [1]
查看完整版本: 5.13 汇编语言:仿写For循环语句