4.4 Tipos de Datos y Verificación de Tipos

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:

  1. Comprobaciones de tipos. Un compilador debe informar de un error si se aplica un operador a un operando incompatible; por ejemplo, si se suman una variable tipo matriz y una variable de función.
  2.  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.
  3. 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.
  4. 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

El diseño de un comprobador de tipos para un lenguaje se basa en información acerca de las construcciones sintácticas del lenguaje, la noción de tipos y las reglas para asignar tipos a las construcciones de lenguaje. Los siguientes extractos del informe de Pascal y del manual de referencia de C, respectivamente, son ejemplos de la información con la que el diseñador de un compilador podría verse obligado a comenzar.

  • "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

(violeta, índigo, azul, verde, amarillo, naranja, rojo)

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

Un componente importante del análisis semántico es la verificación de tipos. aqui , el compilador verifica si cada operador tiene operandos permitidos por la especificación del lenguaje fuente. Por ejemplo, las definiciones de muchos lenguajes de programación requieren que el compilador indique un error cada vez que se use un número real como indice de una matriz. Sin embargo, la especificación del lenguaje puede permitir ciertas coerciones a los operandos, por ejemplo, cuando un operador aritmético binario se aplica a un número entero y a un número real. En este caso, el compilador puede necesitar convertir el número entero a real.

Expresiones de tipos

El tipo de una construcción de un lenguaje se denotará mediante una “expresión de tipo”. De manera informal, una expresión de tipo es, o bien un tipo básico o se forma aplicando un operador llamado constructor de tipos a otras expresiones de tipos. Los conjuntos de tipos y constructores básicos dependen del lenguaje que deba comprobarse. Cada lenguaje de programación requerirá unas expresiones de tipos adecuadas a sus características. A continuación, a modo de ejemplo, se definen las expresiones de tipos más comunes:

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.
  • Productos: Sea T1 y T2 expresiones de tipos, T1 x T2 es una expresión de tipos que representa al producto cartesiano de los tipos T1 y T2. A fin de simplificar consideraremos que el constructor u operador de tipos x es asociativo por la izquierda. 
  • 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, SOBRECARGA DE FUNCIONES Y OPERADORES, FUNCIONES POLIMÓRFICAS

Conversiones de tipos

Considérense expresiones como x + i donde x es de tipo real e i es de tipo entero. Como representación de enteros y reales es distinta dentro de un computador, y se utilizan instrucciones de máquina distintas para las operaciones sobre enteros y reales, puede que el compilador tenga que convertir primero uno de los operandos de + para garantizar que ambos operandos sean del mismo tipo cuando tenga lugar la suma.

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.

Sobrecarga de funciones y operadores

Un símbolo sobrecargado es el que tiene distintos significados dependiendo de su contexto. La sobrecarga se resuelve cuando se determina un significado único para un caso de un símbolo sobrecargado. La resolución de la sobrecarga a menudo aparece referida comoidentificación de operadores porque determina la operación que denota un símbolo de operador.  

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.