在VS2015中调试我的代码,我结束了程序的结尾。但是,寄存器在call ExitProcess
上应有的状态或其任何变型都会导致“访问冲突写入位置0x00000004”。我正在使用Kip Irvine的书中的Irvine32.inc。我尝试使用call DumpRegs
,但这也会引发错误。
我尝试使用call ExitProcess
的其他变体,例如exit
和invoke ExitProcess,0
均不起作用,并抛出相同的错误。以前,当我使用相同的格式时,代码可以正常工作。该代码与最后一个代码之间的唯一区别是利用了通用寄存器。
include Irvine32.inc
.data
;ary dword 100, -30, 25, 14, 35, -92, 82, 134, 193, 99, 0
ary dword -24, 1, -5, 30, 35, 81, 94, 143, 0
.code
main PROC
;ESI will be used for the array
;EDI will be used for the array value
;ESP will be used for the array counting
;EAX will be used for the accumulating sum
;EBX will be used for the average
;ECX will be used for the remainder of avg
;EBP will be used for calculating remaining sum
mov eax,0 ;Set EAX register to 0
mov ebx,0 ;Set EBX register to 0
mov esp,0 ;Set ESP register to 0
mov esi,OFFSET ary ;Set ESI register to array
sum: mov edi,[esi] ;Set value to array value
cmp edi,0 ;Check value to temination value 0
je finsum ;If equal, jump to finsum
add esp,1 ;Add 1 to array count
add eax,edi ;Add value to sum
add esi,4 ;Increment to next address in array
jmp sum ;Loop back to sum array
finsum: mov ebp,eax ;Set remaining sum to the sum
cmp ebp,0 ;Compare rem sum to 0
je finavg ;Jump to finavg if sum is 0
cmp ebp,esp ;Check sum to array count
jl finavg ;Jump to finavg if sum is less than array count
avg: add ebx,1 ;Add to average
sub ebp,esp ;Subtract array count from rem sum
cmp ebp,esp ;Compare rem sum to array count
jge avg ;Jump to avg if rem sum is >= to ary count
finavg: mov ecx,ebp ;Set rem sum to remainder of avg
call ExitProcess
main ENDP
END MAIN
在call ExitProcess
之前注册
EAX = 00000163 EBX = 0000002C ECX = 00000003 EDX = 00401055
ESI = 004068C0 EDI = 00000000 EIP = 0040366B ESP = 00000008
EBP = 00000003 EFL = 00000293
OV = 0 UP = 0 EI = 1 PL = 1 ZR = 0 AC = 1 PE = 0 CY = 1
答案 0 :(得分:2)
mov esp,0
将堆栈指针设置为0。执行该操作后,诸如push / pop或call / ret之类的任何堆栈指令都将崩溃。
为您的数组计数临时而不是堆栈指针选择一个不同的寄存器!您还有其他7种选择,看起来您仍未使用EDX。
在正常的呼叫约定中,只有EAX,ECX和EDX被呼叫了(所以您可以在不保留呼叫者值的情况下使用它们)。但是您正在调用ExitProcess
而不是从main
返回,所以您可以销毁所有寄存器。但是ESP
必须在您call
时有效。
call
的工作原理是将返回地址推入堆栈,例如sub esp,4
/ mov [esp], next_instruction
/ jmp ExitProcess
。参见https://www.felixcloutier.com/x86/CALL.html。如您的注册转储所示,ESP = 8在call
之前,这就是为什么它试图存储到绝对地址4
的原因。
您的代码分为2部分:遍历数组,然后找到平均值。 您可以在2个部分中将寄存器重用于其他用途,通常可以大大降低寄存器压力。 (即您不会用完寄存器。)
使用隐式长度数组(由0
之类的哨兵元素终止)在字符串之外是不常见的。向函数传递指针+长度,而不仅仅是传递指针,这种情况更为常见。
但是无论如何,您都有一个隐式长度数组,因此您必须找到其长度并在计算平均值时记住这一点。您可以从也要递增的指针中计算出它,而不是在循环内递增一个大小计数器。 (或将计数器用作数组索引,例如ary[ecx*4]
,但指针递增通常更有效。)
这是高效(标量)实现的外观。 (使用SIMD的SSE2,您可以通过一条指令添加4个元素...)
总共只使用3个寄存器。我本可以使用ECX代替ESI(因此main
可以ret
而不破坏调用者希望保留的任何寄存器,只有EAX,ECX和EDX),但是为了保持一致性,我保留了ESI加上您的版本。
.data
;ary dword 100, -30, 25, 14, 35, -92, 82, 134, 193, 99, 0
ary dword -24, 1, -5, 30, 35, 81, 94, 143, 0
.code
main PROC
;; inputs: static ary of signed dword integers
;; outputs: EAX = array average, EDX = remainder of sum/size
;; ESI = array count (in elements)
;; clobbers: none (other than the outputs)
; EAX = sum accumulator
; ESI = array pointer
; EDX = array element temporary
xor eax, eax ; sum = 0
mov esi, OFFSET ary ; incrementing a pointer is usually efficient, vs. ary[ecx*4] inside a loop or something. So this is good.
sumloop: ; do {
mov edx, [esi]
add edx, 4
add eax, edx ; sum += *p++ without checking for 0, because + 0 is a no-op
test edx, edx ; sets FLAGS the same as cmp edx,0
jnz sumloop ; }while(array element != 0);
;;; fall through if the element is 0.
;;; esi points to one past the terminator, i.e. two past the last real element we want to count for the average
sub esi, OFFSET ary + 4 ; (end+4) - (start+4) = array size in bytes
shr esi, 2 ; esi = array length = (end-start)/element_size
cdq ; sign-extend sum into EDX:EAX as an input for idiv
idiv esi ; EAX = sum/length EDX = sum%length
call ExitProcess
main ENDP
我使用x86的硬件除法指令,而不是减法循环。您的重复减法循环看起来很复杂,但是手动带符号除法可能很棘手。 我不知道您在哪里处理总和为负数的可能性。如果您的数组的总和为负数,则反复相减会使其增长直到溢出。或者在您的情况下,如果您使用sum < count
,则会超出循环范围,在第一次迭代中为负数时将为真。
请注意,像Set EAX register to 0
这样的注释是没有用的。通过阅读mov eax,0
我们已经知道这一点。 sum = 0
描述的是语义的含义,而不是体系结构的影响。有一些棘手的x86指令值得评论在这种特定情况下甚至可以做什么,但是mov
并不是其中之一。
如果您只是想以sum
开始时为非负数进行重复减法,那么就这么简单:
;; UNSIGNED division (or signed with non-negative dividend and positive divisor)
; Inputs: sum(dividend) in EAX, count(divisor) in ECX
; Outputs: quotient in EDX, remainder in EAX (reverse of the DIV instruction)
xor edx, edx ; quotient counter = 0
cmp eax, ecx
jb subloop_end ; the quotient = 0 case
repeat_subtraction: ; do {
inc edx ; quotient++
sub eax, ecx ; dividend -= divisor
cmp eax, ecx
jae repeat_subtraction ; while( dividend >= divisor );
; fall through when eax < ecx (unsigned), leaving EAX = remainder
subloop_end:
请注意,进入循环之前先检查特殊情况 可以使我们简化它。另请参见Why are loops always compiled into "do...while" style (tail jump)?
sub eax, ecx
和cmp eax, ecx
在同一循环中似乎是多余的:我们可以仅使用sub来设置标志,并纠正过冲。
xor edx, edx ; quotient counter = 0
cmp eax, ecx
jb division_done ; the quotient = 0 case
repeat_subtraction: ; do {
inc edx ; quotient++
sub eax, ecx ; dividend -= divisor
jnc repeat_subtraction ; while( dividend -= divisor doesn't wrap (carry) );
add eax, ecx ; correct for the overshoot
dec edx
division_done:
(但是在大多数现代x86 CPU上,大多数情况下这实际上并不快;即使输入不相同,它们也可以并行运行inc,cmp和sub。这可能对AMD Bulldozer-整数核非常狭窄的家族。)
显然重复的减法是提高性能的总垃圾。可以实现更好的算法,例如一次一次长除法,但是{{1} }指令将对任何事物都更快,除非您知道商为0或1,因此最多需要减1。 ({idiv
/ div
与任何其他整数运算相比都相当慢,但是专用硬件比循环要快得多。)
如果您确实需要手动执行有符号除法,通常会记录符号,取无符号绝对值,然后进行无符号除法。
例如idiv
/ xor eax, ecx
如果EAX和ECX具有相同的符号,则为dl = 0;如果它们不同,则为1(因此商将为负)。 (根据结果的符号位设置SF,并且异或针对不同的输入产生1,对于相同的输入产生0。)