Capítulo 7. Modos de Direccionamiento

Tabla de contenidos

7.1. Notación
7.2. Modos del direccionamiento del Intel Pentium
7.2.1. Modo inmediato
7.2.2. Modo registro
7.2.3. Modo absoluto
7.2.4. Modo registro indirecto
7.2.5. Modo auto-incremento
7.2.6. Modo auto-decremento
7.2.7. Modo base + desplazamiento
7.2.8. Modo base + índice
7.2.9. Modo índice escalado + desplazamiento
7.2.10. Modo base + índice escalado + desplazamiento
7.2.11. Utilización de los modos de direccionamiento
7.3. Hardware para el cálculo de la dirección efectiva
7.4. Resumen de los modos de direccionamiento
7.5. Ejercicios

En este capítulo se estudia una parte concreta de la ejecución de cada instrucción máquina: la obtención de los operandos. Tras recibir una instrucción el procesador debe obtener los operandos necesarios que están almacenados en registros de propósito general, en la propia instrucción o en memoria. El acceso a los dos primeros es sencillo, pero el acceso a memoria puede ser arbitrariamente complejo.

A pesar de que cada dato almacenado en memoria tiene una dirección, en la práctica, se suelen utilizar un conjunto de operaciones aritméticas para obtenerla. La figura 7.1 muestra un ejemplo de las operaciones necesarias para obtener la dirección de un elemento en una tabla de enteros.

Figura 7.1. Dirección de un elemento en una tabla de enteros

Dirección de un elemento en una tabla de enteros

Supóngase una tabla de números enteros de 32 bits almacenados a partir de la posición 100 de memoria. El único dato del que se dispone es dicho valor. ¿Cómo se puede acceder al elemento con índice 3 de la tabla? Sabiendo que los enteros tienen 4 bytes de tamaño, sumando a la dirección base de la tabla el tamaño de los 3 números anteriores se obtiene la dirección del entero deseado.

Los cálculos para obtener una dirección de memoria suelen requerir operaciones de suma, resta, multiplicación y división y por tanto pueden realizarse utilizando las instrucciones aritméticas del procesador. Para acceder a un operando en memoria se calcula primero su dirección con las operaciones pertinentes y luego se accede a memoria.

Pero estos dos pasos resultan ser extremadamente frecuentes en la ejecución de los programas convencionales. Una parte importante de las instrucciones ejecutadas por un procesador están destinadas al cálculo de la dirección de un operando que se necesita para una operación posterior.

A la vista de esta situación, el diseño de los procesadores ha ido incorporando a la propia instrucción máquina la posibilidad de realizar ciertos cálculos sencillos para la obtención de la dirección de sus operandos. La estrategia consiste en incluir los cálculos más comunes como parte de la instrucción y de esta forma conseguir secuencias de instrucciones más compactas, puesto que las operaciones aritméticas se hacen igual pero sin necesidad de ejecutar múltiples instrucciones, y por consiguiente se obtiene una mayor eficiencia en la ejecución.

Para ilustrar la ventaja de los modos de direccionamiento considérese la situación en la que el procesador debe acceder a un operando en memoria cuya dirección d se obtiene mediante la ecuación 7.1. Supóngase que se debe sumar el valor 300 al número de 32 bits almacenado en esta posición. Una posible secuencia de instrucciones para realizar tal operación se ilustra en el ejemplo 7.1

Ecuación 7.1. Expresión de la dirección de un dato en memoria

Ejemplo 7.1. Cálculo de la dirección de un operando mediante instrucciones

        mov %ebx, %ecx    # %ecx = %ebx
        sal $2, %ecx      # %ecx = (%ebx * 4)
        add %eax, %ecx    # %ecx = %eax + (%ebx * 4)
        add $1000, %ecx   # %ecx = 1000 + %eax + (%ebx * 4)
        addl $300, (%ecx) # Sumar 300 a la posición indicada por %ecx

Las cuatro primeras instrucciones calculan el valor de la dirección del operando y es únicamente la última la que realiza el acceso y la suma de la constante $300. Esta última instrucción está accediendo a memoria de una forma especial. La expresión de su segundo operando (%ecx) indica que el operando está en memoria en la posición contenida en el registro %ecx. Se precisa el sufijo de tamaño porque el primer operando es una constante, y el segundo operando, a pesar de contener el nombre de un registro, en realidad especifica una dirección de memoria.

Supóngase ahora que el lenguaje máquina del procesador permite escribir la siguiente instrucción:

addl $300, 1000(%eax, %ebx, 4)

El efecto de esta instrucción es exactamente el mismo que el de las cinco instrucciones del ejemplo 7.1. Se realiza la suma de 300 y el número de 32 bits almacenado en memoria en la posición obtenida al sumar 1000, el contenido del registro %eax y el contenido del registro %ebx previamente multiplicado por cuatro. Este segundo operando es a la vez fuente y destino.

La existencia de tal instrucción en el lenguaje máquina tiene la ventaja de que en una instrucción el procesador recibe mucha más información sobre la operación a realizar. En el primer caso se precisan cinco instrucciones, o lo que es lo mismo, cinco ciclos de ejecución como los descritos en la sección 4.2. En el segundo caso, con una única instrucción el procesador dispone de todos los ingredientes para realizar los cálculos. La mejora en rendimiento no se deriva del número de operaciones aritméticas, pues son exactamente las mismas en ambos casos, sino del número de instrucciones que se ejecutan.

Pero a cambio, el principal inconveniente de esta solución es que la codificación y longitud de las instrucciones se complica tanto como complejas sean las operaciones que se permiten en una instrucción. En este caso se realizan dos sumas y una multiplicación para obtener la dirección de un operando que a su vez participa en la suma final.

Al conjunto de métodos que el lenguaje máquina de un procesador ofrece para acceder a sus operandos se les denomina modos de direccionamiento. El número de métodos diferentes depende de cada procesador y varía enormemente entre diferentes diseños. Los modos de direccionamiento del Intel Pentium contemplan el acceso a operandos como los utilizados en la instrucción addl $300, 1000(%eax, %ebx, 4).

La figura 7.2 ilustra el funcionamiento de los modos de direccionamiento de un procesador.

Figura 7.2. Funcionalidad de los modos de direccionamiento

Funcionalidad de los modos de direccionamiento

En general un modo de direccionamiento es un procedimiento que dado un conjunto de bits o campos de la instrucción calcula el lugar en el que se encuentra almacenado un operando.

7.1. Notación

Para especificar en detalle el funcionamiento de los modos de direccionamiento se precisa una notación para referirse a los componentes de las operaciones necesarias.

La “dirección efectiva de un operando” que se denota por de es el lugar en el que se encuentra almacenado un operando. Esta dirección no tiene por qué ser una posición de memoria. Existen modos de direccionamiento en los que la dirección efectiva de un operando es un registro de propósito general.

La instrucción de la que se obtienen los datos necesarios para calcular de se denota por inst, la dirección de memoria a partir de la cual está almacenada es @inst, y los diferentes campos de esta instrucción se denotan por instc1, ..., instcn. Por campos se entienden aquellos bits que forman parte de la instrucción y que codifican los diferentes elementos necesarios para el cálculo de la dirección efectiva.

Cuando el campo instci codifica uno de los registros de propósito general, dicho registro se denota por Rci. La expresión MEM[n] denota el contenido de memoria almacenado a partir de la posición n. La acción de modificar el contenido del registro R con el dato d se denota R ← d.

A continuación se estudian en detalle los modos de direccionamiento disponibles en la arquitectura IA-32 así como su sintaxis en ensamblador. Para cada uno de ellos se especifica la fórmula utilizada para obtener la dirección efectiva del operando y su valor.

7.2. Modos del direccionamiento del Intel Pentium

Tal y como se ha visto en la sección 5.3.3, los operandos de una instrucción se dividen en cuatro categorías: constantes, registros, direcciones de memoria y operandos implícitos. Excepto estos últimos, el resto se obtienen a través de diferentes modos de direccionamiento. A continuación se presentan en orden creciente de complejidad para finalmente comparar todos ellos con el más complejo.

7.2.1. Modo inmediato

Es el modo de direccionamiento utilizado para obtener operandos de tipo constante, es decir, aquellos que tienen el prefijo $ en ensamblador. El operando está incluido en la propia instrucción. La expresión de su dirección efectiva se muestra en la ecuación 7.2.

Ecuación 7.2. Dirección efectiva y operando del modo inmediato

El valor k representa el número de bytes a partir de la dirección en la que está almacenada instrucción en la que se codifica la constante. La figura 7.3 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo.

Figura 7.3. Acceso a operando con modo inmediato

Acceso a operando con modo inmediato

El primer operando de la instrucción es la constante $3 que tal y como se ve en la figura, en lenguaje máquina se codifica en el último byte. Por tanto, la dirección efectiva del operando es de = @inst + 4. Cuando el procesador necesita este operando, lo obtiene directamente de los bytes de la instrucción.

A pesar de que el sufijo de la instrucción indica que los operandos han de ser de 32 bits, el primer operando se codifica con un único byte. Esta aparente inconsistencia no es más que un mecanismo que utiliza el procesador para que el código máquina sea más compacto. Cuando en una instrucción se codifica un operando en modo inmediato se utilizan 1, 2 o 4 bytes dependiendo de su valor. En este ejemplo, como el valor es $3 sólo precisa un byte.

7.2.2. Modo registro

Es el modo de direccionamiento utilizado para obtener operandos almacenados en uno de los ocho registros de propósito general. La instrucción contiene un campo instc1 de 3 bits que codifica los ocho posibles registros. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.3.

Ecuación 7.3. Dirección efectiva y operando del modo registro

La dirección efectiva del operando, en este caso, no es una dirección de memoria, sino la de uno de los registros de propósito general. La figura 7.4 muestra el funcionamiento de este modo de direccionamiento y un ejemplo.

Figura 7.4. Acceso a operando con modo registro

Acceso a operando con modo registro

En la figura, el código de operación 0x89 no sólo indica que se realiza una operación de mover, sino que el primer operando es de tipo registro. El nombre del registro está codificado en el segundo byte. También en este byte, se codifica el tipo del segundo operando, que es igualmente de tipo registro. Para ello se utiliza el campo R/M del byte ModR/M.

7.2.3. Modo absoluto

Los modos de direccionamiento restantes se refieren todos ellos a operandos almacenados en memoria y se diferencian en los cálculos para obtener la dirección efectiva de.

En este modo de direccionamiento la dirección efectiva corresponde con una dirección de memoria y forma parte de los bytes que codifican la instrucción. En otras palabras, la propia instrucción, en su codificación incluye una dirección de la que obtener uno de sus operandos. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.4.

Ecuación 7.4. Dirección efectiva y operando del modo absoluto

Como la instrucción contiene la dirección efectiva, ésta está contenida en memoria desplazada k bytes con respecto a la dirección @inst. El operando está en memoria en la posición que indica de, de ahí la doble expresión MEM[]. La figura 7.5 muestra el funcionamiento de este modo de direccionamiento.

Figura 7.5. Acceso a operando con modo absoluto

Acceso a operando con modo absoluto

Como muestra la figura, la dirección de memoria ocupa 4 de los bytes que codifican la instrucción. En el ejemplo que se muestra, la dirección es 0x0000059A pues los datos se almacenan en little endian. La representación en ensamblador de este modo de direccionamiento es mediante una etiqueta. El ensamblador asigna a cada una de ellas un valor, y cuando se utiliza en una instrucción se reemplaza el símbolo por el valor de su dirección de memoria.

7.2.4. Modo registro indirecto

El modo registro indirecto accede a un operando en memoria utilizando como dirección el valor contenido en uno de los registros de propósito general. La palabra “indirecto” hace referencia a que primero se obtiene el valor del registro y luego se utiliza dicho valor como dirección de memoria. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.5.

Ecuación 7.5. Dirección efectiva y operando del modo indirecto

Para codificar este modo de direccionamiento sólo es preciso incluir el código del registro de propósito general a utilizar, por tanto con 3 bits es suficiente. La responsabilidad de que en el registro utilizado para la indirección esté contenida una dirección de memoria correcta recae totalmente en el programador. La figura 7.6 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo.

Figura 7.6. Acceso a operando con modo registro indirecto

Acceso a operando con modo registro indirecto

Tal y como muestra la figura, la sintaxis para denotar este modo de direccionamiento en una instrucción es con el nombre del registro escrito entre paréntesis. El operando se obtiene de memoria mediante la dirección almacenada en el registro. La principal ventaja de este modo de direccionamiento es que, al estar almacenada la dirección en un registro, esta se puede manipular con las operaciones aritméticas que ofrece el procesador como cualquier otro dato.

Por ejemplo, la instrucción MOV (%esp), %eax carga en el registro %eax el valor almacenado en la cima de la pila. La instrucción es correcta porque el registro %esp contiene la dirección de memoria de la cima de la pila.

Considérese la secuencia de instrucciones del ejemplo 7.2. La primera instrucción simplemente copia el valor del puntero de pila en el registro %eax. La siguiente instrucción suma la constante $4 al valor almacenado en la cima de la pila. La instrucción necesita el sufijo de tamaño porque el segundo operando es un registro indirecto, con lo que especifica la dirección de memoria del operando, pero no su tamaño.

Ejemplo 7.2. Acceso a los elementos de la pila con el modo registro indirecto

        mov %esp, %eax
        addl $4, (%eax)
        add $4, %eax
        addl $4, (%eax)

La instrucción add $4, %eax muestra como una dirección de memoria se puede manipular como un dato numérico. Al sumarle $4 el nuevo valor obtenido es la dirección de memoria del elemento almacenado en la pila justo debajo de la cima. La última instrucción suma la constante $4 al operando almacenado en memoria en la dirección contenida en el registro %eax, o lo que es lo mismo, al dato almacenado debajo de la cima de la pila.

En el ejemplo anterior, se ha manipulado el contenido de los datos almacenados en la pila sin modificar el puntero a su cima. Esto es posible gracias a que se han hecho los cálculos con una dirección de memoria que es una copia del puntero a la cima.

El modo registro indirecto se puede utilizar para acceder a cualquier dato en memoria. En el ejemplo 7.3 se muestra una secuencia de instrucciones que acceden a elementos de una tabla de datos de 16 bits almacenados a partir de la dirección de memoria representada por la etiqueta tabla.

Ejemplo 7.3. Acceso a los elementos de una tabla con el modo registro indirecto

        mov $tabla, %eax
        mov (%eax), %bx
        add $2, %eax
        mov (%eax), %cx
        add $6, %eax
        mov (%eax), %dx

La primera instrucción almacena en el registro %eax la dirección de memoria representada por la etiqueta tabla. El valor exacto que se carga en el registro es imposible saberlo de antemano, pues depende de dónde en memoria estén almacenados los datos, pero igualmente se puede utilizar con los modos de direccionamiento.

La segunda instrucción accede al primer elemento de la tabla con el modo registro indirecto. El operando destino es el registro %bx que por tanto, fija el tamaño de dato a ser transferido a 16 bits y no es necesario el sufijo de tamaño. La siguiente instrucción suma la constante $2 al registro %eax y se accede de nuevo a un elemento de la tabla mediante registro indirecto. En este caso se almacena en el registro %cx el número de 16 bits almacenado en segunda posición. La instrucción add $6, %eax hace que el registro contenga la dirección del elemento en la quinta posición, o lo que es equivalente, con índice 4. La última instrucción accede a este elemento de la tabla y lo almacena en %dx.

7.2.5. Modo auto-incremento

El funcionamiento del modo auto-incremento es similar al modo registro indirecto, con la salvedad de que el registro, tras ser utilizado para el acceso a memoria, incrementa su valor en una constante. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.6

Ecuación 7.6. Dirección efectiva y operando del modo auto-incremento

El efecto de la modificación del valor contenido en el registro es que la dirección pasa ahora a apuntar a una posición de memoria cuatro bytes más alta que el valor anterior. En principio, cualquier registro de propósito general puede ser utilizado, pero en el caso concreto de la arquitectura IA-32, este modo únicamente se utiliza en la instrucción POP y con el registro %esp. La figura 7.7 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo de la instrucción POP.

Figura 7.7. Acceso a operando con modo auto-incremento

Acceso a operando con modo auto-incremento

Al tratarse de la única instrucción del Intel Pentium que utiliza este modo de direccionamiento y que únicamente se utiliza el registro %esp, la codificación en la instrucción de este modo de direccionamiento es un caso especial, pues está implícita en el código de operación. El byte con valor 0x58 no sólo codifica la operación POP sino también la utilización del modo auto-incremento con el registro %esp para obtener el dato a almacenar.

El efecto de este modo de direccionamiento corresponde con el comportamiento de la instrucción POP. El operando implícito es el dato de la cima de la pila que se obtiene mediante la dirección de memoria almacenada en %esp. Este operando se almacena donde indica el único operando explícito de la instrucción, y a continuación se incrementa el valor de %esp de forma automática en 4 unidades. Como consecuencia, el puntero de pila apunta a la nueva cima.

7.2.6. Modo auto-decremento

El modo auto-decremento es similar al auto-incremento pues realiza una indirección y modifica el registro utilizado, pero la modificación de la dirección se realiza antes de acceder a memoria. La funcionalidad de este modo de direccionamiento se puede considerar complementaria a la anterior. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.7

Ecuación 7.7. Dirección efectiva y operando del modo auto-decremento

La dirección efectiva no está directamente contenida en el registro especificado, sino que previamente se resta la constante 4 a su valor y se accede a memoria con el valor resultante. Además, este valor, tras acceder a memoria, se almacena de nuevo en el registro. En principio cualquier registro de propósito general puede utilizarse para este modo de direccionamiento, pero en el caso de la arquitectura IA-32, este modo únicamente se utiliza en la instrucción PUSH y con el registro %esp. La figura 7.8 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo de la instrucción PUSH.

Figura 7.8. Acceso a operando con modo auto-decremento

Acceso a operando con modo auto-decremento

Al igual que en el caso del modo auto-incremento, al ser PUSH la única instrucción que utiliza este modo en el Intel Pentium, su codificación está implícita en el código de operación 0x6A. El operando implícito, por tanto es la dirección de la nueva cima en la que almacenar el valor dado como operando explícito y que se obtiene restando 4 del registro %esp.

7.2.7. Modo base + desplazamiento

El modo de direccionamiento base + desplazamiento obtiene la dirección efectiva del operando mediante la utilización de dos elementos. Este es el primer ejemplo en el que el procesador, como paso previo para la obtención de los operandos, obtiene más de un dato de la instrucción y lo utiliza para calcular la dirección efectiva. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.8.

Ecuación 7.8. Dirección efectiva y operando del modo base + desplazamiento

En este modo, la dirección efectiva no está contenida en ningún lugar, sino que es el resultado de la suma del contenido de un registro y de un valor almacenado como parte de la instrucción. El procesador obtiene estos dos valores, los suma, y el resultado lo utiliza para acceder a memoria y obtener el operando. El nombre de este modo se deriva de que al registro utilizado se le conoce como el nombre de “base” mientras que el valor numérico adicional se conoce como “desplazamiento”. Como la operación que se realiza entre ambos es la suma, se puede considerar que la dirección efectiva del operando se obtiene partiendo de un registro base cuyo valor se desplaza tanto como indica la constante.

La sintaxis para especificar este modo de direccionamiento tiene dos posibles formatos. El primero consiste en escribir el nombre del registro entre paréntesis precedido de una constante entera sin prefijo alguno, como por ejemplo la instrucción INCL 12(%ecx). La constante puede ser escrita en cualquiera de los formatos permitidos por el ensamblador: decimal, binario, octal, hexadecimal o letra.

El segundo formato permitido consiste en escribir el nombre del registro entre paréntesis precedido por el nombre de una etiqueta previamente definida, como por ejemplo la instrucción SUB %eax, dato(%ecx). El valor dato hace referencia a la dirección de memoria en la que esta etiqueta ha sido definida, por lo que la dirección efectiva se obtiene sumando la dirección de la etiqueta con el valor contenido en el registro base. En los programas ensamblador se utilizan ambas notaciones de forma muy frecuente. La figura 7.9 ilustra el funcionamiento de este modo de direccionamiento así como dos ejemplos.

Figura 7.9. Acceso a operando con modo base + desplazamiento

Acceso a operando con modo base + desplazamiento

La instrucción ADD %eax, 16(%ebx) utiliza el primer formato de este modo en su segundo operando. La instrucción codifica el desplazamiento con sólo 8 bits al tener un valor entre -128 y 127. El byte ModR/M con valor 0x43 codifica que el modo de direccionamiento del primer operando es registro y que el del segundo es base + desplazamiento con el registro %ebx y una constante de 8 bits.

La instrucción ADD %eax, contador(%ecx) tiene una codificación de 6 bytes. Los cuatro últimos codifican la dirección de memoria que representa la etiqueta contador. El byte ModR/M con valor 0x81 en este caso codifica el modo de direccionamiento del primer operando que es registro y que el del segundo es con registro base %ebx y desplazamiento de 32 bits.

Este modo de direccionamiento ofrece un mecanismo muy eficiente para acceder a tablas de elementos. Supóngase que se ha definido una tabla de números enteros de 32 bits y se escribe el código que se muestra en el ejemplo 7.4.

Ejemplo 7.4. Acceso a una tabla de enteros con modo base + desplazamiento

        .data
tabla:  .int 12, 32, -34, -1, 1232, 435
        .text
        .global main
main:
        ...
        mov $0, %ebx
        mov $0, %ecx
        ADD tabla(%ebx), %ecx
        ADD $4, %ebx
        ADD tabla(%ebx), %ecx
        ADD $4, %ebx
        ADD tabla(%ebx), %ecx
        ADD $4, %ebx
        ...

Los enteros se almacenan a partir de la posición de memoria que representa la etiqueta tabla. Las dos primeras instrucciones cargan el valor 0 en los registros %ebx y %ecx. Las siguientes instrucciones suman el valor de los tres primeros de la tabla y depositan el resultado en el registro %ecx. Para ello, el registro %ebx contiene el valor que sumado a la dirección de tabla se obtiene la dirección de los sucesivos elementos. La instrucción ADD tabla(%ebx), %ecx se repite sin modificación alguna y accede a elementos sucesivos porque el registro base va cambiando su valor. Este tipo de instrucciones son muy eficientes si se quiere procesar todos los elementos de una tabla, pues basta con escribir un bucle que vaya incrementando, en este caso, el valor del registro %ebx.

La utilización de constantes como desplazamiento se utiliza generalmente para acceder a elementos almacenados en posiciones alrededor de una dirección dada que se almacena en el registro base. Supóngase que el registro %edx contiene la dirección de memoria a partir de la cual están almacenados, por este orden, dos números enteros y cuatro letras ASCII. El código que se muestra en la ejemplo 7.5 accede a los enteros y las cuatro letras con el registro %edx como base y con diferentes desplazamientos.

Ejemplo 7.5. Definición y acceso a una tabla de enteros

        .data
        .int 34
dato:   .int 12, 24
        .ascii "abcd"
        .text
        .global main
main:
        ...
        mov $dato, %edx
        mov 0(%edx), %ecx
        add 4(%edx), %ecx
        mov 8(%edx), %ah
        mov 9(%edx), %al
        mov 10(%edx), %bh
        mov 11(%edx), %bl
        add -4(%edx), %ecx
        ...

La primera instrucción almacena en %edx la dirección de memoria que representa la etiqueta dato. En la segunda instrucción se utiliza el modo de direccionamiento base + desplazamiento pero con un desplazamiento igual a cero. El entero con valor 12 se almacena en el registro %ecx. Esta instrucción pone de manifiesto que el modo base + desplazamiento con un desplazamiento igual a cero, es equivalente al modo registro indirecto.

La siguiente instrucción add 4(%edx), %ecx suma el valor del segundo entero al registro %ecx. El desplazamiento tiene el valor 4 debido a que los enteros almacenados a partir de la etiqueta son de 4 bytes. La siguiente instrucción accede a la primera letra y la almacena en el registro de 8 bits %ah. En este caso el desplazamiento es 8 porque las letras están almacenadas en la posición siguiente a la del último byte del último entero de la definición anterior. Las siguientes instrucciones cargan las siguientes letras en los registros de 8 bits %al, %bh y %bl.

En este modo de direccionamiento, el número que precede al registro entre paréntesis es un entero, y por tanto puede tener valor negativo. La última instrucción muestra un ejemplo de este caso. De igual forma que se acceden a posiciones a continuación de la especificada por una etiqueta, también se puede acceder a posiciones previas mediante desplazamientos negativos. El procesador suma este desplazamiento al valor del registro y accede a esa posición de memoria. Con las definiciones del ejemplo la instrucción está sumando al registro %ecx el número 34 almacenado justo antes de la etiqueta.

7.2.8. Modo base + índice

El modo de direccionamiento base + índice es similar al anterior puesto que el procesador obtiene la dirección efectiva sumando de nuevo dos números, pero la diferencia es que ambos números se obtienen de registros de propósito general. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.9.

Ecuación 7.9. Dirección efectiva y operando del modo base + índice

Al igual que en caso del modo de direccionamiento anterior, la dirección efectiva no tiene por qué encontrarse en ningún registro sino que se obtiene sumando el valor contenido en los dos registros especificados. No existe restricción alguna sobre los valores que contienen los registros, el procesador realiza la suma y obtiene el operando de la posición de memoria obtenida como resultado. La sintaxis para especificar este modo de direccionamiento es mediante dos registros separados por una coma y entre paréntesis, por ejemplo CMPL $32, (%eax, %esi).

A pesar de que este modo se denomina base + índice, los dos registros especificados son idénticos a todos los efectos, con lo que cualquiera de los dos puede ser el registro base o el índice. La figura 7.10 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo.

Figura 7.10. Acceso a operando con modo base + índice

Acceso a operando con modo base + índice

La dirección efectiva se obtiene sumando los registros %ebx y %edi que previamente deben tener los valores pertinentes para que la dirección de memoria resultante sea la correcta. Supóngase una tabla con 100 elementos, cada uno de ellos, a su vez es una tabla de 5 enteros. Para acceder a un número se precisan dos índices, el primero para seleccionar uno de los 100 elementos, y el segundo para seleccionar uno de los 5 posibles enteros. Se quiere acceder a los cinco números del elemento tabla[32] almacenados a partir de la dirección de memoria contenida en el registro %eax. El ejemplo 7.6 muestra cómo acceder a estos elementos utilizando el modo de direccionamiento base + índice.

Ejemplo 7.6. Acceso a los enteros de un elemento de una tabla

        ....
        mov $0, %ebx
        mov (%eax, %ebx), %ecx
        add $4, %ebx
        add (%eax, %ebx), %ecx
        add $4, %ebx
        add (%eax, %ebx), %ecx
        add $4, %ebx
        add (%eax, %ebx), %ecx
        add $4, %ebx
        add (%eax, %ebx), %ecx
        ...

La primera instrucción carga el valor cero en %ebx. A continuación se accede al primer entero del elemento de la tabla sumando la dirección a partir de donde están almacenados, que está contenida en %eax y el valor en %ebx. Como este último registro tiene el valor cero, el acceso es idéntico al obtenido si se utiliza el modo registro indirecto.

A continuación se suma 4 al registro %ebx y se accede de nuevo con el mismo modo en este caso para sumar el contenido en memoria al registro %ecx. Tras ejecutar esta instrucción en %ecx se obtiene la suma de los dos primeros elementos. Mediante sucesivos incrementos del registro %ebx y luego accediendo a los elementos con el modo base + índice se calcula la suma de los cinco números en el registro %ecx.

La misma secuencia de instrucciones tendría un efecto idéntico si se intercambian los nombres de los registros en el paréntesis que especifica el modo base + índice.

7.2.9. Modo índice escalado + desplazamiento

En este modo de direccionamiento toman parte tres elementos de la instrucción para obtener la dirección efectiva del operando. Un registro, denominado el índice, ofrece un valor que se multiplica por un factor de escala especificado por una constante, y el resultado se suma a una segunda constante entera denominada desplazamiento. El factor de escala puede tener únicamente los valores 1, 2, 4 y 8. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.10.

Ecuación 7.10. Dirección efectiva y operando del modo índice escalado + desplazamiento

La característica más importante de este modo de direccionamiento es el producto entre el registro índice y la escala. Este último factor, en lugar de ser un número entero, tan sólo puede una de las cuatro primeras potencias de dos. La razón para esta restricción es que la operación de multiplicación de enteros es extremadamente costosa en tiempo como para que forme parte del cálculo de la dirección efectiva. En cambio, la multiplicación por estas potencias de dos es muy eficiente pues el resultado se obtiene mediante el desplazamiento del valor del registro. Si el factor de escala utilizado es 1, este modo de direccionamiento es idéntico al modo base + desplazamiento.

La sintaxis de este modo es ligeramente contra-intuitiva, pues se especifica el registro índice y el factor de escala separados por coma pero precedidos por una coma adicional, entre paréntesis y este a su vez precedido por el entero que codifica el desplazamiento. La figura 7.11 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo.

Figura 7.11. Acceso a operando con modo índice escalado + desplazamiento

Acceso a operando con modo índice escalado + desplazamiento

La dirección efectiva del segundo operando de la instrucción de la figura se obtiene multiplicando el contenido de %eax por el factor de escala 8 y sumando el desplazamiento. Al igual que en el caso del modo base + desplazamiento, este último elemento puede ser un número entero o una etiqueta previamente definida. Considérese el ejemplo de una tabla de enteros de 64 bits almacenados a partir de la etiqueta coeficientes. El ejemplo 7.7 muestra la definición y manipulación de estos datos con el modo índice escalado + desplazamiento.

Ejemplo 7.7. Acceso a una tabla de enteros de 64 bits con modo índice escalado + desplazamiento

        .data
coeficientes:
        .quad 21, 34, 56, 98
        .text
        .global main
main:
        ...
        mov $0, %eax
        mov coeficientes(, %eax, 8), %ebx
        inc %eax
        add coeficientes(, %eax, 8), %ebx
        inc %eax
        add coeficientes(, %eax, 8), %ebx
        inc %eax
        add coeficientes(, %eax, 8), %ebx
        ...

Los números definidos por la directiva .quad son de 64 bits y por tanto ocupan 8 bytes. Tras cargar el valor 0 en el registro %eax, las siguientes instrucciones acceden a los números de forma sucesiva utilizando este registro como índice. La multiplicación del registro índice por el factor de escala 8 que coincide con el tamaño de los datos hace que el índice coincida con el valor que se utilizaría en un lenguaje de alto nivel como Java para acceder a los números: coeficientes[3] se accede mediante la expresión coeficientes(, %eax, 8) si %eax contiene el valor 3.

7.2.10. Modo base + índice escalado + desplazamiento

Este modo de direccionamiento es el más complejo que ofrece el procesador y se puede considerar como la combinación de los modos base + desplazamiento e índice escalado + desplazamiento. La dirección efectiva se calcula sumando tres números: el desplazamiento, el contenido de un registro base y la multiplicación de un registro índice por un factor de escala que puede tener los valores 1, 2, 4 u 8. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.11.

Ecuación 7.11. Dirección efectiva y operando del modo base + índice escalado + desplazamiento

Este modo de direccionamiento precisa cuatro elementos que están contenidos en la codificación de la instrucción. El campo que más espacio requiere es el desplazamiento que puede ser un valor entero o el nombre de una etiqueta previamente definida, por lo que se precisan 32 bits. El factor de escala, al poder tomar únicamente cuatro valores, se puede codificar con 2 bits.

La sintaxis de este modo es también una combinación de los anteriores. Entre paréntesis se escribe el registro base, el registro índice y el factor de escala, por este orden y separados por comas. El paréntesis va precedido del valor del desplazamiento. La figura 7.12 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo.

Figura 7.12. Acceso a operando con modo base + índice escalado + desplazamiento

Acceso a operando con modo base + índice escalado + desplazamiento

El efecto de este modo de direccionamiento es que la dirección efectiva se obtiene sumando un registro, una constante (el desplazamiento) y un segundo registro susceptible de ser escalado. Al igual que en el modo índice escalado + desplazamiento, la multiplicación por la escala se realiza desplazando el operando cero, una, dos o tres posiciones hacia su bit de más peso. Al igual que en el resto de modos que utilizan un desplazamiento, este puede ser un número entero o una etiqueta previamente definida. En la instrucción DIVL 4(%ecx, %edi, 4) mostrada en la figura, la dirección efectiva del operando es 4 + %ecx + (%edi * 4), por lo que se asume que el registro %ecx contiene el valor de una dirección con respecto a la cual se accede al operando. En la instrucción INCB contador(%ecx, %edi, 4) el desplazamiento es una dirección de memoria en base de la cual se obtiene la dirección efectiva del operando.

Como ejemplo de utilización de este modo de direccionamiento considérese que se ha definido una tabla de 10 enteros en Java a partir de la posición de memoria con etiqueta num. A continuación se quiere acceder al elemento de la tabla con índice almacenado en el registro %ecx e incrementar su valor en una unidad. El código mostrado en el ejemplo 7.8 muestra las instrucciones necesarias para esta operación.

Ejemplo 7.8. Acceso a una tabla de enteros en Java con modo base + índice escalado + desplazamiento

        .data
num:    .int 10, -1, 32, 345, -3556, 4, 21, 23, 15, 6543, 23
        .text
        .global main
main:
        ...
        mov $4, %ebx
        incl num(%ebx, %ecx, 4)
        ...

Como Java almacena el tamaño de una tabla en los cuatro primeros bytes, la dirección del elemento con índice en %ecx se obtiene mediante la expresión num + 4 + (%ecx * 4) que se puede calcular de forma eficiente utilizando el modo base + índice escalado + desplazamiento. Si se quisiese incrementar el valor de todos los elementos de la tabla se puede escribir un bucle que incremente el valor del registro %ecx desde cero hasta el tamaño de la tabla menos uno e incremente cada uno de ellos con una instrucción idéntica a la del ejemplo.

7.2.11. Utilización de los modos de direccionamiento

Se han estudiado los modos de direccionamiento que ofrece el procesador como mecanismo eficiente de acceso a memoria. No todos los cálculos de la dirección efectiva de un operando pueden realizarse en una sola instrucción por medio de estos modos, tan sólo aquellos que requieran el tipo de operaciones que ofrece alguno de ellos. Los modos de direccionamiento son por tanto, un recurso que el procesador ofrece para ganar eficiencia en la ejecución de programas, pero que de ninguna forma limita la forma de obtener la dirección efectiva.

A la hora de programar en ensamblador y acceder a datos en memoria, la técnica para acceder a los datos es tener en cuenta qué operaciones se deben realizar para obtener su dirección, y si éstas pueden ser incluidas en una misma instrucción como modo de direccionamiento mejor que realizar estos cálculos con instrucciones máquina adicionales.

Supóngase que se define una matriz de enteros con m filas y n columnas a partir de la posición representada por la etiqueta matriz. Los valores m y n son enteros y están almacenados en memoria con etiquetas del mismo nombre. Las matrices se almacenan en memoria utilizando múltiples estrategias. Las dos más comunes son por filas y por columnas. En el primer formato, se almacenan los elementos de una fila en posiciones contiguas de memoria y a continuación los de la siguiente fila. En el segundo formato, los elementos de una columna ocupan posiciones de memoria consecutivas, y a continuación se almacena la siguiente columna. Supóngase que los elementos de esta matriz están almacenados por filas. La figura 7.13 muestra la distribución de los datos en memoria mediante su definición en ensamblador. Cada posición de la matriz contiene un número formado por el número de fila seguido del número de columna.

Figura 7.13. Definición de una matriz de enteros almacenada por filas

Definición de una matriz de enteros almacenada por filas

Para acceder a un elemento de la matriz se precisan dos índices (i, j), donde 0≤ i < m y 0≤ j < n. Dados los índices (i, j), la expresión de la dirección efectiva de este elemento según la definición de la figura 7.13 se muestra en la ecuación 7.12.

Ecuación 7.12. Dirección de un elemento en una matriz de enteros

Supóngase que se tiene que acceder al elemento en la posición que indican los registros %eax y %ebx para incrementar en una unidad su valor mediante la instrucción INC. Dada la funcionalidad ofrecida en los modos de direccionamiento, no es posible acceder al elemento con una única instrucción, pues el cálculo de su dirección efectiva requiere operaciones no contempladas.

Pero una porción de la ecuación 7.12 sí puede ser calculada por el modo de direccionamiento base + índice escalado + desplazamiento. Como desplazamiento se utiliza el valor de la etiqueta matriz, la segunda multiplicación se puede ejecutar como un índice escalado, por lo que tan sólo es preciso obtener el resultado de (i * n * 4) y almacenarlo en un registro. El ejemplo 7.9 muestra una posible secuencia de instrucciones para acceder e incrementar el elemento.

Ejemplo 7.9. Instrucciones para incrementar un elemento de una matriz de enteros

        ...
        mull n
        sal $2, %eax
        incl matriz(%eax, %ebx, 4)
        ...

La instrucción mull n multiplica el número de columnas por el índice que indica el número de fila. Tal y como funciona esta instrucción, al especificar un operando de 32 bits mediante el sufijo, resultado se almacena en el registro de 64 bits obtenido al concatenar %edx:%eax. Se asume que el resultado no sobrepasa los 32 bits de %eax. A continuación la instrucción sal $2, %eax desplaza el registro dos posiciones a su izquierda que equivale a multiplicar por cuatro. Con esto se obtiene en %eax el término de la ecuación 7.12 que falta para poder utilizar el modo base + índice escalado + desplazamiento tal y como muestra la instrucción incl matriz(%eax, %ebx, 4).

Como efectos colaterales de este cálculo se ha perdido el valor inicial del índice almacenado en %eax así como el valor del registro %edx ambos modificados por la instrucción de multiplicación. A modo de comparación, el ejemplo 7.10 muestra una secuencia alternativa de instrucciones para realizar la misma operación pero que únicamente utiliza el modo registro indirecto.

Ejemplo 7.10. Instrucciones para incrementar un elemento de una matriz de enteros utilizando el modo registro indirecto

        ...
        mull n
        sal $2, %eax
        sal $2, %ebx
        add %ebx, %eax
        add $matrix, %eax
        incl (%eax)
        ...

Las instrucciones adicionales realizan los cálculos equivalentes al modo base + índice escalado + desplazamiento solo que utilizando instrucciones máquina del procesador. El resultado de esta secuencia es casi idéntico al anterior (en este caso se ha perdido también el valor dado en el %ebx) pero se ha utilizado un número muy superior de instrucciones, con lo que su ejecución es mucho menos eficiente.

Este ejemplo pone de manifiesto cómo el acceso a los operandos puede realizarse de múltiples formas, pero para obtener una ejecución eficiente y código compacto debe seleccionarse aquella que haga uso de los modos de direccionamiento ofrecidos por el procesador.

7.3. Hardware para el cálculo de la dirección efectiva

Una vez estudiados los modos de direccionamiento que ofrece el procesador se puede intuir el tipo de circuito digital utilizado para calcular la dirección efectiva de un operando almacenado en memoria. La figura 7.14 muestra una posible implementación.

Figura 7.14. Circuito para el cálculo de la dirección efectiva

Circuito para el cálculo de la dirección efectiva

Este circuito calcula la dirección efectiva para los modos de direccionamiento cuyo operando se encuentra en memoria, es decir, todos excepto inmediato y registro. De los bits que codifican la instrucción se obtienen los cuatro posibles elementos el cálculo de la dirección efectiva y mediante la utilización de las señales de control b, i, e y d se activa la participación de cada una de ellos. Por ejemplo, el modo índice escalado + desplazamiento requiere que la señal i seleccione la entrada del multiplexor que procede del banco de registros, la señal d seleccione el valor obtenido de la instrucción, y las dos señales restantes seleccionen la entrada constante de sus respectivos multiplexores.

7.4. Resumen de los modos de direccionamiento

Los modos de direccionamiento son los diferentes procedimientos que utiliza el procesador dentro de la ejecución de una instrucción para acceder a sus operandos. Las diferentes formas que permite el procesador Intel Pentium para acceder a sus operandos se muestran en la tabla 7.1.

Tabla 7.1. Modos de direccionamiento del Intel Pentium

Modo de direccionamiento Dirección efectiva Operando Condiciones adicionales
Inmediato
Registro
Absoluto
Registro indirecto
Auto-incremento
Auto-decremento
Base + desplazamiento
Base + índice
Índice escalado + desplazamiento
Base + índice escalado + desplazamiento

7.5. Ejercicios

  1. Asumiendo que los campos de una instrucción máquina son ci1, ci2, ci3, ci4,... escribir la fórmula del cálculo de la dirección efectiva del operando y explicar su significado para los siguientes modos de direccionamiento: (Utilícese la notación (x) para denotar “el contenido de x”).

    1. Registro Indirecto:

    2. Absoluto:

    3. Base + Índice:

    4. Base + Índice Escalado + Desplazamiento:

  2. Supóngase que de todos los modos de direccionamiento del Pentium, los únicos que se pueden utilizar son el modo registro, modo inmediato y el modo registro indirecto. Escribir la secuencia de instrucciones equivalentes a las siguientes: (es decir que si se reemplaza la instrucción con las instrucciones de cada respuesta, el programa resultante es idéntico).

    1. MOV matrix(%ebx), %eax
    2. MOV table(, %ecx, 4), %eax
  3. Un procesador llamado PDP-11 contiene en su juego de instrucciones dos modos de direccionamiento que no posee el Intel Pentium.

    • Modo Autoincremento Indirecto: Se representa como [Reg]++. El procesador accede a la posición de memoria contenida en el registro Reg y de dicha posición de memoria obtiene la dirección de memoria del operando. El registro Reg queda incrementado en cuatro unidades.

    • Modo Indexado Indirecto: Se representa como $desp[Reg]. El procesador accede a la posición de memoria resultante de sumar Reg y $desp, y de dicha posición de memoria obtiene la dirección de memoria del operando.

    Especificar cómo deben traducirse las siguientes instrucciones del PDP-11 a instrucciones del Intel Pentium para que la ejecución sea equivalente.

    • MOV [%eax]++, %ebx

    • MOV %ecx, $desp[%ecx]

  4. Considerando el circuito de la figura 7.14, rellenar los valores de las señales b, i, e y d para cada uno de los modos de direccionamientos de la siguiente tabla. La entrada constante de los multiplexores se selecciona poniendo la señal de control con valor idéntico a esta.

    Modo de direccionamiento Valor de b Valor de i Valor de e Valor de d
    Absoluto
    Registro indirecto
    Base + desplazamiento
    Base + índice
    Índice escalado + desplazamiento
    Base + índice escalado + desplazamiento

Envío de errata