ASM Practice: Reverse a string

Share your advanced PureBasic knowledge/code with the community.
epidemicz
User
User
Posts: 86
Joined: Thu Jan 22, 2009 8:05 am
Location: USA
Contact:

ASM Practice: Reverse a string

Post by epidemicz »

Hey guys,

I've been trying to learn asm a little, here's a little project I came up with to help practice what I've picked up so far. Would love to hear your suggestion on any improvements.

Code: Select all

Procedure.s asm_strrev(*string)
  *newstring = AllocateMemory(MemoryStringLength(*string))  ;allocate same amt of memory as initial string
  endofnewstring = MemorySize(*newstring)-1                 ;the end should start -1 because the last one is the null terminator already
  MOV eax, *string          ;eax now points to the same spot as parameter
  MOV ebx, *newstring       ;ebx now points to the new(reversed) string location
  ADD ebx, endofnewstring   ;ebx is now the last position of the new string
  reading:
  MOV cl, [eax]             ;reads current char of parameter into cl
  MOV [ebx], cl             ;makes last character of new string = first character of parameter
  ADD eax, 1                ;+1 to the pointer so we get the next value
  SUB ebx, 1                ;-1 on the new string to write the next char
  TEST cl, cl               ;
  JNZ l_reading             ;if cl is not zero, keep going :)
ProcedureReturn PeekS(*newstring)
EndProcedure

Debug asm_strrev(@"Hello World!")
Image
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: ASM Practice: Reverse a string

Post by netmaestro »

Are you sure it works?

Code: Select all

Procedure.s asm_strrev(*string)
  *newstring = AllocateMemory(MemoryStringLength(*string))  ;allocate same amt of memory as initial string
  endofnewstring = MemorySize(*newstring)-1                 ;the end should start -1 because the last one is the null terminator already
  MOV eax, *string          ;eax now points to the same spot as parameter
  MOV ebx, *newstring       ;ebx now points to the new(reversed) string location
  ADD ebx, endofnewstring   ;ebx is now the last position of the new string
  reading:
  MOV cl, [eax]             ;reads current char of parameter into cl
  MOV [ebx], cl             ;makes last character of new string = first character of parameter
  ADD eax, 1                ;+1 to the pointer so we get the next value
  SUB ebx, 1                ;-1 on the new string to write the next char
  TEST cl, cl               ;
  JNZ l_reading             ;if cl is not zero, keep going :)
ProcedureReturn PeekS(*newstring)
EndProcedure

Debug asm_strrev(@"neveroddoreven")
Debug asm_strrev(@"dogeeseseegod")
Debug asm_strrev(@"somemeninterpretninememos")
Just messin' with ya :twisted:
Thanks for sharing 8)
BERESHEIT
User avatar
Demivec
Addict
Addict
Posts: 4270
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: ASM Practice: Reverse a string

Post by Demivec »

This line of code

Code: Select all

*newstring = AllocateMemory(MemoryStringLength(*string))  ;allocate same amt of memory as initial string
should be

Code: Select all

*newstring = AllocateMemory(MemoryStringLength(*string) + SizeOf(Character))  ;allocate same amt of memory as initial string
because MemoryStringLength() does not include the terminating null(s) in the length.

The code currently works by luck because you aren't allocating enough memory for the reverse string. In other words the return PeekS(*newString) only works if the memory immediately following the area allocated for the reverse string contains #Nulls. This is because the code you posted completely overwrites all of the allocated memory.
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Re: ASM Practice: Reverse a string

Post by ts-soft »

It is right what Demivec says, but your code only support ascii mode, so this change do the trick:

Code: Select all

Procedure.s asm_strrev(*string)
  *newstring = AllocateMemory(MemoryStringLength(*string) + 1)  ;allocate same amt of memory as initial string
  endofnewstring = MemorySize(*newstring)  - 2                ;the end should start -1 because the last one is the null terminator already
  MOV eax, *string          ;eax now points to the same spot as parameter
  MOV ebx, *newstring       ;ebx now points to the new(reversed) string location
  ADD ebx, endofnewstring   ;ebx is now the last position of the new string
  reading:
  MOV cl, [eax]             ;reads current char of parameter into cl
  MOV [ebx], cl             ;makes last character of new string = first character of parameter
  ADD eax, 1                ;+1 to the pointer so we get the next value
  SUB ebx, 1                ;-1 on the new string to write the next char
  TEST cl, cl               ;
  JNZ l_reading             ;if cl is not zero, keep going :)
ProcedureReturn PeekS(*newstring)
EndProcedure

Debug asm_strrev(@"Hello World!")
to see the difference activate the purifier in compileroptions in pb 4.50

greetings
Thomas
cas
Enthusiast
Enthusiast
Posts: 597
Joined: Mon Nov 03, 2008 9:56 pm

Re: ASM Practice: Reverse a string

Post by cas »

I don't see where you call FreeMemory() on that allocated memory in procedure, i think you have memory leak. How about reversing a string without allocating new memory, but in-place reversion (because reversed string will be same lenght)?
epidemicz
User
User
Posts: 86
Joined: Thu Jan 22, 2009 8:05 am
Location: USA
Contact:

Re: ASM Practice: Reverse a string

Post by epidemicz »

cas wrote:I don't see where you call FreeMemory() on that allocated memory in procedure, i think you have memory leak. How about reversing a string without allocating new memory, but in-place reversion (because reversed string will be same lenght)?
I normally do try to call freememory(), and I did notice I forgot in this example. However, it should be freed up anyway when the program ends..Unless I'm missing something?

According to my help file:
Note: all remaining allocated memory blocks are automatically freed when the program ends.
In place reversion...suppose I could do an xor swap?
Image
epidemicz
User
User
Posts: 86
Joined: Thu Jan 22, 2009 8:05 am
Location: USA
Contact:

Re: ASM Practice: Reverse a string

Post by epidemicz »

Now with in place reversion, that was fun :D. Still may not be unicode friendly.

Code: Select all

;pb 4.30 x86

Procedure.s asm_strrev(*string)
  endofnewstring = MemoryStringLength(*string)-1  ;the end should start -1 because the last one is the null terminator already
  MOV eax, *string          ;eax now points to the same spot as parameter
  MOV ebx, *string          ;ebx also points to the same spot atm
  ADD ebx, endofnewstring   ;add to ebx so it points to the last real character of string
  reading:
  
  MOV cl, [eax]             ;reads current char of string into cl
  MOV dl, [ebx]             ;reads last char of string into dl
  
  XOR cl, dl                ;performs xor swap
  XOR dl, cl                ;
  XOR cl, dl                ;at this point the values of cl and dl are swapped (ie first character is now the last character)
  
  MOV [eax], cl             ;writes ending char to beginning
  MOV [ebx], dl             ;writes beginning char to end
 
  ADD eax, 1                ;step up the start to the next character from the beginning
  SUB ebx, 1                ;step back the end to the next character from the end
  CMP eax, ebx              ;ebx - eax
  JNA l_reading             ;if eax < ebx then keep going :)
 
ProcedureReturn PeekS(*string)
EndProcedure

Debug asm_strrev(@"): !NOISREVER ECALP NI OLLEH")
Image
cas
Enthusiast
Enthusiast
Posts: 597
Joined: Mon Nov 03, 2008 9:56 pm

Re: ASM Practice: Reverse a string

Post by cas »

epidemicz wrote:I normally do try to call freememory(), and I did notice I forgot in this example. However, it should be freed up anyway when the program ends..Unless I'm missing something?

According to my help file:
Note: all remaining allocated memory blocks are automatically freed when the program ends.
True, in this example it is not needed but if someone decides to use your procedure in some large project then he must free memory before procedure ends.
epidemicz wrote:Now with in place reversion, that was fun :D. Still may not be unicode friendly.
Nice, better than allocating additional memory (and freeing it) when all needed memory is already there :P, unicode support should't be hard to implement.

One question, PB help file says:
- The available volatile registers are: eax, edx and ecx. All others must be always preserved.
You use some other registers and you don't return them to original values. Does that mean that all registers used in some procedure can be used without preserving values, and only outside of procedure we must preserve values?
User avatar
Demivec
Addict
Addict
Posts: 4270
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: ASM Practice: Reverse a string

Post by Demivec »

cas wrote:One question, PB help file says:
- The available volatile registers are: eax, edx and ecx. All others must be always preserved.
You use some other registers and you don't return them to original values. Does that mean that all registers used in some procedure can be used without preserving values, and only outside of procedure we must preserve values?
No, it means they must always be preserved. For instance if the reverse-string routine changed the esp register first thing it would have trouble assessing the local variables.

The non-volatile registers should be saved and then restored before using any of the PureBasic commands. This is true wherever the code is found.
epidemicz
User
User
Posts: 86
Joined: Thu Jan 22, 2009 8:05 am
Location: USA
Contact:

Re: ASM Practice: Reverse a string

Post by epidemicz »

Code: Select all

;pb 4.30 x86

Procedure.s asm_strrev(*string)
  endofnewstring = MemoryStringLength(*string)-1  ;the end should start -1 because the last one is the null terminator already
  MOV eax, *string          ;eax now points to the same spot as parameter
  MOV ebx, *string          ;ebx also points to the same spot atm
  ADD ebx, endofnewstring   ;add to ebx so it points to the last real character of string
  reading:
  
  MOV cl, [eax]             ;reads current char of string into cl
  MOV dl, [ebx]             ;reads last char of string into dl
  
  ;XOR cl, dl                ;performs xor swap
  ;XOR dl, cl                ;
  ;XOR cl, dl                ;at this point the values of cl and dl are swapped (ie first character is now the last character)
  
  MOV [eax], dl             ;writes ending char to beginning
  MOV [ebx], cl             ;writes beginning char to end

  ADD eax, 1                ;step up the start to the next character from the beginning
  SUB ebx, 1                ;step back the end to the next character from the end
  CMP eax, ebx              ;ebx - eax
  JNA l_reading             ;if eax < ebx then keep going :)

ProcedureReturn PeekS(*string)
EndProcedure

Debug asm_strrev(@"): !NOISREVER ECALP NI OLLEH")
after looking at it (duh) for a second, i really didn't need the xor swap but for some reason I just wanted to do it, this will work too.

Also, forgot I was using ebx.. From what you say I should have to preserve it, but if I push it before hand and pop it after I get crazy mem violations. It seems to set itself back to what it was previous to the function being called.

Edit:
Well I think I could prob keep going with a million different ways to do it, heres (maybe) the proper xor swap, also without using a non volatile register(according to documentation at least)

Code: Select all

;pb 4.30 x86

Procedure.s asm_strrev(*string)
  endofnewstring = MemoryStringLength(*string)-1  ;the end should start -1 because the last one is the null terminator already
  MOV eax, *string          ;eax now points to the same spot as parameter
  MOV ecx, *string          ;ecx also points to the same spot atm
  ADD ecx, endofnewstring   ;add to ecx so it points to the last real character of string
  reading:
  
  MOV dl, [ecx]             ;move last char into dl
  
  XOR [eax], dl             ;swaps
  XOR dl, [eax]             ;
  XOR [eax], dl             ;first char to last char
  
  ;why can't you xor memory with memory? grr
  MOV [ecx], dl             ;dl now = first char, move first char into last position
  
  ADD eax, 1                ;step up the start to the next character from the beginning
  SUB ecx, 1                ;step back the end to the next character from the end
  CMP eax, ecx              ;ecx - eax
  JNA l_reading             ;if eax < ecx then keep going :)

ProcedureReturn PeekS(*string)
EndProcedure

Debug asm_strrev(@"): !NOISREVER ECALP NI OLLEH")
Why can't you xor memory with memory?

I'll quit now because I dont want to seem spammy. At least I'm learning stuff, thanks guys :D
Image
User avatar
Demivec
Addict
Addict
Posts: 4270
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: ASM Practice: Reverse a string

Post by Demivec »

epidemicz wrote:Also, forgot I was using ebx.. From what you say I should have to preserve it, but if I push it before hand and pop it after I get crazy mem violations. It seems to set itself back to what it was previous to the function being called.
Thus this quote:
cas wrote:No, it means they must always be preserved. For instance if the reverse-string routine changed the esp register first thing it would have trouble assessing the local variables.
Your parameters and local variables are stored on the stack, when you push a copy of ebx onto the stack you are changing the offset's needed to access the local variables. You can use the actual stack offsets instead of having PureBasic calculating them (i.e. don't use *string, use esp + 12)adjust the stack offsets (i.e after a push instead of esp + 12, use esp + 16).

Code: Select all

Procedure.s asm_strrev(*string)
  endofnewstring = MemoryStringLength(*string)-1  ;the end should start -1 because the last one is the null terminator already
  PUSH ebx                  ;*string is now at esp + 16, endofnewstring is at esp + 4
  MOV eax, [esp + 16]       ;eax now points to the same spot as parameter
  MOV ebx, [esp + 16]       ;ebx also points to the same spot atm
  ADD ebx, [esp + 4]        ;add to ebx so it points to the last real character of string
  reading:
  
  MOV cl, [eax]             ;reads current char of string into cl
  MOV dl, [ebx]             ;reads last char of string into dl
  
  ;XOR cl, dl                ;performs xor swap
  ;XOR dl, cl                ;
  ;XOR cl, dl                ;at this point the values of cl and dl are swapped (ie first character is now the last character)
  
  MOV [eax], dl             ;writes ending char to beginning
  MOV [ebx], cl             ;writes beginning char to end
  
  ADD eax, 1                ;step up the start to the next character from the beginning
  SUB ebx, 1                ;step back the end to the next character from the end
  CMP eax, ebx              ;ebx - eax
  JNA l_reading             ;if eax < ebx then keep going :)
  
  POP ebx
  
  ProcedureReturn PeekS(*string)
EndProcedure

Debug asm_strrev(@"): !NOISREVER ECALP NI OLLEH")


If you want to keep things simple you can also use the other volatile registers when they are available, like this:

Code: Select all

Procedure.s asm_strrev(*string)
  endofnewstring = MemoryStringLength(*string)-1  ;the end should start -1 because the last one is the null terminator already
  MOV eax, *string          ;eax now points to the same spot as parameter
  MOV ecx, ebx              ;preserve ebx
  MOV ebx, *string          ;ebx also points to the same spot atm
  ADD ebx, endofnewstring   ;add to ebx so it points to the last real character of string
  reading:
  
  MOV cl, [eax]             ;reads current char of string into cl
  MOV dl, [ebx]             ;reads last char of string into dl
  
  ;XOR cl, dl                ;performs xor swap
  ;XOR dl, cl                ;
  ;XOR cl, dl                ;at this point the values of cl and dl are swapped (ie first character is now the last character)
  
  MOV [eax], dl             ;writes ending char to beginning
  MOV [ebx], cl             ;writes beginning char to end
  
  ADD eax, 1                ;step up the start to the next character from the beginning
  SUB ebx, 1                ;step back the end to the next character from the end
  CMP eax, ebx              ;ebx - eax
  JNA l_reading             ;if eax < ebx then keep going :)
  
  MOV ebx, ecx              ;restore ebx
  
  ProcedureReturn PeekS(*string)
EndProcedure

Debug asm_strrev(@"): !NOISREVER ECALP NI OLLEH")
Post Reply