Cómo invertir una cadena en ensamblador x86: 8086, 8088...
Este pequeño código en lenguaje ensamblador está hecho para las arquitecturas x86 (procesadores Intel y AMD 32 bits) y usa la sintaxis de NASM, un ensamblador libre, gratuito y que puede ser utilizado en diferentes plataformas como Linux o Windows.
¿Cuáles son las funciones y nociones utilizadas?
Las funciones externas utilizadas provienen de la biblioteca C estándar. Por tanto, no tendrás problemas con tu equipo al utilizar este código, ya que no depende del sistema operativo utilizado. Únicamente depende de la arquitectura x86. Para saber cómo utilizar NASM a fin de probar este código, consulta el siguiente artículo: Cómo compilar un programa ensamblador con NASM.
¿Qué es una cadena de caracteres?
Un carácter ASCII estándar está codificado en un octeto. Por lo tanto, una cadena de caracteres es una serie de caracteres de un octeto y no de cuatro como los enteros. Una cadena termina con el carácter 0 (el valor 0 y no el carácter '0'). Además, únicamente se pueden apilar elementos de dos o cuatro octetos en la pila (a menos que se trate de la constante de un octeto).
Por otro lado, probablemente sepas que la pila es una memoria cuyo acceso es del tipo Last In First Out. Lo que significa que, si pones en la pila tres letras una a continuación de la otra, por ejemplo primero a, luego b y luego c, las recuperarás en el orden inverso: primero c, luego b y luego a.
¿Cuál es el código en C?
El objetivo es crear una función en ensamblador que sea capaz de invertir una cadena de caracteres. La función deberá aceptar como parámetro de entrada un puntero hacia una cadena de caracteres e invertir esta cadena en el mismo puntero. El código en C sería:
void invertir_cadena(char *str); //El modelo de esta función //Ejemplo de uso: char string[] = "Esta es la cadena a invertir"; invertir_cadena(string); printf(string); /*Mostrará "ritrevni a anedac al se atsE", que es el resultado del string invertido.*/
¿Cuál es el código en ensamblador x86?
- A continuación, el código:
invertir_cadena: ;Inicio de la función push ebp mov ebp, esp ;Cargamos el puntero pasado como parámetro en eax mov eax, [ebp+8] ;Introducimos el carácter de fin de cadena en la pila push word 0 cadena_en_pila: ;Vamos a apilar cada carácter de la cadena ;Recuperación del carácter actual mov bl, byte [eax] ;¿Es el fin de la cadena? (bl = 0 ?) test bl, bl ;Si es sí, pasamos a la etapa siguiente jz fin_cadena_en_pila ;Si no, apilamos el siguiente carácter push bx ;Incrementamos el puntero en 1 para procesar el siguiente carácter inc eax ;Pasamos al siguiente carácter jmp cadena_en_pila fin_cadena_en_pila: ;Volvemos a cargar el puntero de la cadena ;para desapilar uno a uno cada carácter mov eax, [ebp + 8] invertir: ;Desapilamos el carácter actual pop bx ;Lo cargamos en el puntero de cadena mov byte [eax], bl ;Incrementamos la dirección inc eax ;¿Era el fin de la cadena? (¿el 0 que hemos apilado al inicio?) test bl, bl ;No, entonces continuamos jnz invertir ;Es el fin de la cadena, indicamos el fin de la función leave ret
- Deberás insertar el código aquí:
extern printf section .data cadena db '¡Inviérteme! ¡Yo te diré qué programador eres!', 0x0 section .text global main, invertir_cadena invertir_cadena: ;Pon el código aquí main: mov eax, cadena ;Dirección de cadena en eax push eax ;Llamada a invertir_cadena con la dirección de la cadena a invertir call invertir_cadena ;Las dos líneas siguientes son opcionales ya que la dirección ;de la cadena (ahora invertida) siempre está en la pila. mov eax, cadena ;Dirección de la cadena en eax push eax ;Visualización de la cadena con printf call printf add esp, 4 ;Salimos de la función main mov eax, 0
¿Cuál es el algoritmo utilizado?
Como ha sido sugerido anteriormente, lo ideal es utilizar la pila. Se apilan los caracteres de la cadena y al acceder a ellos se obtienen en el orden inverso. Esto es lo que ocurre:
- Primero apilamos 0, que será obtenido al final para indicar el fin de la cadena.
- Luego, apilamos uno a uno cada carácter de la cadena pasando por bl (los 8 caracteres de menor peso de ebx) que es suficiente para un carácter. Al apilar, tomamos bx (16 bits de menor peso de ebx) que contiene a su vez bl, pues estamos obligados a apilar al menos 2 octetos. Durante estos sucesivos apilamientos, tendremos cuidado de no apilar el carácter de fin.
- En la siguiente etapa, volvemos a cargar el puntero con la dirección del inicio de la cadena. Luego, vamos obteniendo sucesivamente cada carácter en la cadena borrando los valores antiguos. Cuando hayamos llegado al 0 de fin de cadena, también lo insertamos en la cadena y terminamos.