News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Simple array in assembly languge

Started by NoCforMe, March 17, 2025, 11:56:13 AM

Previous topic - Next topic

NoCforMe

Here's one way to implement an array in assembly language (32-bit).
This creates a 1-dimension (linear) array of 100 elements, fills it with values and prints the values.
There are other ways to do this; this is just one example.
;===================================
; Simple array in assembly languge
; Single-dimension (linear) array
;===================================

; Declare the array in the data section:
.data

Array DD 100 DUP(?)

; Put values into the array:
.code
MOV ECX, 100 ;Size of arrray
MOV EDX, OFFSET Array
MOV EAX, 1 ;Value to stuff into arrray elements
putlp: MOV [EDX], EAX ;Put in the value
INC EAX ;Increment the value
ADD EDX, 4 ;Point to next array element
LOOP putlp

; Now you can print the values:
PUSH EBX ;Save the "sacred" register
MOV EBX, OFFSET Array
MOV ECX, 100
print: PUSH ECX ;Save this so it doesn't get clobbered

; Using pseudocode here, since there are lots of ways to print something:
Print "Element = ", [EBX]
POP ECX ;Recover count
ADD EBX, 4 ;Next array element
LOOP print

"Print" is a made-up instruction: there are many ways to display data.
Assembly language programming should be fun. That's why I do it.

LordAdef

Hey NotC,

As a side and useless note. ADD used to be faster than INC.

add eax, 1
vs
inc eax


NoCforMe

OK, whatever.
Not even commenting on optimization for this simple, simple example.
Assembly language programming should be fun. That's why I do it.

Vortex

Hello,

Here is a similar version for Masm64 :

EXTERN ExitProcess:PROC
EXTERN printf:PROC

.data?

array   dq 100 dup(?)

.data

string  db 'Member[%u] = %u'
        db 13,10,0
       
.code

main PROC uses rsi rbx

    sub     rsp,4*8+8
    mov     rcx,100
    lea     rdx,array
    mov     rax,1
@@:
    mov     QWORD PTR [rdx],rax
    inc     rax
    add     rdx,8
    loop    @b
   
    mov     rbx,1
    mov     rsi,OFFSET array
@@:
    lea     rcx,string
    mov     rdx,rbx
    mov     r8,QWORD PTR [rsi]
    call    printf
    inc     rbx
    add     rsi,8
    cmp     rbx,101
    jne     @b

    xor     rcx,rcx
    call    ExitProcess

main ENDP

END

NoCforMe

And if you want a 2-dimensional array, here's how to compute the address of an array element:

You cannot view this attachment.

i is the column index (position in the row) and j is the row index (the position in the column). Both are 0-based.
Important: w is the width of one row in bytes, not the number of elements. (h is the number of rows.)
So if there are 6 elements per row and each element is a DWORD (4 bytes), then the width is 24 bytes.

(Coding is left as an exercise for the reader)
Assembly language programming should be fun. That's why I do it.

daydreamer

@nocforme
Old trick for using 256*256 2d array is use BL for x and BH for y
@lord adef
Depending on CPU,that advice was for P4
If I have slow loop,for example loop that contains api calls that take milliseconds
It doesn't matter,might be more convenient with
Dec variable
Than register preservation push/pop + Dec reg or sub reg,1
my none asm creations
https://masm32.com/board/index.php?topic=6937.msg74303#msg74303
I am an Invoker
"An Invoker is a mage who specializes in the manipulation of raw and elemental energies."
Like SIMD coding

NoCforMe

Can we please leave out the optimizations and just focus on the simple problem at hand for the OP's sake?
They can deal with optimizing things later, once they've figured out how to handle arrays.
Assembly language programming should be fun. That's why I do it.

FORTRANS

Hi,

   Usual comment; if the original poster of this thread, or a moderator,
thinks this is an inappropiate addition to this thread, have at it.

   This thread was about how an array is set up in an assembly program.
This post is partly that, and partly something else all together.  I
hope it helps someone to see how to use an array a bit clearer.

   An array can be used to represent a matrix.  A matrix is a:  "A
rectangular array of numerical or algebraic quantities treated as an
algebraic entity."  Nifty, that's what you get from a dictionary.  Here
I will use an array of numbers in a three by three square as my subset
of matrices.  Matrices are used as a shorthand to apply mathematical
operations to a system of equations.  Multiple algebraic operations
applied to a set of numbers in a specified order shown as numbers en-
closed by brackets.  Well, numbers arranged in a rectangle, enclosed in
brackets is a matrix.  The operations use the matrices.

   This post will discuss my implementation of a program to display
a set of elementary matrix operations to a user.  The program shows
how the matrix is changed by each action.  It was originally a DOS
program that I later converted to a Windows program.  Thanks to Raymond
for his Windows FPU library routines.

   I was using the extended ASCII characters from the default code page
(437) in a VDM for my ease of use to draw the matrix.  This does not
work in Wordpad or Notepad.  Wordpad has an option when opening a file
to open as a "DOS text file".  That doesn't work to do anything.  I
tried using Word, but that did not work either.  Though admittedly an
ancient version.  I had thought the forum editor had an insert symbol
option, but I must have been wrong.

   So a matrix should be shown as (Big left bracket), (Numbers), and
(Big right bracket).  I will use the following notation.
   
   | 1 2 3 |
   | 4 5 6 |
   | 7 8 9 |

   Ugly but workable.

   A matrix uses a one based indexing system in row then column order.
Here the matrix is represented as elements with the address of the
element as numbers in the form below.

       | a11 a12 a13 |
   A = | a21 a22 a23 |
       | a31 a32 a33 |

   High level languages like BASIC or FORTRAN use one based addressing
as well.  Text and reference books use the one based addressing for the
matrices they use.  As mentioned in Reply #4, MASM assembly language
programs normally use zero based addressing for arrays.  So, the main
problem for a programmer is that the machine wants a zero based address
and the user wants the display to be using one based indicies.

   I can use one based indexing by ignoring the zeroth element in the
array to represent a matrix.  That way I can sort of think of it as a
one based array.

   An array is laid out in a linear fashion in memory.

   0 1 2 3 4 5 6 7 8 9

   When used as a matrix we order the elements into a square.

   0  1 2 3      | 11 12 13 |
      4 5 6  =>  | 21 22 23 |
      7 8 9      | 31 32 33 |

   Not using the zeroth element is used to think about the ordering of
the elements in the matrices.  It was to make programming simpler.  Or
actually to make debugging the program and displaying intermediate
results easier.  The following discussion is about how I worked on the
programming.  It is not necessary for anyone else to use this.  It made
things easier for me, and did not violate the 15% rule.

   So now for some code to show how things were written.  These are
snippets and just show some of the thought behind the program operation
and is not intended to be used as your program's organization.  Do your
own thing.

   I am using three by three maticies with floating point numbers to be
used with the FPU. 

; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; - - - EQUates. - - -
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Element EQU     3       ; Number of elements in a column or row.
ElSize  EQU     10      ; Ten bytes per floating point number.
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; - - - 3 x 3 matrices with leading zero element for addressing.
IdentWrk DT     0.0,  1.0, 0.0, 0.0,  0.0, 1.0, 0.0,  0.0, 0.0, 1.0
IdentTmp DT     0.0,  1.0, 0.0, 0.0,  0.0, 1.0, 0.0,  0.0, 0.0, 1.0
IdentRes DT     0.0,  1.0, 0.0, 0.0,  0.0, 1.0, 0.0,  0.0, 0.0, 1.0
MatrixWk DT     0.0,  3.0,-2.0, 2.0,  1.0, 2.0,-3.0,  4.0, 1.0, 2.0
MatrixW2 DT     0.0,  3.0,-2.0, 2.0,  1.0, 2.0,-3.0,  4.0, 1.0, 2.0
ColumnWk DT     10 DUP( 0.0 )
RowWork  DT     10 DUP( 0.0 )
TempWork DT     10 DUP( 0.0 )

ColI    DW      0
ColJ    DW      0
RowI    DW      0
IndxWrk DW      0
Matrix  DD      OFFSET MatrixWk ; Defines an OFFSET to the wanted matrix.
Three   DW      Element
Ten     DW      ElSize

; - - - Matrix OFFSETs for FMatMul
MatrixA DD      OFFSET RowWork
MatrixB DD      OFFSET MatrixWk
MatrixC DD      OFFSET TempWork

; - - - Matrix elements are specified by row and column number.
IndexI  DW      0
IndexJ  DW      0
IndexK  DW      0

; - - - Matrix elements are specified by one number, an offset
;       from array start.
;   C(I,J)=C(I,J)+(A(I,K)*B(K,J))
IndexIJ  DW     0
IndexIK  DW     0
IndexKJ  DW     0

; - - - Matrix elements are specified by an address, one number,
;       an offset from array start based on element size.
ADDR_IJ  DD     0
ADDR_IK  DD     0
ADDR_KJ  DD     0

   For instance, the IndexIJ, IndexIK, and IndexKJ came from the matrix
multiply routine.  It uses something like;

 C(I,J) = C(I,J) + ( A(I,K) * B(K,J) )

in a loop.  Matrix C is made from the product of A multiplied by B.

   The ColI, ColJ, and RowI were one based indices used in the display
code.  The IndexI, IndexJ, and IndexK were one based indices intended
to be used in the calculation portions of the program.  This was a
side effect of reusing some older code.

COMMENT !
28 August 2019
   Implement a set of elementary matrix operations.

Type One:   Interchange of (swap) the ith and jth columns denoted by
            C(i,j).
Type Two:   Multiplication of the ith column vector by the nonzero
            constant c, denoted by cCi.
Type Three: Adding to the ith Col vector, k times the jth column
            vector, denoted by Ci + kCj.

   These can be done as an algorithm or by using a multiplcation by
an elementary matrix.

Reference:  Basic Matrix Theory, Leonard E. Fuller, Dover edition
            2017; Prentice-Hall edition 1962.
        !

   Note that there are the equivalent set of elementary row operations.
And column and row operations can be both be used.

   The higher level routines then tend to look like the following code.

; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ColT1   PROC
; Type One:
;    Interchange of (swap) the ith and jth columns denoted by C(i,j).
;
;  Change identity   | 1  0  0 |    | 1  0  0 |
;  matrix to a swap  | 0 ii  0 | -> | 0  0 ij |
;  columns matrix.   | 0  0 jj |    | 0 ji  0 |
;
;  INPUT: ColI
;         ColJ
;         Matrix
        MOV     EBX, OFFSET ColumnWk
        CALL MkIdent
        CALL ZeroII
        CALL ZeroJJ
        CALL OneIJ
        CALL OneJI
        RET

ColT1   ENDP
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

   Note that in the above, the matrix is hard wired and not an input
despite the comment.  Hey, work was in progress type of excuse?

   Now for some low level routines to show how the matrix is actually
accessed by the program.  First a routine to create the identity matrix.

   | 1.0  0.0  0.0 |
   | 0.0  1.0  0.0 |
   | 0.0  0.0  1.0 |

; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MkIdent PROC
;    While matrix notation is one based, X86 addressing is
; easier as zero based.  Either think or be confused?

; Element is the number of rows and columns
; in the square matrix, one based.
;
;  INPUT: EBX = OFFSET Matrix

        MOV     CX,0    ; Column counter
        MOV     EDI,10   ; Offset in matrix IdentWrk
MId_1:
        MOV     BP,0    ; Row counter
MId_2:
        CMP     CX,BP   ; I = J ?
        JNE     MId_3
        FLD1
;-      FSTP    IdentWrk[EDI]
        FSTP    TBYTE PTR [EBX+EDI]
        JMP     MId_4
MId_3:
        FLDZ
;-      FSTP    IdentWrk[EDI]
        FSTP    TBYTE PTR [EBX+EDI]
MId_4:
        ADD     EDI,10   ; Size of number...

        INC     BP
        CMP     BP,Element     ; End of row?
        JB      MId_2
        MOV     BP,0

        INC     CX
        CMP     CX,Element     ; End of Matrix?
        JB      MId_1

        RET

MkIdent ENDP
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

   The commented out lines with IdentWrk were when the identity matrix
was hard wired to a default matrix.  Program growth required an arbi-
trary matrix to be filled in.  So the input matrix address is passed
in EBX.

   The next routine converts ColI and ColJ to IndexWrk.  ColI and ColJ
are integer index numbers ( 1, 2, 3 ).  IndexWrk is the byte address in
memory.  An artifact of writing the program is that a matrix element
address is specified by ColI and ColJ instead of RowI and ColJ.  Oh
well, sorry.  IndexWrk is the linear address into the array as specified
by the row index and the column index.  The array elements are ten bytes
apiece.

; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MkIndexIJ PROC
;  INPUT: ColI
;         ColJ

        MOV     AX,[ColI]
        DEC     AX      ; One to zero based.
        MUL     Three
        MOV     DX,[ColJ]
        DEC     DX      ; One to zero based.
        ADD     AX,DX
        INC     AX      ; Zero to one based.
        MUL     Ten     ; Array element size.
        MOV     [IndxWrk],AX

        RET
MkIndexIJ ENDP
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

   The program is attached.  It was written by me, for my use, to
answer a question posed by some lectures and the reference book.  Once
that was done, work mostly stopped.  It basiclly shows what happens to
a matrix as it is worked to create the matrix inverse.

cheers,

Steve

NoCforMe

#8
Well, Steve, as the OP here I don't think your post is inappropriate.
I do think it's a bit too big of a chunk for someone just starting to use matrices in an assembly-language program to bite off. But I'll leave that for them to decide.

Rather than get into all the intricacies of matrix manipulation at the outset, I'm much more interested in just getting our n00b started on laying out and addressing a simple matrix.

First things first, y'know.
Assembly language programming should be fun. That's why I do it.

Vortex

Here is a simple string array example :

include     \masm32\include\masm32rt.inc

.data

string1     db 'Melon',0
string2     db 'Apple',0
string3     db 'Orange',0
string4     db 'Banana',0

strArray    dd string1,string2
            dd string3,string4

msg         db 'Fruit %u = %s'
            db 13,10,0

.code

start:

    call    main
    invoke  ExitProcess,0

main PROC uses esi ebx

    xor     ebx,ebx
    mov     esi,OFFSET strArray
@@:
    inc     ebx
    invoke  crt_printf,ADDR msg,\
            ebx,DWORD PTR [esi]
           
    add     esi,4
    cmp     ebx,4
    jne     @b
    ret
   
main ENDP

END start

FORTRANS

Hi,

   Well a bit long I will agree with.  But I wanted a "real life"
example to show how that affects the programming involved in writing
a program that involves interacting with a user.  The only real
description of matrices I gave was 'Well, numbers arranged in a
rectangle, enclosed in brackets is a matrix'.  And how I laid things
out in memory to be used as an array.  And some information on indexing.
Anyway after laying out the background, I forgot to give the punch line.

  Synopsis:

   One, when dealing with a user, use human oriented concepts to make
things easier to program.  One based indexing as an example.

   Two, when dealing with programming the CPU, use computer oriented
concepts, basiclly because you have no other choices with assembly
language.  Zero based addressing for instance.

   Three, you will need to translate between the two paradigms
mentioned above.

   Example:

   You have obtained a number you want to store in the first element of
the matrix.  The first element has a row value of one and a column value
of one.  You will convert those values to a byte offset to address the
correct array member.  Simplified (fake) code example using stuff I
posted above.

        MOV     RowI,1          ; Human values, ColI in posted code.
        MOV     ColJ,1          ; These are the matrix index values.

        CALL    MkIndexIJ       ; Make the byte offset to address the
                                ; proper array member from the indicies.

        MOV     EDI,OFFSET MatrixWk     ; Point to array holding the matrix.

        ADD     EDI,[IndxWrk]   ; Add in byte offset to point to the proper
                                ; array member.

        MOV     ESI,OFFSET FConst       ; Point to wanted number.

        FLD     TBYTE PTR [ESI] ; Load number into FPU.
        FSTP    TBYTE PTR [EDI] ; Store number into the array/matrix.

    And that was the missing punch line.  Laughter ensues?  Whatever.

Regards,

Steve

NoCforMe

#11
Quote from: FORTRANS on March 22, 2025, 12:16:06 AMOne, when dealing with a user, use human oriented concepts to make
things easier to program.  One based indexing as an example.
Maybe when presenting array indices to the user.
But certainly not internally; zero-based indices are the norm and are much easier to handle programmatically. So you'd need to convert (add or subtract 1 to/from each index) for the user-interface values.

But I guess you said as much in your post ...
Assembly language programming should be fun. That's why I do it.

daydreamer

Might start thread in workshop for more advanced array coding?
my none asm creations
https://masm32.com/board/index.php?topic=6937.msg74303#msg74303
I am an Invoker
"An Invoker is a mage who specializes in the manipulation of raw and elemental energies."
Like SIMD coding

jj2007


FORTRANS

Hi,

Quote from: NoCforMe on March 22, 2025, 07:04:31 AMBut I guess you said as much in your post ...

   Yes.  Assuming we are talking on the same subject, I guess we must
agree to disagree.

   I could post a snippet of code from this program that the majority
of our "regular" programmers would see as ugly or wrong.  And could
explain why or how it can cause problems in some cases.  I knew this
when I wrote it and could have "fixed" it then or now.  But it was
easier to do it in the quick and dirty fashion and, knowing that it
works, and why it works, leave it as is.  Saved two or three lines
of code that would need to be changed or added.  And until I typed it
up here, saved a hand full of seconds.

   Writing code should be done in a manner that works for you.
Following "the rules" is usually the best way of doing things.  But
doing things differently is sometimes easier or more fun.  Depending
on some definition of fun.

   In this particular case, it made looking at intermediate results
and comparing them to the published methods, possible or easier for
me.  Generating the indices to access a matrix was a bit tedious to
debug in some cases. 

Cheers,

Steve N.