Escolar Documentos
Profissional Documentos
Cultura Documentos
Un compilador debe comprobar si el programa fuente sigue tanto las convenciones sintácticas
como las semánticas del lenguaje fuente. Esta comprobación, llamada comprobación estática
(para distinguirla de la comprobación dinámica que se realiza durante la ejecución del programa
objeto), garantiza la detección y comunicación de algunas clases de errores de programación. Los
ejemplos de comprobación estática incluyen:
Comprobaciones del flujo del control. Las proposiciones que hacen que el flujo del control
abandone una construcción deben tener algún lugar a dónde transferir el flujo de control. Por
ejemplo, una proposición break en C hace que el control abandone la proposición que la engloba,
while, for o switch más cercana; si dicha proposición englobadora no existe, ocurre un error.
Comprobaciones de unicidad. Hay situaciones en que se debe definir un objeto una vez
exactamente. Por ejemplo, en Pascal, un identificador debe declararse de forma única, las
etiquetas en una proposición case deben ser diferentes y no se pueden repetir los elementos en
un tipo escalar.
Comprobaciones relacionadas con nombres. En ocasiones, el mismo nombre debe aparecer dos o
más veces. Por ejemplo, en Ada, un lazo o bloque puede tener un nombre que aparezca al
principio y al final de la construcción. El compilador debe comprobar que se utilice el mismo
nombre en ambos sitios.
Un comprobador de tipos se asegura de que el tipo de una construcción coincida con el previsto
en su contexto. Por ejemplo, el operador aritmético predefinido mod en Pascal exige operandos
de tipo entero, de modo que un comprobador de tipos debe asegurarse de que Los operandos de
mod tengan tipo entero. De igual manera, el comprobador de tipos debe asegurarse de que la
desreferenciación se aplique sólo a un apuntador, de que la indización se haga sólo sobre una
matriz, de que una función definida por el usuario se aplique al número y tipo correctos de
argumentos, etcétera.
Figura 4.15
Puede necesitarse la información sobre los tipos reunida por un comprobador de tipos cuando se
genera el código. Por ejemplo, los operadores aritméticos como + normalmente se aplican tanto a
enteros como a reales, tal vez a otros tipos, y se debe examinar el contexto de + para determinar
el sentido que se pretende dar. Se dice que un símbolo que puede representar diferentes
operaciones en diferentes contextos está "sobrecargado". La sobrecarga puede ir acompañada de
coacción de tipos, donde un compilador proporciona un operador para convertir un operando en
el tipo esperado por el contexto. Una noción diferente de la de sobrecarga es la de
"polimorfismo". El cuerpo de una función polimórfica puede ejecutarse con argumentos de varios
tipos.
SISTEMAS DE TIPOS
"Si ambos operandos de los operadores aritméticos de suma, sustracción y multiplicación son de
tipo entero, entonces el resultado es de tipo entero."
"El resultado del operador unario & es un apuntador hacia el objeto al que se refiere el operando.
Si el tipo del operando es '...', el tipo del resultado es 'apuntador a .....´.”.
En los anteriores extractos se encuentra implícita la idea de que cada expresión tiene asociado un
tipo. Además, los tipos tienen estructura; el tipo "apuntador a ... " se construye a partir del tipo al
que se refiere " ... ".
En Pascal y en C, los tipos son básicos o construidos. Los tipos básicos son los tipos atómicos sin
estructura interna por lo que concierne al programador. En Pascal, los tipos básicos son boolean,
character, integer y real. Los tipos de subrango, como 1 .. 10, y los tipos enumerados, como
se pueden considerar como tipos básicos. Pascal admite que un programador construya tipos a
partir de tipos básicos y otros tipos construidos, como matrices (array), registros (record) y
conjuntos (set). Además, los apuntadores y las funciones también pueden considerar~ como tipos
construidos.
Verificación de tipos
Expresiones de tipos
Tipos simples: Son expresiones de tipos los tipos simples del lenguaje, y algunos tipos especiales:
Integer
Real
Char
Boolean
Void
Error
Los cuatro primeros son los tipos de datos simples más comunes en los lenguajes de
programación, los dos últimos son tipos simples especiales que usaremos para su atribución a
diversas partes de un programa, a fin de homogeneizar el tratamiento de todo el conjunto
mediante el método de las expresiones de tipos.
Tomando el lenguaje C como ejemplo, el segmento de código al que está asociada la expresión de
tipos integer es aquella en que aparece la palabra reservada int, etc.
Constructores de tipos: Permiten formar tipos complejos a partir de otros más simples. La
semántica de cada lenguaje tiene asociada unos constructores de tipos propios. En general, en los
lenguajes de programación se definen los siguientes constructores:
Matrices: Si T es una expresión de tipos, entonces array(R,T)es también una expresión de tipos que
representa a una matriz de rango R de elementos de tipo T.
Registros: Sea un registro formado por los campos u1, u2 ... uN, siendo cada uno de ellos de los
tipos T1,T2 ... TN, entonces, la expresión de tipos asociada al conjunto es: record ( (u1:T1) x (u2:T2)
x ... x (uN:TN) )
Punteros: Si T es una expresión de tipos, entonces pointer(T) es una expresión de tipos que
representa a un puntero a una posición de memoria ocupada por un dato de tipo T.
Funciones: Sean T1,T2 ... TN, las expresiones de tipos asociadas a los segmentos de código
correspondientes a los argumentos de una función, y sea T el tipo devuelto por la función.
Entonces, la expresión de tipos asociada a la función es: ((T1xT2 x... xTN) -> T )
Las expresiones de tipo pueden contener variables cuyos valores son expresiones de tipos.
Conversiones de tipos
La definición del lenguaje especifica las conversiones necesarias. Cuando un entero se asigna a un
real, o viceversa, la conversión es al tipo del lado izquierdo de la asignación. En expresiones, la
transformación más común es la de convertir el entero en un número real y después realizar una
operación real con el par de operandos reales obtenidos. Se puede utilizar el comprobador de
tipos en un compilador para insertar estas operaciones de conversión en la representación
intermedia del programa fuente.
Coerciones
La definición del lenguaje especifica las conversiones necesarias. Cuando un entero se asigna a un
real, o viceversa, la conversión es al tipo del lado izquierdo de la asignación. En expresiones, la
transformación más común es la de convertir el entero en un número real y después realizar una
operación real con el par de operandos reales obtenidos. Se puede utilizar el comprobador de
tipos en un compilador para insertar estas operaciones de conversión en la representación
intermedia del programa fuente.
Los operadores aritméticos están sobrecargados en la mayoría de los lenguajes. Sin embargo, la
sobrecarga que afecta a los operadores aritméticos como + se puede resolver observando
únicamente los argumentos del operador.
Funciones polimórficas
El término “polimórfico” se aplica a cualquier parte de código que pueda ejecutarse con
argumentos de tipos distintos, de modo que se puede hablar de funciones, así como de
operadores polimórficos.
Los operadores predefinidos para indicar matrices, aplicar funciones y manipular apuntadores son
generalmente polimórficos porque no se limitan a una determinada clase de matriz, función o
apuntador.
Expresiones y operadores
Expresiones.
a + 5*b
(a >= 0) and ((b+5) > 10)
a
-a * 2 + b
-b + sqrt(b**2 - 4*a*c)
length(s) > 0
Operadores.
Operadores lógicos.
A B (A and B) (A or B) (A xor B)
True True True True False
True False False True True
False True False True True
False False False False False
Para los tipos booleanos, existen versiones de los operadores "and" y "or",
llamadas "and then" y "or else" que tienen el mismo significado, pero realizan una
"evaluación en cortocircuito" consistente en que evalúan siempre primero el
operando izquierdo y, si el valor de éste es suficiente para determinar el resultado,
no evalúan el operando derecho.
La evaluación en cortocircuito resulta muy útil si la correcta evaluación del operando derecho
depende del valor del operando izquierdo, como en el siguiente ejemplo:
if i <= Vec'last and then Vec(i) > 0 then ...
(Evaluar Vec(i) produciría un error si i tiene un valor superior al límite máximo del vector).
Operadores relacionales.
Los operadores de igualdad están predefinidos para todos los tipos no limitados.
Sea T un tipo con estas características:
Los operadores de ordenación están predefinidos para todos los tipos escalares y los
arrays de elementos discretos. Sea T un tipo con estas características:
Existe también un operador de pertenencia ("in", "not in") que determina si un valor
pertenece a un rango o a un subtipo: if i in 1..10 then ...
if A = B then ...
C := (A <= B);
return (A >= B);
C := A + B;
return (A - B);
Los operadores unarios de adición predefinidos para cualquier tipo númerico, T, son
la identidad y la negación:
B := -A;
Operadores multiplicativos.
C := A * B;
return (A / B);
Los operadores módulo y resto están definidos para cualquier tipo entero, T:
El operador módulo se define de manera que (A mod B) tiene el mismo signo que
B, un valor absoluto menor, y existe un número entero, N, tal que: A = B*N +
(A mod B).
C := A rem B;
return (A mod B);
Los operadores de máxima prioridad son: el operador de cálculo del valor absoluto,
definido para cualquier tipo numérico, T1, el operador de negación lógica, definido
para cualquier tipo booleano, modular o array monodimensional de componentes
booleanos, T 2, y el operador de exponenciación, definido para cualquier tipo
entero, T3, o para cualquier tipo real en coma flotante, T4. Cada uno, de acuerdo
con las siguientes especificaciones:
function "abs"(Right : T) return T
function "not"(Right : T) return T
function "**"(Left : T; Right : Natural) return T
function "**"(Left : T; Right : Integer'Base) return T
A := abs(B);
C := not D;
return (E ** 3); -- E elevado al cubo
Sobrecarga de operadores.
Ada permite que el programador sobrecargue los operadores del lenguaje, esto es,
que pueda redefinirlos dándoles nuevos significados. Para sobrecargar un operador,
simplemente hay que definir una función cuyo nombre sea el operador entre
comillas y que tenga los parámetros adecuados. Por ejemplo, dado el siguiente tipo:
podemos sobrecargar el operador de suma ("+") para utilizarlo con el fin de sumar
números complejos:
Una vez definida esta función, se puede aplicar el operador entre variables del tipo
definido en los parámetros, devolviendo un valor del tipo definido como resultado.
...
S, X, Y: Complejo;
...
S := X + Y;
Sólo se pueden redefinir los operadores del lenguaje (no se pueden inventar
operadores) y manteniendo siempre su cardinalidad (los binarios se redefinirán con
funciones de dos parámetros y los unarios con funciones de un parámetro).
El operador de desigualdad ("/=") devuelve siempre el complementario de la igualdad ("="),
por lo que, en caso necesario, sólo hay que sobrecargar este último. De hecho, el operador de
desigualdad sólo se puede sobrecargar si se le atribuye un resultado que no sea de tipo
Boolean.