Documente Academic
Documente Profesional
Documente Cultură
Tabla de Smbolos y
Anlisis Semntico de
Java y GCC
200320831 Edwin Alexander Juan Vallejos
2004-12874Julio Enrique Vivas Castillo
Introduccin
La fase de anlisis semntico obtiene su nombre por requerir informacin relativa al
significado del lenguaje, que est fuera del alcance de la representatividad de las
gramticas libres de contexto y los principales algoritmos existentes de anlisis; es por
ello por lo que se dice que captura la parte de la fase de anlisis considerada fuera del
mbito de la sintaxis.
As, la mayora de los compiladores utilizan una gramtica libre de contexto para
describir la sintaxis del lenguaje y una fase de anlisis semntico posterior para
restringir las sentencias que semnticamente no pertenecen al lenguaje.
1.2 Objetivos de la Tabla de Smbolos (TS)
Las Tablas de Smbolos (en adelante TS) son estructuras de datos que
almacenan toda la informacin de los identificadores del lenguaje
fuente.
Las misiones principales de la TS en el proceso de traduccin son:
Colaborar con las comprobaciones semnticas.
Facilitar ayuda a la generacin de cdigo.
La informacin almacenada en la TS depende directamente del tipo
de elementos del
lenguaje especfico a procesar y de las
caractersticas de dicho lenguaje. Habitualmente los elementos del
lenguaje que requieren el uso de la TS son los distintos tipos de
identificadores del lenguaje (nombres de variables, de objetos, de
funciones, de etiquetas, de clases, de mtodos, etc.).
La informacin relativa a un elemento del lenguaje se almacena en
los denominados
atributos de dicho elemento. Estos atributos
tambin varan de un tipo de lenguaje a otro y de un elemento a otro.
As ejemplos de atributos tales como nombre, tipo, direccin relativa
en tiempo de ejecucin, dimensiones de los arrays, nmero y tipo de
los parmetros de procedimientos, funciones y mtodos, tipos de
acceso a los elementos de una clase (public, private, protected),
etc. se recogen y se guardan en la TS.
La jerarqua de smbolos
SymbolTable
+ insert() : void
+ search() : Symbol
+ set() :
void
+ reset()name
: void
Symbol
- name: string
- address: string
+has
+ getType() :
+ getName() : string
+ getAddress() : string
LocalVar
type
Field
At
+ getType() : Type
+ getName() : string
+ getAddress() : string
+ getType() :
+ getName() : string
+ getAddress()
: string
Parameter
+ getType() : Type
+ getName() : string
+ getAddress()
: string
+ getType(
+ getName
+ getAddre
Property
+ getType() :
+ getName() : string
+ getAddress() : string
Obsrvese que en la Figura 4-26 se ha hecho un esquema muy genrico de tabla de smbolos que prev que cada uno de los posibles sm
SymbolTable
+ insert() : void
+ search() : Symbol
+ set() :
void
+ reset()name
: void
GenericSymbol
- name: string
- address: string
+has
Type
type
+ getName() : string
+ getType() : Type
+ getAddress() : string
La jerarqua de tipos
En paralelo a la jerarqua de smbolos tenemos una jerarqua de tipos
donde pueden estar todos los tipos bsicos del lenguaje y los tipos
definidos por el usuario (con typedef, clases, etc.).
Un esquema bastante general se puede ver en la Figura 4-28.
En este caso se tiene una tabla de tipos (TypeTable) que tiene
guardados todos los tipos que aparecen durante la compilacin del
cdigo fuente.
Al comenzar dicha compilacin estarn nicamente los tipos bsicos
del sistema (los BuiltinType). En el momento en que el cdigo
comienza a describir clases, arrays, mtodos, interfaces, etc. Va
creando los nuevos tipos y metindolos en la tabla.
Hay que hacer notar que la clase BuiltinType representa a cada uno
de los tipos bsicos.
TypeTable
SymbolTable
+ insert() : void
+ search() : Type
name
name
Symbol
+has
Type
type
+field
+atribute
+implements
BuiltInTypeArrayTypeClassTypeInterfaeTypeFunctionTypeRecordType PointerType
+extends *
+extends *
+typename
name
+contains
*
+contains
name
name
position
+type
+contains+method
+contains
+method
Atributes
Atributes
- access: int
- static: boolean
- access: int
- static: boolean
+ getAccess() : int
+ getStatic() : boolean
+ getAccess() : int
+ getStatic() : boolean
+return value
+contains
1: int i;
2:
3:
4:
5:
6:
7:
8:
9:
float f;
main ()
{
int f;
read f;
i = f;
write i + 2;
}
Figura 4-48. rbol AST decorado resultante de la primera pasada del visitor
Macros
Estos han sido declarados como macros, ya sea por medio de la lnea
de comandos o por medio de #define. Unos pocos, tales como
__TIME__ son parte del compilador (built-in) e ingresados en la tabla
hash durante la inicializacin. El nodo hash para un macro normal
apunta a una estructura con ms informacin acerca del macro, tal
como, si es de tipo funcin, cuantos argumentos toma, y su
expansin. Los macros que son parte del compilador estn marcados
como especiales, y a cambio contienen una enumeracin indicando
de los macros built-in es.
Aserciones (asserts)
Las Aserciones o afirmaciones se encuentran en un espacio de
nombres separado al de los macros. Para asegurar esto, cpp de hecho
agrega un caracter # antes de hacer el hash y meterlo en la tabla
hash. Un nodo de asercin apunta a una cadena de respuestas a la
asercin.
Void
Cualquier otro identificador cae en esta categora un identificador
que no es un macro, o un macro que desde entonces ha sido
indefinido con #undef. Cuando se preprocesa C++, esta categora
tambin incluye operadores con nombre (named operadors) tal como
xor. En las expresiones estos se comportan como los operadores que
representan, pero en contextos donde la estructura del token es
importante se escriben de forma diferente. Esta distincin en la forma
de escribirlos es relevante cuando son operandos los operadores
macro # y ##. Los nodos hash para los operadores con nombre estn
marcados (flagged), ambos para poder distinguir la forma de escritura
(spelling) y para prevenir que stos sean definidos como macros.
08048410 T __libc_csu_fini
08048420 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
0804a014 A _edata
0804a01c A _end
080484ac T _fini
080484c8 R _fp_hw
08048298 T _init
08048310 T _start
0804a014 b completed.6625
0804a00c W data_start
0804a018 b dtor_idx.6627
080483a0 t frame_dummy
080483c4 T main
U printf@@GLIBC_2.0
puede ser reescrita con una variable t para eliminar una evaluacin
extra el trmino sin(u/2)
t = sin(u/2)
x = cos(v)*(1+t) + sin(w)*(1-t)
Funciones en-lnea
Otro tipo de optimizacin a nivel de cdigo es el llamado reemplazo
de funciones en-lnea, el cual incrementa la eficiencia de funciones
llamadas frecuentemente.
Ya sea que una funcin es usada, cierta cantidad de tiempo extra es
requerido para que el CPU pueda realizar la llamada: ste debe
almacenar los argumentos de la funcin en los registros apropiados y
localidades de memoria, saltar al inicio de la funcin, comenzar a
ejecutar el cdigo, en entonces retornar al punto original de ejecucin
cuando la llamada a la funcin est completa. Este trabajo adicional
se conoce como gasto de llamada a funcin (o, function-call
overhead). Este tipo de optimizacin elimina este gasto adicional al
reemplazar llamadas a auna funcin por el cdigo de la funcin en s
(tambin conocido como, colocar el cdigo en-lnea.)
En muchos casos, el costo de llamar a una funcin es una parte
considerable del tiempo total de ejecucin de un programa. Puede
volverse significativo solo cuando hay funciones que contienen un
nmero relativamente bajo de instrucciones, y estas funciones
cuentan por una fraccin sustancial del tiempo de ejecucin, en este
caso el costo se vuelve una gran proporcin del tiempo total de
ejecucin.
Poner en lnea siempre es favorable si hay un solo punto de
invocacin de una funcin. Tambin es incondicionalmente mejor si la
invocacin de una funcin requiere ms instrucciones (memoria) que
mover el cuerpo de la funcin en-lnea. Esta optimizacin tambin
pude facilitar optimizaciones ms precisas, tales como eliminacin de
subexpresiones comunes, por medio de la unin de varias funciones
separadas en una funcin ms grande.
La siguiente funcin sq(x) es un ejemplo tpico de una funcin que
podra beneficiarse de la optimizacin en-lnea. sta computa x^2, el
cuadrado de su argumento x:
double sq (double x)
{
return x * x;
}
Eliminando la llamada a la funcin y realizando la multiplicacin enlnea permite que el bucle se ejecute a la mxima eficiencia.
GCC selecciona funciones para optimizar por este mtodo usando un
nmero pruebas heursticas, tales como examinar que la funcin
candidata sea pequea. Como optimizacin que es, se lleva a cabo
solo dentro de cada archivo objeto. La palabra reservada inline puede
ser usada para pedir que explcitamente una funcin especfica sea
reemplazada por su contenido, incluyendo su uso en otros archivos
Scheduling
ste es el nivel ms bajo de optimizacin, en el cual el compilador
tenermina la mejor forma de ordenar instrucciones individuales. La
mayora de CPUs permiten que una o ms instrucciones nuevas
comiencen a ejecutarse antes que otras hayan finalizado. Muchos
CPUs tambin soportan pipelining, donde mltiples instrucciones se
ejecutan en paralelo en el mismo CPU.
Cuando esta optimizacin est habilitada, las instrucciones deben ser
ordenadas de tal manera que permita que los resultados estn
disponibles para ser utilizados por instrucciones posteriores en el
momento indicado, y para que permitan una ejecucin mxima en
paralelo. sta optimizacin mejora la velocidad de un ejecutable sin
incrementar su tamao, pero requiere de memoria adicional y ms
tiempo en el proceso de compilacin (debido a su complejidad).
Niveles de optimizacin
A modo de controlar el tiempo de compilacin y la utilizacin de
memoria por el compilador, y el costo-beneficio entre velocidad y
espacio para el ejecutable resultante, GCC provee un rango de niveles
de optimizacin general, numerados de 0 a 3, adems de las
opciones individuales para tipos especficos de optimizacin.
-O0 u omitir la opcin -O (default)
En este niel de optimizacin GCC no realiza ninguna optimizacin y
compila el cdigo fuente de la forma ms directa posible. Cada
comando en el cdigo fuente es convertido directamente a las
Ejemplos
El siguiente programa ser utilizado para demostrar los efectos de los
diferentes niveles de optimizacin:
#include <stdio.h>
double
powern (double d, unsigned n)
{
double x = 1.0;
unsigned j;
for (j = 1; j <= n; j++)
x *= d;
return x;
}
int
main (void)
{
double sum = 0.0;
unsigned i;
for (i = 1; i <= 100000000; i++)
{
sum += powern (i, i % 5);
}
printf ("sum = %g\n", sum);
return 0;
}
sys
0m0.000s
Fase 1: Preprocesado
En el cdigo fuente generalmente aparece una serie de directivas del
preprocesador (#define, #include, #ifdef, ) que necesitan ser
interpretadas pra que el cdigo pueda ser compilado.
Por ejemplo, el programa:
#define PI 3.141592
void main(void)
{
float area, radio;
radio = 3;
area = PI * (radio * radio);
printf("\n%f\n", area);
}
tras el preprocesado el programa estar compuesto de sentencias
similares a las del siguiente cdigo fuente:
# 1 "programa.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "programa.c"
void main(void)
{
float area, radio;
radio = 3;
area = 3.141592 * (radio * radio);
printf("\n%f\n", area);
}
El programa anterior fue obtenido utilizando el siguiente comando al
momento de compilar:
gcc -save-temps -v -o programa.o programa.c
y luego consultando el archivo programa.i, o bien, con el siguiente
comando:
gcc -v -E programa.c > programa.preprocesado
y luego leyendo el archivo programa.preprocesado.
Fase 2: Compilacin
En esta fase GCC transforma el cdigo C en cdigo ensamblador,
propio del procesador que utilizamos en nuestra mquina Linux (x86,
MIPS, Sparc, PPC, Alpha, ) Por ello es necesario que en caso de
compilar el GCC se le indique para que arquitectura generar cdigo.
Se puede generar cdigo ensamblador desde el cdigo C
preprocesado con la ayuda del programa cc1, o con un parmetro
especial del GCC:
cc1 programa.preprocesado
o bien,
gcc -S programa.preprocesado
Esto generar el cdigo ensamblador para nuestra mquina en el
archivo de salida, programa.s.
Fase 3: Ensamblado
En esta fase, el compilador transforma el cdigo ensamblador a
cdigo mquina binario que pueda entender nuestro procesador. Esto
se puede realizar con el comando as:
as -o programa.o programa.s
Lo cual generar la salida objeto programa.o
Fase 4: Enlazado
En nuestro cdigo fuente generalmente aparecen llamadas a
funciones como printf, fgets, fopen, etc. Estas funciones ya se
encuentran compiladas y ensambladas en las libreras que se pueden
encontrar en directorios como /lib, /usr/lib, /usr/local/lib. Lo que debe
hacerse es aadir dinmica o estticamente estos binarios a nuestro
cdigo ya compilado y ensamblado. Este proceso se denomina
Enlazado o linkado.
Hay dos tipos principales de enlazado:
Enlazado esttico: Los binarios de las libreras se aaden a
nuestros binarios compilados.
Enlazado dinmico: Esto no aade los binarios de las libreras a
nuestro binario, ms bien, har que nuestro programa cargue en
memoria la librera en el momento que la necesite. Esto permite que
nuestro programa no aumente mucho en tamao.
El enlazado lo hace el programa ld donde a nuestro binario se le
aade las libreras correspondientes a las funciones que llamamos, y
a una serie de rutinas de inicializacin y salida.
Por ejemplo, el siguiente comando enlaza nuestro programa con las
libreras requeridas:
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o programa.o -z
relro /usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/crt1.o
/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/crti.o /usr/lib/gcc/i486linux-gnu/4.3.2/crtbegin.o -L/usr/lib/gcc/i486-linux-gnu/4.3.2
-L/usr/lib/gcc/i486-linux-gnu/4.3.2 -L/usr/lib/gcc/i486-linuxgnu/4.3.2/../../../../lib -L/lib/../lib -L/usr/lib/../lib -L/usr/lib/gcc/i486-linuxgnu/4.3.2/../../.. programa.o -lgcc --as-needed -lgcc_s --no-as-needed
-lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i486-linuxgnu/4.3.2/crtend.o /usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/crtn.o
Aunque todo este proceso est englobado por el GCC, ya que GCC es
un front-end de los programas cpp, cc1, as y ld.
As todo el proceso anterior se puede simplificar en un solo comando:
gcc -o programa programa.c
Si se cuenta con varias libreras que se desean usar, se puede indicar
por medio de la directiva -l. Por ejemplo, para enlazar la librera con
funciones matemticas (libm.so) se indica con -lm.
Si se cuenta con mdulos, es decir, otros archivos fuente que
queremos aadir, primero debern ser compilador todos por separado
como sigue:
gcc -c codigo_principal.c
gcc -c modulo1.c
gcc -c modulo2.c