x86 assembly: Irvine32 - Get last element of an array - arrays

I'm new to Assembly, I need help with an assignment in Assembly Language Irvine32. I want to know where I'm going wrong. I believe my code is 80% right but there's something I'm not seeing or recognizing. Here's the program details.
"Write an assembly language program that has an array of words. The program loads the last element of the array into an appropriately sized register and prints it. (Do not hardcode the index of the last element.)"
INCLUDE Irvine32.inc
.data
val1 word 1,2,3,4,5,6
val2 = ($-val1)/2
.code
main PROC
mov ax, 0
mov ax, val1[val2]
Call WriteDec
Call DumpRegs
exit
main ENDP
END main

First of all, your code has a bug: val1[val2] indexes with the element count in words, not the length in bytes (unless MASM syntax is even more magical than I expect). And it reads from one past the end of the array, since the first element is at val1[0].
To find the end, you either need to know the length (explicit length, like a buffer passed to memcpy(3)), or search it for a sentinel element (implicit length, like a C string passed to strcpy(3)).
Having a function that accepts an explicit length as a parameter seems fine to me. It's obviously much more efficient than a loop scanning for a sentinel element, and the array shown doesn't include one. (See Jose's answer for a suggestion to use '$' (i.e. 36) as a sentinel value. -1 or 0 might be more sensible sentinels/terminators.)
Obviously knowing the length is much better, since there's no need for a loop scanning the whole array.
I'd only call it hard-coding if you wrote val2 = 6, or worse val2 dw 6, rather than having it calculated at assemble-time from the array. If you want to write a function that could work with non-compile-time-constant arrays, you can have it accept the length as a value in memory, instead of an immediate that will be embedded into its load instruction.
e.g.
Length as a parameter in memory
.data
array word 1,2,3,4,5,6
array_len word ($-array)/2 ; some assemblers have syntactic sugar to calc this for you, like a SIZE operator or something.
.code
main PROC ; inputs: array and array_len in static storage
; output: ax = last element of array
; clobbers: si
; mov ax, 0 ; This is useless, the next mov overwrites it.
mov si, [array_len] ; do we need to save/restore si with push/pop in this ABI?
add si,si ; multiply by 2: length in words -> length in bytes
mov ax, [array + si - 2] ; note that the -2 folds into array at assemble time, so it's just a disp16 + index addressing mode
Call WriteDec
Call DumpRegs
exit
main ENDP
END main
You could also write a function to take pointer and length args on the stack or in registers, and have main pass those args.
You could save the add (or shl) by accepting a length in bytes, or a start and one-past-the-end pointer (like C++ STL range functions that take .begin() and .end() iterators). If you have the end pointer, you don't need the start pointer at all, except to return an error if they're equal (size = 0).
Or if you were not stuck with obsolete 16bit code, you could use a scaled index in the addressing mode, like [array + esi * 2]. You include Irvine32.inc...

I think your solution to reach the last element is the most efficient (($-val1)/2), but #zx485 is right and your teacher might believe you are cheating, so, among other solutions, you can reach the last element with a loop and the pointer SI :
INCLUDE Irvine32.inc
.data
val1 word 1,2,3,4,5,6
val2 = ($-val1)/2
.code
main PROC
; mov ax, 0
; mov ax, val1[val2]
mov cx, val2-1 ;COUNTER FOR LOOP (LENGTH-1).
mov si, offset val1 ;SI POINTS TO FIRST WORD IN ARRAY.
repeat:
add si, 2 ;POINT TO NEXT WORD IN ARRAY.
loop repeat ;CX--, IF CX > 0 REPEAT.
mov ax, [ si ] ;LAST WORD!
Call WriteDec
Call DumpRegs
exit
main ENDP
END main
One shorter way would be to get rid of the loop and jump straight to the last element by using the SI pointer (and changing val2 just a little) :
INCLUDE Irvine32.inc
.data
val1 dw 1,2,3,4,5,6
val2 = ($-val1)-2 ;NOW WE GET LENGTH - 2 BYTES.
.code
main PROC
; mov ax, 0
; mov ax, val1[val2]
mov si, offset val1 ;SI POINTS TO FIRST WORD IN ARRAY.
add si, val2 ;SI POINTS TO THE LAST WORD.
mov ax, [ si ] ;LAST WORD!
Call WriteDec
Call DumpRegs
exit
main ENDP
END main
And "Yes", you can join those two lines :
mov si, offset val1 ;SI POINTS TO FIRST WORD IN ARRAY.
add si, val2 ;SI POINTS TO THE LAST WORD.
into one, I separated them to comment each other :
mov si, offset val1 + val2
If you cannot use val2 = ($-val1)/2, one option would be to choose some terminating character for the array, for example, '$', and loop until it's found:
INCLUDE Irvine32.inc
.data
val1 word 1,2,3,4,5,6,'$' ;ARRAY WITH TERMINATING CHARACTER.
;val2 = ($-val1)/2
.code
main PROC
;mov ax, 0
;mov ax, val1[val2]
mov si, offset val1 ;SI POINTS TO VAL1.
mov ax, '$' ;TERMINATING CHARACTER.
repeat:
cmp [ si ], ax
je dollar_found ;IF [ SI ] == '$'
add si, 2 ;NEXT WORD IN ARRAY.
jmp repeat
dollar_found:
sub si, 2 ;PREVIOUS WORD.
mov ax, [ si ] ;FINAL WORD!
Call WriteDec
Call DumpRegs
exit
main ENDP
END main

Related

x86 Irvine32: Assembly language - Get last array [duplicate]

This question already has answers here:
x86 assembly: Irvine32 - Get last element of an array
(2 answers)
Closed 6 years ago.
I'm trying to figure out this program. Just want to know if I'm on point with the program. How do I correct this program? Any help is appreciated. Thank you.
"Write an assembly language program that has an array of words. The program loads the last element of the array into an appropriately sized register and prints it. (Do not hardcode the index of the last element.)"
INCLUDE Irvine32.inc
.data
val1 word 1,2,3,4,5,6
val2 = ($-val1)/2 ; This is hardcoding
.code
main PROC
mov ax, 0
mov ax, val1[val2]
Call WriteDec
Call DumpRegs
exit
main ENDP
END main
If you cannot use val2 = ($-val1)/2, one option would be to choose some terminating character for the array, for example, '$', and loop until it's found:
INCLUDE Irvine32.inc
.data
val1 word 1,2,3,4,5,6,'$' ;ARRAY WITH TERMINATING CHARACTER.
val2 = ($-val1)/2 ; This is hardcoding
.code
main PROC
;mov ax, 0
;mov ax, val1[val2]
mov si, offset val1 ;SI POINTS TO VAL1.
mov ax, '$' ;TERMINATING CHARACTER.
repeat:
cmp [ si ], ax
je dollar_found ;IF [ SI ] == '$'
add si, 2 ;NEXT WORD IN ARRAY.
jmp repeat
dollar_found:
sub si, 2 ;PREVIOUS WORD.
mov ax, [ si ] ;FINAL WORD!
Call WriteDec
Call DumpRegs
exit
main ENDP
END main

Assembly Array and loop

I've a problem in assembly language that I want to make loop for sum element of an array. Suppose an array contains 10,20,30,40,50,60,70,80,90,100 I have to sum all elements of the array by loop... How can I do this?
I'm trying this:
.MODEL SMALL
.STACK 100H
.DATA
W DW 10,20,30,40,50,60,70,80,90,100
.CODE
MAIN PROC
MOV AX, #data
MOV DS, AX
XOR AX, AX
XOR BX, BX
MOV CX, 10
ADDNOS:
ADD AX, W [BX]
ADD BX, 2
LOOP ADDNOS
;this for display
MOV DX, AX
MOV AH,2
INT 21H
MOV AH, 4CH
INT 21H
MAIN ENDP
END MAIN
but something wrong in display that print from ascii (&).
EDIT: Updated answer since the code in the question has been changed:
INT 21h / AH=2 prints a single character (note that the integer 1 and the character '1' are different values).
The sum of the elements in your array is 550, which requires 3 characters to print. The way to solve that is to write a routine that converts the value 550 to the string "550", and then use INT 21h / AH=9 to print that string. How you'd go about doing that has been asked several times before on StackOverflow; see e.g. this question and the answers to it.
This is my answer for the original question
For future questions, note that "but something wrong" is a terrible problem description. You should explain precisely in what way the code isn't behaving the way you intended.
That said, there are a number of problems with your code:
Here you're initializing CX to the first value in x. Actually, since the elements in x are bytes (because you used DB) and CX is a word (two bytes) you'll get CX = 301h (which is 769 in decimal):
MOV CX, x
Here you're simply moving the first element of x into BX over and over, instead of doing an addition. And again, x contains bytes while BX is a word register.
top: MOV BX, [x]
The loop instruction decrements CX by 1 and jumps to the given label if CX != 0. By incrementing CX before the loop you're creating an infinite loop. Also, the CMP is useless (and I'm not sure why you're comparing against 7 since x only has 5 elements):
INC CX
CMP CX, 7
loop top
This will only work for values in the range 0-9. If the sum is >=10 it will require multiple characters. See e.g. this answer for an example of how to convert a multi-digit number to a string that can be printed. Also, you're writing a word-sized register to a byte variable:
ADD BX, '0'
MOV [sum], BX
Here I'm a bit lost at what you're trying to do. If you wanted to write a single character to STDOUT you should use INT 21h / AH = 2 / DL = character. Note that MOV AX,4 sets AH=0 and AL=4. Also, you should end your program with INT 21h / AX = 4C00h:
display_:
MOV DX,1
MOV CX, sum
MOV BX, 1
MOV AX,4
INT 21h
MOV AX, 1
INT 21h
I suspect that there is an error in the code following the top label.
You do MOV BX, [x] but I think there you should sum the item pointed by CX with what currently is in BX (that seems to store the sum). So substitute the move instruction with:
ADD BX, [CX]

Irvine Assembly (MASM ) - Sorting Through an Array / Table

I'm currently presented with implementing a program that takes an input of a grade value (ex. 75) and then outputs a letter grade corresponding to it. I've implemented the following requested scale via an array / table:
.data
table BYTE 89d, 'A'
BYTE 79d, 'B'
BYTE 69d, 'C'
BYTE 59d, 'D'
BYTE 0d, 'F'
NumCols = 2
NumRows = 5
user_ip BYTE ?
message1 BYTE "Enter a grade value: ", 0h
message2 BYTE "The Grade of ", 0h
message3 BYTE " Yields A Letter Grade of ", 0h
I'm using the following code to sort through this array / table and output the letter grade.
mov edx, OFFSET message1
call WriteString
call readDec
mov user_ip, al
mov esi, OFFSET user_ip
mov edi, OFFSET table
mov ecx, NumRows
L1:
CMPSB
jae L2
add edi, NumCols
Loop L1
L2:
mov edx, OFFSET message2
call WriteString
mov al, user_ip
call WriteDec
mov edx, OFFSET message3
call WriteString
mov edx, edi
call WriteString
call Crlf
With an input of 75, I'm being presented with: "The Grade of 75 Yields A Letter Grade of EC;D". The Program also temporarily stops working.
I'm confident it has something to do with pointers and data sizes. My ideal goal is to store the value of the letter grade in a variable, but I can't seem to find a way to do it given the data size needed to use pointers. Any ideas of how to do this?
You are calling WriteString but the values in your table are characters not strings. The difference in this case is that they are not zero terminated. Either use WriteChar if you have that, or put a zero in your table but then don't forget to adjust NumCols too.
Also note that CMPSB increments both pointers which means your comparisons will be wrong. You should probably just use the non-string CMP especially since the user_ip is already in register AL.
PS: Finally somebody who uses a table :)

Assembly not jumping properly

I am having a problem understanding why my cmp statements are not working correctly.
When I run this, I enter 0 in first and it goes to storeValue. I enter 0 in for the second value and it goes to the searchArray like it is supposed to.
I have breakpoints on my cmp and jump statements and a watch on AL so I don't understand why it's storing the first 0 when it should prompt for the search value at that point.
Thanks for looking.
.DATA
prompt1 BYTE "Enter a value to put in array or 0 to search array.", 0
prompt2 BYTE "Enter a value to search array for.",0
intArray DWORD ?
numElem DWORD 0
SearchVal DWORD ?
resultNope BYTE "Not in array.",0
.CODE
_MainProc PROC
lea ebx, intArray ;get the address of array.
start: input prompt1, intArray, 50 ;read in integer
atod intArray ;convert to int
mov al, [ebx] ;move int to register
cmp al, 0 ;if integer is positive - store it!
jg storeValue ;JUMP!
cmp al, 0 ;if 0 - time to search array!
je searchArray ;JUMP!
storeValue: add numElem, 1 ;Adds 1 to num of elements in array.
mov [ebx], al ;moves number into array.
add ebx, 1 ;increment to next array address.
jmp start ;get next number for array. JUMP!
searchArray:input prompt2, searchVal, 50 ;What are we searching array for?
atod searchVal ;convert to int
lea ebx, intArray ;get address of array.
mov ecx, 1 ;set loop counter to 1.
You have forgotten to show how input and atod work. Looking into my crystal ball, I guess that input expects a buffer to store the user input as text, and the argument 50 is presumably its size. Notice that you don't have such a buffer and you don't even have 50 bytes space. I also think that since atod apparently only takes 1 argument, which is the text buffer to convert, it presumably returns the value in eax. This is also reinforced by the fact that your storeValue writes from al which would make no sense otherwise.
Long story short:
allocate a buffer of the proper size for the text entered
pass this array to atod
do not clobber al after the call to atod
(Applies to the search part too.)

MASM32 Reversing Digits

I am trying to make a very simple program that will list out values in a BYTE named array, then reverse the digits. This is the problem I was given:
Write a program to do the following:
Use the BYTE directive to define the list of the 9 digits of your Student's ID number and name it array. Write instructions to reverse the order of those digits in array.
So far this is what I have:
.DATA
array BYTE 9h, 6h, 4h, 5h, 2h, 8h, 7h, 4h, 2h
.CODE
start:
mov esi, 0
mov edi, 0
; ?????
call DumpRegs
call WriteInt
exit
END start
I have used a BYTE for nine digits named array. I do not know how to start the reversal process. Is this done by LOOP? I understand the simple loop, however I am at a complete loss on this one. Any help or answer you can give is very much appreciated. Thank you in advance for helping me learn this material.
Here is something i put together, hope it helps.
.DATA
array BYTE 9h, 6h, 4h, 5h, 2h, 8h, 7h, 4h, 2h
reversearray BYTE 9 DUP (0) ; assign storage for array that will contain reverse
.CODE
mov ecx, SIZEOF array ; length of array is 9, so ecx contains 9
lea esi, array ; address of the start of array
lea edi, reversearray ; address of the start of reversearray
decarray:
movzx eax, byte ptr [esi+ecx-1] ; byte from esi (array start) + ecx counter - 1
mov byte ptr [edi], al ; store byte we have in al, into reverse array (edi)
inc edi ; add 1 to reverse array location for next bytes storage when we loop again
loop decarray ; if ecx is 0 we end, otherwise loop again and ecx will now be ecx-1
; do other stuff here, print our arrays etc

Resources