Ejercicio para principiantes de montaje: calcule los números cuadrados de 1 a n -- beginner campo con assembly camp codereview Relacionados El problema

Assembly beginner exercise: Calculate the square numbers from 1 to n


10
vote

problema

Español

Trato de aprender (plana, fasm-) en la actualidad.

para familiarizarse con el concepto de ramificación que hice un pequeño programa. Inspirado por un ejercicio que tuve que hacer una vez en la escuela al aprender PHP.

"Escriba un programa que realice un número entero dado por el usuario. El programa luego calcula e imprime los números cuadrados de 1 al número dado".

Aquí mi código fuente. He añadido comentarios en el código. Tratando de explicar mis pensamientos y motivaciones.

  x + 18  

Parece que funciona como se espera.

ingrese la descripción de la imagen aquí

Pero estoy seguro de que hay mucho espacio para mejoras ...

Por lo tanto,: sugerencias y comentarios de programadores de ensamblaje más experimentados muy apreciados. :)

Original en ingles

I try to learn (flat-, FASM-) Assembly currently.

For to become more familiar with the concept of branching I made a tiny program. Inspired by an exercise I had to do once in school when learning PHP.

"Write a program which takes an integer given by the user. The program then calculates and prints the square numbers from 1 to the given number."

Here my source code. I have added comments in the code. Trying to explain my thoughts and motivations.

format PE console entry start  include 'win32a.inc'   ; =========================================== ; = Displays the square number from 1 until = ; =  number n. n is given by the user.      = ; = @param [ integer ] n - The upper        = ; =  limit. The last integer to calculate   = ; =  the square number of.                  = ; ===========================================  ; - Example -------------------------------- ; 100 // User have entered 100. ; 1 ; 4 ; 9 ; 10  // 16 in base 10 ; 19  // 25 in base 10 ; 24  // 36 in base 10 ; 31  //   ... ; 40 ; 51 ; 64 ; 79  section '.text' code readable executable  start:     call    read_hex ; Provided by teacher. Reads in a hexadecimal number from stdin.      mov     ecx, eax     mov     ebx, 1 ; First number ...      cmp eax, 1000000 ; When the number received is smaller the upper limit ...     jle createSquareNumbers ; ... then you do not have to override it.      mov     ecx, ebp ; ... otherwise you override it with the upper limit.  createSquareNumbers:     mov edx, 0     mov eax, ebx     mul ebx ; Multiply the number with itself.      call print_eax ; ; Provided by teacher. Prints eax to stdin.      inc ebx ; Go to the next integer.      cmp ebx, ecx     jle createSquareNumbers      push    0     call    [ExitProcess]  include 'training.inc' 

It seems to work as expected.

enter image description here

But I'm sure there's a lot of room for improvements ...

So therefore: Hints and comments from more experienced Assembly programmers very appreciated. :)

     

Lista de respuestas

9
 
vote

El mayor problema aquí, y en general, con la asamblea, es el uso de registros en el código sin documentación explícita de lo que los registros contienen, E.G. , en forma de comentarios. Ciertas suposiciones se pueden hacer en función de las convenciones de llamadas comunes (como las funciones devolverán su resultado en cmp eax, 1000000 ; When the number received is smaller the upper limit ... jle createSquareNumbers ; ... then you do not have to override it. mov ecx, ebp ; 1 ), pero que aún debe documentarse para mayor claridad. En otros casos, no es tan obvio. Por ejemplo, en este Código, cmp eax, 1000000 ; When the number received is smaller the upper limit ... jle createSquareNumbers ; ... then you do not have to override it. mov ecx, ebp ; 2 aparentemente contiene el "límite superior", pero que no sea asumiendo que el código funciona y la ingeniería inversa de su lógica, no tengo ninguna base para ese supuesto. De hecho, en la implementación actual, los contenidos del Registro 998877666555443313 para la función para la función, por lo que su uso debe ser íntimo Documentado como parte de la firma de la función.

Además, supongo que cmp eax, 1000000 ; When the number received is smaller the upper limit ... jle createSquareNumbers ; ... then you do not have to override it. mov ecx, ebp ; 4 es simplemente cmp eax, 1000000 ; When the number received is smaller the upper limit ... jle createSquareNumbers ; ... then you do not have to override it. mov ecx, ebp ; 5 , en cuyo caso esta parte de su código es demasiado verbosa (y, por lo tanto, ineficiente). Simplemente puedes hacer:

  cmp eax, 1000000 ; When the number received is smaller the upper limit ... jle createSquareNumbers ; ... then you do not have to override it. mov     ecx, ebp ; 6  

Si está dirigido al Pentium Pro o posterior (por lo que básicamente, CUALQUIER moderno X86 Procesador) Puede usar instrucciones de movimiento condicional por lo que:

  cmp eax, 1000000 ; When the number received is smaller the upper limit ... jle createSquareNumbers ; ... then you do not have to override it. mov     ecx, ebp ; 7  

En algunos casos, los movimientos condicionales harán que el código sea más rápido al eliminar la posibilidad de sucursales erróneas. En este caso, no verá ninguna mejora de rendimiento, pero estas instrucciones aún son algo con lo que ser consciente y esto hace que el código sea un poco más fácil de leer. (Suponiendo que desea mantener la dependencia del Registro 99887766555443318, que puede ser útil si desea realizar la variable de límite superior. De lo contrario, solo el código duro el 1000000 y use la primera versión. Mejor Sin embargo, defina una constante para ese límite superior para que pueda darle un nombre legible).


  cmp eax, 1000000 ; When the number received is smaller the upper limit ... jle createSquareNumbers ; ... then you do not have to override it. mov     ecx, ebp ; 9  

Este es un código completamente correcto para configurar un registro en 0. Sin embargo, no es un conjunto idiomático x86 porque es ineficiente. Tanto los compiladores como los programadores de idiomas de ensamblaje expertos deberán escribir esto como:

  ebp0  

Esto funciona porque el Xoring Bitwise cualquier valor consigo mismo le devuelve 0. La ventaja es que es más corta (2 bytes en lugar de 5 bytes) y más rápido .

La única vez que desea usar ebp1 es si no quisieras clavar las banderas, lo que es útil cuando se está preparando para hacer un movimiento condicional y en ciertas veces bien Código optimizado a mano. De lo contrario, siempre que desee aclarar un registro, piense ebp2 . Si bien esto es posiblemente una micro optimización, realmente necesita saber este idioma para que pueda leer el código de ensamblaje de otros pueblos (y compiladores "), por lo que también puede usarlo usted mismo.


Puedo decirle que está trabajando con valores enteros firmados porque está utilizando el ebp3 y ecx24 CÓDIGOS DE CONDICIÓN. Si está trabajando con valores de enteros sin firmar, estaría usando el 99887766655443325 ebp6655443326 Códigos de condición. Por lo tanto, debe estar haciendo un entero firmado multiplicar ( ebp7 en lugar de ebp8 ). De hecho, ebp9 es preferible por una serie de razones, el jefe de ellos es el hecho de que tiene una forma normal y de dos operandos que funciona como cualquier otra instrucción, lo que significa que no tiene Para preocuparse por los operandos de registro codificados de duros que 99887776655443330 requiere. Esto hace que el código sea más fácil de optimizar y, francamente, más fácil de leer. También puede usar ecx1 para multiplicaciones insignificadas, siempre y cuando no esté preocupado por el desbordamiento, ya que los 32 bits más bajos son siempre los mismos. (Y claramente no está preocupado por eso aquí, ya que su función 99887766655443332

Lamentablemente, hay un error en espera aquí:

Solo está revisando el límite Upper , ¿qué sucede cuando el valor de entrada es 0? Su código imprimirá ecx33

, ¿cuándo debería imprimirlo ¡Nada ! Además, me pregunto si es posible para ecx4 devolver un valor negativo ? No tengo la implementación de la función, ni ninguna documentación para ella, así que no puedo decir. Si no está seguro de que nunca volverá a devolver un negativ. Valor, su código necesita manejar este caso. Afortunadamente, puede solucionar estos dos problemas potenciales fácilmente agregando un cheque para el límite inferior antes de ingresar al bucle.

y mientras estamos nitpicking, algunos de sus comentarios tienen "errores", también:

  •   ecx5  

    que debe ser ecx6 , NO 99887776655443337 ! - -)

  •   ecx8  

    Su función no toma realmente un parámetro. Más bien, recupera la entrada llamando a la función ecx39 . Por lo tanto, esta documentación es incorrecta, porque sugiere que la función pueda leer un parámetro del registro normal utilizado para pasar parámetros en cualquier convención de llamadas que use, lo que no es correcto. Es importante documentar sus funciones, y es importante documentarlas con precisión . En realidad, la función no recibe ni devuelve ningún valor.


En términos de lectura, debe alinear su código en columnas verticales para que las instrucciones opciones de instrucciones estén claramente separadas de sus operandos. Esto hace que sea mucho más fácil leer y escanear. Además, debe alinear verticalmente sus comentarios de final de línea, o ponerlos en arriba el código.

También cambiaría el nombre de su cmp eax, ebp 0 Etiqueta a cmp eax, ebp 1 , desde (1), imprime de salida en lugar de simplemente crear cosas, y (2 ) "Números" es redundante. También me gusta personalmente usar Pascalcase para las etiquetas para que sean distintas, pero lo que sea bien, siempre y cuando sea consistente.

Finalmente, preferiría usar el 9988776665544334225443342 registrarse como My Loop "Counter", dado que es lo que originalmente estaba destinado a (la "C" significa "contador"). Técnicamente, esto no importa en absoluto en el código X86 moderno, y puede tratar a todos los registros como registros de propósito general, usándolos indistintamente. Y, de hecho, en código optimizado, querrá hacer esto. Pero cuando lo estás escribiendo a mano y no te importa necesariamente la velocidad, es simplemente agradable y legible usar registros de manera predecible.


Aquí es cómo escribiría personalmente el código para su función, tomando todas estas cosas que he mencionado en cuenta:

  cmp  eax, ebp 3  

ADVERTENCIA: todavía hay un error en aquí, dependiendo de cómo se comporta la función cmp eax, ebp 4 . Normalmente, en la mayoría de las convenciones de llamadas, se permite que una función se deslice el cmp eax, ebp 5 , cmp eax, ebp 6 y 99887766555443347 Registros. Por lo tanto, para estar en el lado seguro, tendría que asumir que 99887766555443348 podría deslobizar los valores en estos registros, lo que significa que tendría que elegir diferentes registros para almacenar sus valores temporales, o usted Tendría que guardar explícitamente los contenidos de estos registros antes de llamar a cmp eax, ebp 9 y restaurarlos después. Sin embargo, si sabe de la documentación o la implementación de createSquareNumbers: mov edx, 0 0 que no es clavo createSquareNumbers: mov edx, 0 1 o createSquareNumbers: mov edx, 0 2 , entonces está seguro. Su código original también tenía este error, pero funcionó correctamente, así que asumo que esto es seguro.

 

The biggest problem herexe2x80x94and generally with assemblyxe2x80x94is the use of registers in the code without explicit documentation of what those registers contain, e.g., in the form of comments. Certain assumptions can be made based on common calling conventions (like that functions will return their result in eax), but that should still be documented for clarity. In other cases, it is not quite so obvious. For example, in this code, ebp apparently contains the "upper limit", but other than assuming that the code works and reverse-engineering its logic, I have no basis for that assumption. In fact, in the current implementation, the contents of the ebp register are a pre-condition for the function, so its use should unarguably be documented as part of the function's signature.

I further assume that ebp is actually just 1000000, in which case this portion of your code is overly verbose (and thus inefficient). You can simply do:

cmp  eax, 1000000          ; see if input is over the upper limit jle  createSquareNumbers   ; if not, skip the next line mov  eax, 1000000          ; if it is, bound it 

If you are targeting the Pentium Pro or later (so basically, any modern x86 processor) you can use conditional move instructions like so:

cmp    eax, ebp cmovg  eax, ebp 

In some cases, conditional moves will make the code faster by eliminating the possibility of branch mispredictions. In this case, you won't see any performance improvement, but these instructions are still something to be aware of and this does make the code slightly easier to read. (Assuming that you want to keep the dependency on the ebp register, which might be handy if you want to make the upper limit variable. Otherwise, just hard-code the 1000000 and use the first version. Better yet, define a constant for that upper bound so you can give it a readable name.)


mov edx, 0 

This is completely correct code for setting a register to 0. However, it is not idiomatic x86 assembly because it is inefficient. Both compilers and expert assembly-language programmers will instead write this as:

xor  edx, edx 

This works because bitwise XORing any value with itself gives you back 0. The advantage is that it is shorter (2 bytes instead of 5 bytes) and faster.

The only time you would ever want to use mov reg, 0 is if you didn't want to clobber the flags, which is handy when you're preparing to do a conditional move and in certain other tightly hand-optimized code. Otherwise, whenever you want to clear a register, think xor reg, reg. While this is arguably a micro-optimization, you really do need to know this idiom so you can read other peoples' (and compilers') assembly code, so you might as well use it yourself.


I can tell that you're working with signed integer values because you're using the l and g condition codes. If you were working with unsigned integer values, you'd be using the b and a condition codes. Therefore, you should be doing a signed integer multiply (imul instead of mul). In fact, imul is preferable for a number of reasons, chief among them is the fact that it has a normal, two-operand form that works just like any other instruction, which means you don't have to worry about the hard-coded register operands that mul requires. This makes the code easier to optimize and, frankly, easier to read. You can use imul for unsigned multiplications, too, as long as you aren't worried about overflow, since the lower 32 bits are always the same. (And clearly you aren't worried about that here, since your print_eax function used to display the output can only print 32-bit values!)


There is, unfortunately, one bug lying in wait here:

You are only checking the upper bound, so what happens when the input value is 0? Your code will print 1, when instead it should print nothing! Also, I wonder if it is possible for read_hex to return a negative value? I don't have the function's implementation, or any documentation for it, so I can't tell. If you're not confident that it won't ever return a negative value, your code needs to handle this case. Fortunately, you can fix both of these potential problems easily by adding a check for the lower bound before entering the loop.

And while we're nitpicking, some of your comments have "bugs", too:

  • call print_eax ; ; Provided by teacher. Prints eax to stdin. 

    That should be stdout, not stdin! :-)

  • ; =========================================== ; = Displays the square number from 1 until = ; =  number n. n is given by the user.      = ; = @param [ integer ] n - The upper        = ; =  limit. The last integer to calculate   = ; =  the square number of.                  = ; =========================================== 

    Your function does not actually take a parameter. Rather, it retrieves the input by calling the read_hex function. Therefore, this documentation is wrong, because it suggests that the function should be able to read a parameter from the normal register used for passing parameters in whatever calling convention it uses, which is not correct. It is important to document your functions, and it's important to document them accurately. In actuality, the function neither receives nor returns any values.


In terms of readability, you should align your code into vertical columns so that instruction opcodes are clearly separated from their operands. This makes it much easier to read and scan. Also, you should either vertically align your end-of-line comments, or put them above the code.

I would also rename your createSquareNumbers label to printSquares, since (1) it prints output rather than simply creating things, and (2) "numbers" is redundant. I also personally like to use PascalCase for labels to make them distinct, but whichever is fine, as long as you're consistent.

Finally, I would prefer to use the ecx register as my loop "counter", since that's what it was originally intended for (the "c" stands for "counter"). Technically, this doesn't matter at all in modern x86 code, and you can treat all of the registers as general-purpose registers, using them interchangeably. And, indeed, in optimized code, you will want to do this. But when you're writing it by hand and don't necessarily care about speed, it is just nice and readable to use registers in a predictable way.


Here is how I would personally write the code for your function, taking all of these things that I've mentioned into account:

start:     ; Provided by teacher: reads in a hexadecimal number from stdin,     ; returning it in EAX.     call   read_hex      ; Check for underflow: if the input value is less than or equal to 0,     ; we skip the loop altogether.     ; NOTE: The naive way to do this check is "cmp eax, 0", but as with     ;       the XOR trick discussed above, "test eax, eax" is both     ;       shorter and faster, so that's what everyone uses when they     ;       want to test the value of a register against 0.     ;       (You still use CMP with memory operands, of course.)     test   eax, eax     jle    finished      ; Check for overflow: if the input value is larger than 1000000,     ; we bound it to be exactly 1000000.     cmp    eax, 1000000     mov    edx, 1000000     cmovg  eax, edx      ; Prepare to enter the loop:     mov    edx, eax        ; make a copy of the input     mov    ecx, 1          ; initialize our counter  printSquares:     mov    eax, ecx        ; put the counter into our scratch register, EAX     imul   eax, eax        ; EAX = EAX * EAX      ; Print the square (in EAX) by calling teacher-provided function,     ; which prints the contents of EAX to stdout.     ; It may or may not clobber EAX (we don't care),     ; but it does not return a significant value.     ; TODO: Make sure it doesn't clobber ECX or EDX!     call   print_eax      inc    ecx             ; increment our counter      ; See if we've reached the input value yet.     ; If not, continue looping.     cmp    ecx, edx     jle    printSquares  finished:     ; We're now done with the loop, so exit.     push   0     call   [ExitProcess] 

Warning: There is still potentially a bug in here, depending on how the print_eax function behaves. Normally, in most calling conventions, a function is allowed to clobber the eax, ecx, and edx registers. Therefore, to be on the safe side, you would have to assume that print_eax could clobber the values in these registers, meaning that you would have to either pick different registers to store your temporary values in, or you would have to explicitly save the contents of these registers before calling print_eax and restore them afterwards. However, if you know from the documentation or implementation of print_eax that it does not clobber either ecx or edx, then you are safe. Your original code had this bug, too, yet it worked properly, so I assume this is safe.

 
 
         
         
8
 
vote
vote
La mejor respuesta
 
  cmp eax, 1000000 ; When the number received is smaller the upper limit ... jle createSquareNumbers ; ... then you do not have to override it. mov     ecx, ebp ;   

Sospecho que el límite superior está en el registro 9988776665544332 . (¿Por qué más lo movería a ecx cuando la entrada es demasiado grande?) Entonces, sería mejor no usar el valor inmediato y simplemente escribir:

  cmp  eax, ebp   

  createSquareNumbers: mov edx, 0   

Antes de la multiplicación, no es necesario cero el registro 9988776665544336 . Es la división la que necesita esto. De esta manera se afeitará algunos bytes.


¡Debe probar el desbordamiento en la multiplicación! Usted está imprimiendo resultados del EAX Registro, pero mira lo que sucede cuando el cuadrado excede los 32 bits de EAX . Debe usar este desbordamiento ( 9988776655544339 & lt; & gt; & gt; & gt; & gt; & gt; & gt; & gt; & gt; & gt; & gt; & gt; & gt; & gt; & gt; & gt; & gt;


Debes escribir de una manera más legible.
Le sugiero que haga todas las instrucciones, operandos y comentarios comienzan en sus propias columnas.

  cmp eax, 1000000 ; When the number received is smaller the upper limit ... jle createSquareNumbers ; ... then you do not have to override it. mov     ecx, ebp ; 0  

 
cmp eax, 1000000 ; When the number received is smaller the upper limit ... jle createSquareNumbers ; ... then you do not have to override it. mov     ecx, ebp ; 

I suspect that the upper limit is in the ebp register. (Why else would you move it to ecx when the input is too large?) Then it would be better to not use the immediate value and just write:

cmp  eax, ebp 

createSquareNumbers: mov edx, 0 

Prior to the multiplication it is not necessary to zero the edx register. It's the division that needs this. This way you'll shave off some bytes.


You should test for overflow on the multiplication! You are printing results from the EAX register only, but look what happens when the square exceeds the 32 bits of EAX. You should use this overflow (EDX <> 0) as an extra reason to exit the loop.


You should write in a more readable fashion.
I suggest you make all the instructions, operands, and comments start in their own columns.

start:     call    read_hex            ; Provided by teacher. Reads in a hexadecimal number from stdin.     mov     ecx, eax     mov     ebx, 1              ; First number ...     cmp     eax, ebp            ; When the number received is smaller the upper limit ...     jle     createSquareNumbers ; ... then you do not have to override it.      mov     ecx, ebp            ; ... otherwise you override it with the upper limit. 
 
 
 
 

Relacionados problema

16  X64 Assembly ClearMem / Zeromem  ( X64 assembly clearmem zeromem ) 
Acabo de empezar a aprender a la asamblea de ayer, y la primera cosa útil que he escrito es una función 99887776655544330 . Busco comentarios generales con...

5  Mostrar valor hexadecimal almacenado en un registro  ( Display hexadecimal value stored at a register ) 
Leí un libro sobre el desarrollo del sistema operativo y se enfrenta a un ejercicio simple: Escriba una función que imprime un valor hexadecimal almacenado e...

3  Calculadora de RPN de NASM  ( Nasm rpn calculator ) 
He estado aprendiendo ensamblaje en los últimos días, y he hecho una simple calculadora RPN. Aquí está la lógica principal del programa, excluyendo las func...

7  8086 ASM BRESENHAM LINE ALGORITHM PT2  ( 8086 asm bresenhams line algorithm pt2 ) 
Siguiendo la Revisión exitosa de mi implementación de la línea de algoritmo de Bresenham, i Se le ha pedido que cargue la implementación completa de mi proy...

1  X86-16 Función 01 -> Cambiar destino y / o Páginas de visualización  ( X86 16 function 01 change destination and or display pages ) 
Este código está diseñado para ser incluido con x86-16 Escribiendo cadenas asciiz directamente a video y depende de algunas de las declaraciones en ese códi...

3  Leyendo todos los contenidos de archivos a través de la Asamblea X64  ( Reading all file contents via x64 assembly ) 
He surgido con el siguiente fragmento mediante la construcción de las respuestas que se le da a mi Pregunta de StackOverflow . Solo tratando de obtener otros...

6  Encontrar el máximo de una lista dada de datos en la Asamblea GNU X86 (32 bits)  ( Finding the maximum of a given list of data in gnu assembly x86 32 bit ) 
Estoy siguiendo el libro Programación de la base hacia arriba y como respuesta a una pregunta En la sección use los conceptos del capítulo 4: Conviert...

13  Programa de cuenta regresiva en X86 NASM  ( Countdown program in x86 nasm ) 
Soy bastante nuevo para la programación de idiomas de montaje y, para la práctica, me di un problema: cuenta desde 10 y justo después de 1, di "¡Blast Off!". ...

2  X86-16 Escribiendo cadenas asciiz directamente al video  ( X86 16 writing asciiz strings directly to video ) 
Mientras desarrolla mi sistema operativo, decidí que había una necesidad de ser más verbosa sobre lo que estaba sucediendo en modo real. La idea de incrustar ...

5  Sumando todos los primos por debajo de 2,000,000 - Project Euler # 10 en ensamblaje  ( Summing all primes below 2 000 000 project euler 10 in assembly ) 
Actualmente estoy aprendiendo asambleas para la universidad, y me gustaría escuchar algunos comentarios sobre lo que he escrito. Actualmente he implementado p...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos