I'm writing code to find the last 0 on an array.
Basically I need to move a new value on the "top" of each array, if it has only zeros it puts it at the end and if it finds other value it puts it on the last 0 (I'm treating my arrays as piles).
So far my subroutine works fine for the most part but sometimes it rewrites a value that I don't want (instead of getting the first value different from 0 it takes the next one). Here's the code I've been using to get the "top" of the array.
TOP:
xor ecx,ecx
xor ebx,ebx
TOP_FOR:
mov bx,word[eax+ecx*2] ;eax has the pointer of the array
cmp ecx,n ;n is the array's length
je END_TOP
inc ecx
cmp bx,0
je TOP_FOR
;here i get the direction of the first value different
END_TOP: ;from 0 but in my code i need the last 0, so
dec ecx ;i decrease ecx (result of this subrutine)
ret
For example,
If I put an array with 0,2 I expect ecx = 0, but with that input actually get 1.
With the array 1,2 I get 0 (which is what I want)
with the array 0,0 I get 1 (what I want, again)
Edit: tried starting the loop on n-1 and it's giving me even weirder results.
TOP:
xor ecx,ecx
;xor ebx,ebx
mov ecx,n-1
TOP_FOR:
;mov bx,word[eax+ecx*2]
cmp word[eax+ecx*2],0
je FIN_TOPE
dec ecx
cmp ecx,0
jne TOP_FOR
END_TOP:
ret
Your logic is totally backwards. Your cmp/je loop condition leaves the loop when you find the first non-zero. (And you've already incremented ECX after loading, but before checking it).
So after your loop, ECX = index of the element after the first non-zero element.
You at least 2 options:
remember the last-seen 0 in another register, and use it at the end of the loop
loop backwards, starting with ECX = n-1, and exit the loop on the first zero. (Or on dec ecx producing 0.)
One of these is obviously more efficient and easier than the other. :P
I'll leave it up to you to solve the off-by-1 problems, but probably you want to have the ecx < n or ecx >= 0 check at the bottom of the loop, e.g. dec ecx / jge TOP_FOR. i.e. a do{}while(--i) loop.
Also, normally EBX is a call-preserved register. You don't need to use it at all, though. cmp word [eax + ecx*2], 0 works fine.
Also in your current code, you read 2 bytes past the end of the array. potentially faulting if it was at the end of a page. (You don't use it, though, so it's not a correctness problem other than that.) You use ECX as an index before checking if it's too large! That problem goes away if you just use a memory, immediate cmp.
Also, normally a pointer-increment is more efficient. After the loop you can subtract and right-shift to get an index.
Related
I'm solving online test, that our teacher has come up with.
The task is not too hard itself:
EAX contains a pointer to an unknown size array of shorts terminated with 0.
Write a code that finds maximum of all elements that !does not repeat!, which goes to BX register.
EAX and contents of the array must remain unchanged.
For example, the array:
15,900,900,8,96,108,108,96,0
will have 15 in BX.
If an element with properties listed above, return 0;
But the problem is in the instruction set we have to use:
the only commands we have are MOV, PUSH, POP, INC, DEC, NEG, LOOP, STD, CLD, REP* MOVS*, CMPS*, SCAS*, LODS* and STOS*!
No conditional jumps allowed, no functions.
With those instructions I'm completely lost.
I tried searching the end of the array using
cld
mov edi, eax
mov ecx, -1
mov al, 0
repne scasb
inc ecx
inc ecx
neg ecx
and I'm thinking of repeating loop rep cmps with decrementing array length I found in previous state. But can't come up with a solution that works.
I'm using Intel x86 assembly NASM variation, but any will do.
I want to copy a 5x5 matrix of bits to a peripherical. The problem I´m having is that I can´t start the column cicle with the line incrementation variable with 0. In a high-level it would be like this (very simple):
for (line=0;line<4;line++)
for (column=0;column<4;column+++)
R2- line
R3- column
line_cicle:
CMP R2, 4
JZ end
ADD R2,1
column_cicle:
; do stuff that is not depend of the end of a line
CMP R3, 4
JZ line_cicle
; do stuff that is depend of the end of a line
ADD R3, 1
JMP column_cicle
That ADD R2,1 is what is messing up, but where do I put it so that it doesn´t start with 1?
I don't really understand what you're saying/doing with your proposed assembly implementation. Why are you initializing the register to 1 when your loop is supposed to start at 0?
But a for loop nested in another for loop is a relatively simple thing to write, so let's start over and just take things one step at a time, starting from the high-level C code:
for (line=0;line<4;line++)
for (column=0;column<4;column++)
Here is the first (outer) for loop:
xor eax, eax ; line = 0
.LineLoop:
; Do something with line (EAX).
; ...
inc eax ; ++line
cmp eax, 4
jb .LineLoop ; keep looping if line < 4
; We are now finished with the loop.
Now, of course, a compiler wouldn't generate this code. This is a very small loop—it only goes around 4 times—so the overhead of the loop is probably going to be substantial compared to the code that gets executed inside, on each iteration. So a compiler would actually unroll the loop 4 times, producing code that is not only faster but more readable. However, I digress…we were writing loops. :-)
We have the outer loop, and we need the inner loop. Of course, the inner loop is basically the same thing as the outer loop, just with a different variable. Here is the inner loop:
xor edx, edx ; column = 0
.ColumnLoop:
; Do something with column (EDX).
; ...
inc edx ; ++column
cmp edx, 4
jb .ColumnLoop ; keep looping if column < 4
; We are now finished with the loop.
Simple enough, right? I just changed the variable/register and the label name. The last task is to nest them. It turns out that is simple, too. The inner loop's code just gets stuck right in the outer loop's code, right there where I said Do something with line (EAX), since the inner loop is going to do something with line—it's going to loop through all of the columns associated with that line. It is another copy-paste job:
xor eax, eax ; line = 0
.LineLoop:
xor edx, edx ; column = 0
.ColumnLoop:
; Do something with line (EAX) and column (EDX).
; ...
inc edx ; ++column
cmp edx, 4
jb .ColumnLoop ; keep looping if column < 4
inc eax ; ++line
cmp eax, 4
jb .LineLoop ; keep looping if line < 4
; We are now finished with both loops.
Remember that you can choose different registers for your loop counters. I just arbitrarily chose EAX and EDX. If you are going to call a function inside the body of the loop that does something with the line and column, and that function expects its parameters to be passed in different registers, then you might as well use those registers as your loop counters.
Note that there is a slightly more optimal way to write this code that would eliminate the cmp instructions. Instead of starting from 0 and counting up (which requires us to do a comparison to see if we've reached the end yet), we can start from the end and count down. Then, we just take advantage of the fact that the dec instruction sets the zero flag (ZF) when the result is 0, branching directly on that flag, instead of having to do a comparison. The code is easier to understand than the explanation:
mov eax, 4 ; line = 4
.LineLoop:
mov edx, 4 ; column = 4
.ColumnLoop:
; Do something with line (EAX) and column (EDX).
; ...
dec edx ; --column
jnz .ColumnLoop ; keep looping if column > 0
dec eax ; --line
jnz .LineLoop ; keep looping if line > 0
; We are now finished with both loops.
The only issue with this is that you are looping backwards over the lines and columns. This is usually not a problem, though.
I'm new to Assembly language and I need help with an error I keep experiencing, my task in class is to do what I did below, everything I did is correct and how the professor wants it but I can't stop the program from infinite looping, I have the correct answer which is 14 but how do I stop the loop from being infinite without using special commands like ret. How do I stop it?
;Declare an array of words
;Write a loop that adds all the elements of the array located in even places
;Example 3,7,2,8,9
;3+2+9=14
INCLUDE Irvine32.inc
.data
val1 WORD 3,7,2,8,9
.code
main PROC
mov eax, 0
L1:
mov ecx, (LENGTHOF val1)*(TYPE val1+2)-(TYPE val1+4)
mov eax, ecx
call writeDec
loop L1
exit
main ENDP
END main
At the bottom of your loop, you have this instruction:
loop L1
which means "go back to L1."
Loop L1 is a conditional LOOP that is based on the value in ECX. The
real issue is the problem with the value in ECX. LOOP will first
decrement ECX by 1 and compare the new value in ECX with zero. If it
isn't zero it goes to the label (L1). If it is zero it falls through.
Look carefully at where you set the value of ecx. After LOOP decrements ecx by 1 and execution goes to L1, what happens?
I am experiencing some difficulties with understanding of how I can calculate the offset to the row in two dimensional array. The offsets will be used by main later to access the rows for the assignments and quizzes.
Assuming that I have,
scores DWORD 80,80,100, ; midterms, 2 scores
20,20,20,20,20,20,20,100, ; assignments, 7 scores
10,10,10,10,10,10,100 ; quizzes, 6 scores (lowest score dropped)
where "100" is a sentinel value. I understand that the offset is how many bytes away is the row from the start of array.
mov ecx, sentinelVal
mov edi, OFFSET scores
mov eax, sentinelVal
OffsetLoop:
repne scasd ; walk through the array until the target value is found.
jnz endLoop ; if the sentinel value is not found jump from the loop
; If the sentinel value if found
; edi is pointing to the location after the sentinel value
; I am not sure what I should do with the address of the array and edi
; to figure out the offset. Any help would be appreciated. Thanks!
loop OffsetLoop
endLoop:
edited:
I figured out what was my problem. My approach to calculate the offset was right, but it was the loop that caused the problem. It's not possible to simply set ecx to any arbitrary large numbers because scasd also uses ecx as a counter. By setting ecx to a large number, the instruction goes beyond the array boundary which triggers the exception.
mov ecx, LENGTHOF scores
OffsetLoop:
cld
repne scasd
jnz endLoop
mov ebx, edi
sub ebx, OFFSET scores
push ebx
inc ecx
loop OffsetLoop
endLoop:
Assuming your descriptions are correct in that edi is left pointing one beyond the sentinel word, you can simply do:
sub edi, OFFSET scores
to get the byte offset from the beginning of the table.
I'd be a little worried about this though:
mov ecx, sentinelVal
The ecx register is supposed to be a length limit and, while 100 may be a decent value, you should have a different symbolic name for it such as lenLimit.
I have an array that I am attempting to print.
I would like to print it out so I can see if it is correct.
It is currently printing the number 1 and stopping. Or, if I mess with the ECX differently it prints out a bunch of zeros and crashes.
Here is my program.
.data
array DWORD 10 DUP(5, 7, 6, 1, 4, 3, 9, 2, 10, 8)
my_size dd 10
sorted DWORD 0
first DWORD 0
second DWORD 0
.code
start:
main proc
cls
mov EBX, offset[array]
mov ECX, [my_size]
dec ECX
sub ESI, ESI
sub EDI, EDI
; print
mov EBX, offset aa
sub ECX, ECX
;mov ECX, my_size
mov ECX, 10
my_loop:
mov EAX, [EBX]
inc EBX
dec ECX
cmp ECX, 0
jle exit_loop
mov first, EAX
print chr$("printing array elements: ")
print str$(first)
loop my_loop
exit_loop:
ret
main endp
; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
end start
I hate to say it, but you're not "ready" to write a bubble sort. Either it's a completely insane homework assignment, or you haven't followed along with the class so far (possibly both).
Very first thing, I don't think you've defined your array correctly. As I read your code, you've got 100 dwords there - 10 copies of the 10 numbers you specify. You shouldn't need "DUP" in there.
I would print the unsorted array first, just to make sure you've got that part right. You appear to be using a couple of macros, there - they sure as heck aren't instructions. Just from the names, I would guess(!) that "print_chr$" prints a single character and "print_str$" prints a string (although you seem to be printing your string and the number 1). If you've got a "print_int$" in your macro set, I would guess(!) that's what you want. Since I'm not familiar with your macros, I could be wrong.
Although you've defined the array as "dword", you only compare a single byte in your sort routine. This probably works for the small numbers you're using, but it isn't really right.
The usual way to do a bubble sort is to set a "flag" (register or variable - this may be what "sorted" is for) to zero at the beginning of each run through the array, and set it to 1 every time you do a swap. When you've done a pass through your array and the flag is still zero - you haven't done a swap - then, and only then, your array is sorted. If you print the array after each pass, you'll see why it's called a "bubble" sort - the smallest/largest number "bubbles up" to its final position.
Your code to walk through a dword array (esi * 4) looks about right (outside of only comparing a byte), but your print routine only increments ebx by one each time through the loop. Either "add ebx, 4" or use "ebx * 4" (not both) to print dwords. Or perhaps your array is only supposed to be bytes?
Seriously, I'd start with something simpler - just print the array - and work up to adding the sort routine after you've got that working.
Hope it helps.
Best,
Frank
I see you've simplified the code. Good idea! I'm still not familiar with the macro you're using, "print_str$". That doesn't "look" to me like it prints a number. Have you got a "print_int$" or similar? If you can get it to print just the first number "5", that would be a good start.
Now... working through your loop, you just "inc ebx". That won't get you the next dword, it'll get you bytes 2, 3, and 4 from the first dword and the first byte from the second dword. Since you used "* 4" in the (removed) sort code, you probably want "[ebx * 4]" here. Either that, or add 4 to ebx each time through the loop. One or the other (but not both) should step through an array of dwords.
I suspect that the first step would be to select the "right" macro to print a number. It'll probably get easier from there(?). Courage! :)
Best,
Frank