Está en la página 1de 11

Generacion de código objeto

Introduccion
La conversión del PF hacia un PO equivalente implica varios retos debido a las diferencias que
generalmente tienen ambos lenguajes. En el caso de un compilador habrán diferencias
significativas entre el LF, típicamente de alto nivel, y el LO, lenguaje de maquina.

Lenguaje Fuente Lenguaje Maquina


Orientado al ser humano Orientado al hardware
Instrucciones simples y compuestas Instrucciones simples
Estructura modular Estructura lineal

if ( a <= 2) { mov ax, 2


while ( b ¡= 5) { inc bx
a++; add ax, bx
} cmp ax, 3
else jg E1
b++;

La instrucción compuesta del LF es básicamente lo que llamamos anidamiento, es decir una


instruccion contenido en el modulo verdadero/falso/repetitivo de otra instrucción, la cual a su vez
también puede contener a otra instrucción, etc.

Este anidamiento produce estructuras modulares, las cuales resaltamos en el PF a través del uso
de espacios en blanco, para visualizar mejor las estructuras.

En el Lenguaje de máquina, en cambio, todas las instrucciones son simples, cada una de ellas
ocupa una sola línea y no comprende ni está comprendida dentro de otra instrucción.

Tambien hay que considerar la relatividad de lo que es simple, por ejm en el alto nivel una
instrucción simple e indivisible es x = a + b * c , pero en el código de maquina esa es una
instrucción compuesta de 3 operaciones básicas (multiplicación, suma, asignación)

Linealizacion
Es el proceso por el cual rompemos la estructura modular para obtener una estructura lineal en su
lugar. Para esto tenemos que obtener “equivalencias estructurales” para las instrucciones de
control, las cuales son las que generan la estructura modular.

Definimos entonces las operaciones básicas, no estructuradas, de control que nos permitirán
plantear dichas equivalencias.

Lenguajes y Compiladores / Jaime Pariona Quispe


Transferencia, goto

goto <etiqueta> // Efectua una bifurcación (transferencia del punto de ejecucion)


// hacia <etiqueta> , la cual representa una dirección de memoria
// en la que empieza una instrucción.
If de bajo nivel

If <condicion> goto <etiqueta> // Si <condición> es verdad entonces hace goto


// si es falso, entonces se ignora el goto y pasa a la
// siguiente linea
Ejm.

a=1;
b=3;
if ( b ¡= 1) goto E1
a++
goto E2
E1: b++
E2: cout << a << b // imprimirá 1, 4

Equivalencias Estructurales

1. Condicionales
if (cond) if ( ! cond ) goto E1
<MV> <MV>
else ---- > goto E2
<MF> E1: <MF>
E2:
2. Repetitivos

while (cond) E1: if (! cond ) goto E2


<MR> ---- > <MR>
goto E1
E2:

do E1: <MR>
<MR> ---- > if (cond) goto E1
while (cond)

for (exp1; exp2; exp3) exp1


<MR > ---- > E1: if (!exp2) goto E2
<MR>
exp3

Lenguajes y Compiladores / Jaime Pariona Quispe


goto E1
E2:

3. Alternativo
Caso x vx = valor(x)
valor1 : modulo1 if (vx == valor1) goto E1
valor2 : modulo2 if (vx == valor2) goto E2
… …. ------ > ……..
valor_n : modulo_n if (vx == valor_n) goto En
sino modulo_y goto Ez
FinCaso E1: modulo1
goto fin
E2: modulo2
goto fin
…………..
En: modulo_n
goto fin
Ez: modulo_y
fin:
Ejemplos:

1. Linealizar el PF
int a, b=1;
while ( b < 8 ) {
a = b +1;
if ( a%2 == 0)
a++;
b++;
}
2. Cual seria el equivalente estructural para una sentencia switch del lenguaje C

Codigo Intermedio
Generalmente la conversion a codigo objeto se hace a traves primero de la conversion hacia un
codigo intermedio, el cual es generalmente independiente de la plataforma de hardware

PF ---- > Codigo Intermedio ---- > Codigo Objeto

Esto se hace generalmente por las siguientes razones:

1. El codigo intermedio esta a medio camino entre la complejidad del alto nivel y la
simplicidad del bajo nivel, esto facilita la conversion.
2. El codigo intermedio puede ser simple y estructurado, esto facilita los procesos de
optimizacion posteriores

Lenguajes y Compiladores / Jaime Pariona Quispe


Tipos de codigo intermedio:

1. Notacion de Tercetos
2. Notacion Postfija
3. Codigo P

Codigo Intermedio: Tercetos


Los tercetos son una representacion basica y estructurada de operaciones elementales:
La sintaxis es: <operador>, <operando1>, <operando2>

Ej. x=a+b*c; ----- > (1) *, b, c


(2) +, a, (1)
(3) =, x, (2)

Ej. x = y = ( a + b) * ( c – d ); ---- > (1) +, a, b


(2) - , c, d
(3) *, (1), (2)
(4) =, y, (3)
(5) =, x, (4)
Ej. z = ++a – b++; ---- > (1) ++, a,
(2) -,a,b
(3) = , z , (2)
(4) ++, b,

Tercetos de control

condicionales
BP, <etiqueta>, // Bifurcar en positivo a <etiqueta>
BC, <etiqueta>, // Bifurcar en cero a <etiqueta>
BN, <etiqueta>, // Bifurcar en negativo a <etiqueta>

Incondicional
B, <etiqueta>, // Bifurca incondicionalmente a <etiqueta>

Ej. (1) =, a, 1
(2) = , b, 2
(3) -,a,b
(4) BP, 6,
(5) BN, 7,
(6) ++, a,
(7) ++, b,
(8) cout, a,

Lenguajes y Compiladores / Jaime Pariona Quispe


(9) cout, b,

Ej. Usando tercetos implementar un programa que ingrese 2 números e imprima el mayor o 0 si
ambos son iguales.

(1) cin, a,
(2) cin, b,
(3) - , a , b
(4) BP, (8)
(5) BN, (10)
(6) cout, 0,
(7) B, (11)
(8) cout, a,
(9) B, (11)
(10) cout , b
(11) nop , ,

Llamadas a funciones
Consideremos el siguiente programa y su secuencia de ejecución:

void f1() {
……
……
}
void f2() {
…..
f1()
……
}
main () {
…..
f2()
……
f1()
}

El paso del punto de ejecución en el punto 2 desde main hacia f2() se hace a través de una llamada
CALL. Cuando la función termina regresa a través de una instrucción RET.

Tanto CALL como RET son instrucciones que tienen sus correspondientes en el código maquina. Se
diferencian de un GOTO en que guardan y recuperan la dirección de retorno en una pila.

Lenguajes y Compiladores / Jaime Pariona Quispe


Internamente dichas instrucciones actúan de la siguiente forma:

CALL: push @retorno RET: @retorno = pop()


goto @funcion goto @retorno

Ejemplo:

void f1() {
int a; (1) B , 9 ,
a ++; (2) ++, a ,
} (3) RET, ,
void f2() { (4) =, c, 4
int b, c=4; (5) CALL, 2,
f1() (6) +, b, c
Tercetos
cout << b + c; (7) cout, (6),
------>
} (8) RET, ,
main () { (9) =, d, 5
int d=5; (10) CALL, 4,
f2() (11) ++, d,
d ++; (12) CALL, 2,
f1() (13) cout, d,
cout << d; (14) RET, ,
}

Descripcion:
• El terceto (1) bifurca hacia el inicio del main() pues es la primera función a ejecutar.
• Las funciones f1, f2 y main empiezan en los tercetos (2), (4) y (9) respectivametne.
• Toda función termina con la instrucción RET, incluso la función main.

Paso de parámetros
El potencial de las funciones es gracias al uso de los parámetros, los cuales les permiten adecuarse
a diversas variantes de procesamiento.

Los parámetros utilizados en la definición de la función se conocen como parámetros formales.


Los parámetros utilizados en la llamada a la función se conocen como parámetros actuales.
Ejem.
int f1( int a, int b) { // a y b son parámetros formales
}
main ( ) {
cout << f1( x, y); // x e y son parámetros actuales.
}

Lenguajes y Compiladores / Jaime Pariona Quispe


Entre los parámetros formales y los parámetros actuales se establece una asociación, la cual
puede ser de diversas formas, a esto se le conoce como tipos de llamada. Los dos principales tipos
de llamada son:

Llamada por valor.


El parámetro formal se inicializa con el valor del parámetro actual. Cualquier cambio en el
contenido del parámetro formal no se refleja en el parámetro actual.

Llamada por referencia.


El parámetro formal se crea en la dirección del parámetro actual. Esto implica que tanto el
parámetro formal como el parámetro actual son dos identificadores para la misma ubicación, por
lo tanto cualquier cambio en el parámetro formal se refleja en el contenido del parámetro actual.

Implementación de la llamada por valor. Se puede implementar a través de una pila de datos,
donde, antes de hacer el CALL, se empilan los valores de los parámetros actuales. Luego, cuando
la función se ejecute, lo primero que debe hacer es desempilar esos valores y asignárselos a los
parámetros formales.
En el caso del return la función devuelve el valor igualmente a través de la pila. El modulo que
llamo a la función debe inmediatamente sacar de la pila dicho valor de retorno cuando se retorne
de la función.

Ejem.
(1) B,8, se bifurca al inicio del main
int f1(int a, int b) { (2) pop, b , inicio de f1, desempila sus
int c; (3) pop, a, parametros.
c = a + b; (4) +, a, b
return c; Tercetos
(5) =, c, (4)
} ------>
(6) push, c, empila el valor de retorno
main () { (7) RET, , fin de f1, retorna
int d, e; (8) cin, d, inicio de main
cin >> d >> e; (9) cin, e,
cout << f1(d, e); (10) push, d, empila parametros actuales
} (11) push, e,
(12) CALL, 2, llamada a f1
(13) pop, , desempila valor de retorno
(14) cout, (13),
(15) RET

Lenguajes y Compiladores / Jaime Pariona Quispe


Otro ejemplo:

(1) B, 14 , saltar al inicio de main()


(2) pop, a , inicio de f1
int f1(int a) {
(3) *, 2, a
return 2 * a;
(4) push, (3), f1 devuelve valor
}
(5) RET, ,
int f2( int b, int c) {
(6) pop, c, inicio de f2
return f1 (b + c); tercetos
(7) pop, b,
} ------>
(8) +, b, c se calcula parametro actual
main () {
(9) push, (8), se empila
int d, e;
(10) CALL, 2, se llama a f1
cin >> d >> e;
(11) pop, , se extrae lo devuelto por f1
cout << f1(d) +f2(d-e);
(12) push, (11), y se empila para el retorno
}
(13) RET, ,
(14) cin, d, inicio de main()
(15) cin, e,
(16) push, d, se empila parametro actual
(17) CALL, 2, y se llama a f1
(18) pop, ,
(19) - , d, e se calcula el otro param. actual
(20) push, (18), se empila
(21) CALL, 6, y se llama a f2
(22) pop, ,
(23) + , (18), (22) se suma lo devuelto por f1 y f2
(24) cout, (23),
(25) RET

Implementacion de la llamada por referencia. Se implementa pasando por valor el valor de la


referencia, es decir la dirección del parámetro actual. Luego el parámetro formal se crea en esa
misma dirección de tal forma que cualquier cambio en el contenido del parámetro formal se
reflejara también en el parámetro actual.

Funciones con parámetros opcionales. En este caso podemos usar otra primitiva de la pila para
verificar si hay mas parámetros en la pila. Específicamente podemos usar empty() el cual nos
devuelve TRUE (valor +1) o FALSE (valor 0).

Ejem.

Lenguajes y Compiladores / Jaime Pariona Quispe


(1) B, 13 , saltar al inicio de main
int f1(int a, int b=3) { (2) pop, , inicio de f1, desempilar
return a + b; (3) empty, , ver si hay mas parametros
} (4) BP, 8, si no hay otro ir a 8
main () { Tercetos
(5) =, b, (2) asignar el primer parametro
int d, e; ------>
(6) pop, a , sacar 2do parametro hacia a
cin >> d >> e; (7) B, 10,
cout << f1(d + e); (8) =, b, 3 asignar valor por default
cout << f1(d, e); (9) =, a, (2) asignar param. desempilado
} (10) +, a, b
(11) push, (10),
(12) RET, ,
(13) cin, d,
(14) cin, e,
(15) +, d, e
(16) push, (15),
(17) CALL, 2, se llama con un parametro
(18) pop, ,
(19) cout, (18),
(20) push, d,
(21) push, e, se llama con 2 parametros
(22) CALL, 2
(23) pop, ,
(24) cout, (23),
(25) RET, ,

Codigo Intermedio: Notacion Postfija (Polaca)


Notacion para la representación de expresiones, donde el operador esta ubicado entre los
operandos.

a + b --- > infija


a b + -- > postfija

Ejm.
x= a + b*c;
xa bc*+=

Ejm.
x = (a + b) * c;
xab+c*=

Lenguajes y Compiladores / Jaime Pariona Quispe


Ejerc.
x = y = (a + b*c) / (c - d)

Caracteristicas de la Notacion Postfija


1. El operador sigue a los operandos
2. No es necesario el uso de paréntesis
3. La evaluación es de Izquierda a Derecha usando una pila

Algoritmo:
1. Si es opn == > push (opn)
2. Si es opr == > op2 = pop()
op1 = pop()
push (op1 opr op2)
Ejm.
xab+c*=

Observaciones:
1. Recordar que toda expresión tiene valor.
2. En el caso de operadores unarios la expresión tiene que evaluarse acorde al tipo de
operador.
Ejm. x = -a + b * c;
Postfijo: x a – b c * + = ojo .. no es: x = b*c – a;
3. En la expresión postfija los operandos tienen la misma secuencia que en la expresión infija

Obs:
-a + b <> b – a
-a <> 0 – a

Ejm:
x = a ^b ^3; postfijo: x a b 3 ^ ^ = xab^3^=
CORRECTO INCORRECTO
y = a + b + c; postfijo: y a b + c += yabc++=
CORRECTO INCORRECTO
Ojo:
Los operadores tienen:
1. Representacion
2. Nro y tipo de operandos
3. Prioridad
4. Asociatividad

Lenguajes y Compiladores / Jaime Pariona Quispe


Objetivo:
OPCION 1 OPCION 2
x = a + b * c; ep = “x a b c * + =”
(1) *, b, c resultado = rutina_evaluar (ep)
(2) +, a , (1)
(3) =, x, (2)
Usamos criterio humano

Conversion de Infijo a Postfijo

Algoritmo:
1. Precondicion: Empilar INICIO, el cual tiene la mas baja prioridad
2. Llamar al scanner
3. Si es OPN == > va a la SALIDA
4. Si es OPR ==>
Si prioridad (OPR) > prioridad ( CabezaPila() ) == > empilar (OPR)
Si prioridad (OPR) < prioridad ( CabezaPila() ) == > desempilar todos los de prioridad >
y enviar a la SALIDA , empilar(OPR)
Si prioridad (OPR) = prioridad ( CabezaPila() ) == > ver asociatividad
Si ASOC_IZQ => desempilar y enviar a la SALIDA, luego empilar(OPR)
Si ASOC_DER => empilar(OPR)

5. Si es ‘(’ == > empilar INICIO


6. Si es ‘)’ ==> desempilamos hasta la marca de INICIO mas próxima.
7. Al llegar a $ se desempila y envía a la SALIDA lo que aun quede en la pila.

Observacion:
La expresión infija debe ser CORRECTA, lo cual implica que previamente le hemos realizado un
ANALISIS SINTACTICO.

Observacion:
En una expresión podemos tener diversos tipos de valor:
x = a + b * c + f1(4) – 5;
valor almacenado + valor calculado + valor procesado - valor inmediato

--- EOF ---

Lenguajes y Compiladores / Jaime Pariona Quispe

También podría gustarte