1

I've been trying to write a code that reverses the content of an array two ways. I've done it right using push and pop method, but I don't know how to do it the pointer way.

I'm asking for any clarifications that could help.

.model small
.data
    tab db '12345' ,13,10,'$'
.code
main proc
    mov ax,@data
    mov ds,ax
    mov si,offset tab
    mov di,offset tab+5
    mov cx,5

    
    etiq1:  
    mov bx,[si]
    push bx
    inc si
    loop etiq1
    
    mov cx,5
    
    etiq2:
    pop dx
    mov ah,02h
    int 21h
    loop etiq2

main endp
end main
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Rosalyo
  • 37
  • 5
  • 1
    You haven't actually reversed the array in memory, you've just *printed* it backwards, 1 char at a time. The reversed data is only ever on the stack and in registers, not stored back into the array. (That's fine, no need to do that if you don't need the data there. But notice that you're not printing the CR LF line ending, and the `$` terminator is doing nothing: presumably it's there so you can pass the address of the whole string to a DOS print-string function. – Peter Cordes Jan 19 '21 at 20:57
  • Does this answer your question? [Reverse an array in Assembly x86](https://stackoverflow.com/questions/40567955/reverse-an-array-in-assembly-x86) – Peter Cordes Jan 19 '21 at 20:59
  • Also [Reverse contents of array in assembly](https://stackoverflow.com/q/5251904) – Peter Cordes Jan 19 '21 at 20:59

1 Answers1

2

Reversing the string array via the stack and using string primitives lodsb and stosb.

Both the push-loop and the pop-loop end when they encounter the carriage return that terminates the string.
The CLD instruction is required to make lodsb and stosb go upwards in memory.
The code will print the result in one go with the DOS.PrintString function 09h.

  mov  ax, @data
  mov  ds, ax
  mov  di, offset tab
  mov  si, di
  mov  dx, di
  cld

etiq1:  
  lodsb
  push ax
  cmp  byte ptr [si], 13
  jne  etiq1

etiq2:
  pop  ax
  stosb
  cmp  byte ptr [di], 13
  jne  etiq2

  mov  ah, 09h        ; DOS.PrintString
  int  21h

  mov  ax, 4C00h      ; DOS.Terminate
  int  21h

Alternative way to reverse your array using a pointer.

The element on the left is read/written using the SI pointer, and the element on the right is read/written using the same pointer but offsetted by a suitable amount.
The loop can continue for as long as this offset remains greater than zero. The offset must be at least 1 for both addresses to talk to a different byte in memory.

  mov  ax, @data
  mov  ds, ax
  mov  si, offset tab
  mov  bx, 4          ; 5 elements : last element is at offset 4 from the first element
More:
  mov  al, [si]       ; Read on left side
  mov  dl, [si+bx]    ; Read on right side
  mov  [si], dl       ; Write on left side
  mov  [si+bx], al    ; Write on right side
  inc  si             ; Move to the right
  sub  bx, 2          ; Next couple of elements is 2 bytes closer to each other
  ja   More           ; Must stay above 0 to address different elements

  ; Here BX is -1, or 0 for remaining 0, or 1 element

  mov  dx, offset tab
  mov  ah, 09h        ; DOS.PrintString
  int  21h

  mov  ax, 4C00h      ; DOS.Terminate
  int  21h

Next is an optimization that reads and writes 2 elements at once.

This is of course only important when working with an array of some length, so not in your limited 5 byte array! This time, the offset must be at least 3 for both addresses to talk to a different word in memory.

  mov  ax, @data
  mov  ds, ax
  mov  si, offset tab
  mov  bx, NumberOfByteSizedElementsMinusOne
  jmp  Begin
More:
  mov  ax, [si]       ; Read pair on left side
  mov  dx, [si+bx-1]  ; Read pair on right side
  xchg al, ah
  xchg dl, dh
  mov  [si], dx       ; Write pair on left side
  mov  [si+bx-1], ax  ; Write pair on right side
  add  si, 2          ; Move to the right
  sub  bx, 4          ; Next couple of paired elements is 4 bytes closer to each other
Begin:
  cmp  bx, 3
  jge  More           ; Must stay above 2 to address different paired elements

  ; Here BX is -1, 0, 1, or 2 for remaining 0, 1, 2, or 3 elements

  cmp  bx, 0
  jle  Done
  mov  al, [si]       ; Read on left side
  mov  dl, [si+bx]    ; Read on right side
  mov  [si], dl       ; Write on left side
  mov  [si+bx], al    ; Write on right side
Done:

  mov  dx, offset tab
  mov  ah, 09h        ; DOS.PrintString
  int  21h

  mov  ax, 4C00h      ; DOS.Terminate
  int  21h
Sep Roland
  • 33,889
  • 7
  • 43
  • 76
  • And BTW, for later x86, doing even wider loads is possible with `bswap` to reverse the bytes in a 4 or 8-byte register, instead of `xchg` or `rol ax,8`. And with SSSE3, `pshufb` to reverse 16 bytes. – Peter Cordes Jan 23 '21 at 22:55
  • Note that it's actually save to let wide-load loops (partially) overlap on the last iteration because you load original data from both sides before doing either store. You only need narrower cleanup to handle the case when the whole array is smaller than your load/store width to avoid reading data *outside* the array. (In this case, with 2 byte load/store it works for any size > 1, and reversing a 0 or 1 byte array is a no-op, so no cleanup is needed. For size=2, it's totally fine to load the same data twice and store it twice, or for overlapping load/store with size=3.) – Peter Cordes Jan 23 '21 at 22:59
  • 1
    So you can also just do a pointer comparison for end and start crossing, like `add di,2` / `sub si,2` / `cmp di,si` / `jbe .loop`, where `si` starts out pointing to a word that ends with the last byte of the array. i.e. `array + size - 2`. Your way isn't worse in 16-bit mode where indexed addressing modes don't take any more space. Although you do have a `-1` in an addressing mode that costs an extra byte of code size in the loop. – Peter Cordes Jan 23 '21 at 23:02