adm.html
Tabla de contenidos
- 7.1. Notación
- 7.2. Modos del direccionamiento de la arquitectura IA-32
-
- 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
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.
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
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 de la
arquitectura IA-32 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.
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.
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 inst
c1, ..., inst
cn. 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 inst
ci 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.
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.
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.
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.
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.
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 inst
c1 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.
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.
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.
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.
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.
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.
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.
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.
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
.
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
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
.
Al tratarse de la única instrucción de la arquitectura IA-32 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.
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
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
.
Al igual que en el caso del modo auto-incremento, al ser PUSH
la única instrucción que utiliza este modo en
la arquitectura IA-32, 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
.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 la arquitectura IA-32 para acceder a sus operandos se muestran en la tabla 7.1.
Tabla 7.1. Modos de direccionamiento de la arquitectura IA-32
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 | ![]() |
![]() |
![]() |