是这么一段代码,来自于《Intel汇编语言程序设计》(第四版)
;----------------------------------------------------------------------------
ArraySum PROC
;
; Calculates the sum of an array of 32-bit integers.
; Receives : ESI = the array offset
; ECX = number of elements in the array
; Returns : EAX = sum of the array elements
;----------------------------------------------------------------------------
push esi ; save ESI,ECX
push ecx
mov eax,0 ; set the sum to zero
L1:
add eax,[esi] ; add each integer to sum
add esi,4 ; point to next integer
loop L1 ; repeat for array size
pop ecx ; restore ECX,ESI
pop esi
ret ; sum is in EAX
ArraySum ENDP
以上就是数组就和的函数代码,我们来一句一句的分析。
首先,我们定义了一个函数,用下面的语句
ArraySum PROC
然后,我们为了保存esi和ecx的值,将其push进了堆栈中,这样,我们在整个函数结束之后,就可以重新通过pop得到esi和ecx原来的值:
push esi ; save ESI,ECX
push ecx
然后,因为我们会将我们就得的数组的和一直保存在eax寄存器当中,所以用以下语句将EAX寄存器清零:
mov eax,0 ; set the sum to zero
然后,我们增加了一个L1的标号,用于循环,接着我们就开始了循环,看一下代码:
L1:
add eax,[esi] ; add each integer to sum
add esi,4 ; point to next integer
loop L1 ; repeat for array size
首先,esi寄存器中保存着当前数组的元素的偏移地址,然后通过方括号,可以取得此地址的数组值,将其add到eax中,然后,又将esi这个地址增加4(因为我们这里计算的是32位整数的就和,32位整数,需要4个BYTE,所以,这里我们将esi增加的是4,而不是其他值,这样便得到了下一个数组元素的偏移地址),然后下一句跳回标号L1。
说到这里,有人可能会问,那循环如何知道什么时候结束呢?其实在循环的时候,寄存器ECX中保存了循环会执行的次数(这里就是数组元素的个数),看看函数的注释:
;----------------------------------------------------------------------------
ArraySum PROC
;
; Calculates the sum of an array of 32-bit integers.
; Receives : ESI = the array offset
; ECX = number of elements in the array
; Returns : EAX = sum of the array elements
;----------------------------------------------------------------------------
这一句:
ECX = number of elements in the array
说明了ECX寄存器保留了数组中元素的个数(ECX寄存器保留循环的个数,并且在循环的时候(每执行一次LOOP),会自减1,直至变为0),所以这个其实是需要我们在调用ArraySum 这个函数之前,将数组元素的个数计算出来之后,保留在ECX寄存器中的。
到这里,函数的功能就完成了,不过我们还要进行一下收尾工作,看以下的代码:
pop ecx ; restore ECX,ESI
pop esi
ret ; sum is in EAX
我们需要把ECX和ESI寄存器之前的值赋回去,我们只要弹出ecx和esi就可以了。
最后一句ret是所有非主函数都需要带的,用来将 指令指针寄存器ESP 指向函数调用之前的地址,好接着让主函数向下执行。
然后我们再来看看如何使用这个数组求和函数。
.data
array DWORD 10000h,20000h,30000h,40000h,50000h
theSum DWORD
.code
main PROC
mov esi , OFFSET array ; ESI points to array
mov ecx, LENGTHOF array ; ECX = array count
call ArraySum ; calculate the sum
mov theSum , eax ; return in EAX
main ENDP
END main
在这里,我们看到,主函数确实在执行ArraySum函数之前向esi和ecx寄存器中放入了合适的值^_^。
另外,我们还可以不使用上面那种容易出错的方法,而使用伪指令 USES 来代替我们进行寄存器值的保存,下面是使用USES修改后的求和代码:
ArraySum PROC USES esi ecx
mov eax , 0
L1:
add eax , [esi]
add esi , 4
loop L1
ret
ArraySum ENDP
编译器将生成以下代码:
ArraySum PROC
push esi
push ecx
mov eax , 0
L1:
add eax , [esi]
add esi , 4
loop L1
pop ecx
pop esi
ret
ArraySum ENDP
(注意:不能将用于过程的返回值的寄存器进行压栈和弹出,否则其中的值会丢失,例如此例中的EAX)