Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Tipos de datos
Cualquier programa, no importa qué lenguaje use, se puede considerar como la especificación
de un conjunto de operaciones que se van a aplicar a ciertos datos en un orden determinado.
Existen diferencias básicas entre los lenguajes en cuanto a los tipos de datos permisibles, los
tipos de operaciones disponibles y los mecanismos que se suministran para controlar el orden
en el que las operaciones se aplican a los datos. Estas tres áreas, datos, operaciones y control,
forman la base de los cínco capítulos siguientes y también conforman el marco para gran
parte del análisis y comparación de lenguajes en la parte 11. Este capítulo considera los datos,
tipos y operaciones que se incorporan comúnmente en los lenguajes, en tanto que en el capítulo
5 se amplía ese concepto a las extensiones definidas por el programador a estos tipos básicos
de datos, un concepto que llamamos czbs/rcíccz.ó#. En el capítulo 6 se examinan las operaciones
individuales y el ordenamiento secuencial que controlan la ejecución de cada componente.
En el capítulo 7 se estudia cómo se organizan las abstracciones en torno a componentes de
programas que se conocen ordinariamente como swbprogrcJwczs o procec7Í.mJ.e#/os y, por último,
en e] capítulo s se presentan los conceptos de ¢ere#c/.c} y mé/ocÍos para permitir al programador
ampliar las capacidades del lenguaje de programación con técnicas or;.c#/czc7czs cz o¿y.e/os.
En primer término se investigan las propiedades que definen objetos de datos en un lenguaje
de programación. Después se analizan tipos representativos en casi todos los lenguajes de
programación en términos de características disponibles a partir del hardware de la compu-
tadora real (es decir, tipos elementales de datos) y características que por lo general se simulan
con softwareh(es decir, tipos de datos estructurados).
107
108 Tiposdeddtos Cdp. 4
AE ioooi A 0000000000010001
(a) Objeto de datos: Una localidad (b) Valor del dato: Un (c) Variable enlazada: Objeto
en la memoria de la computadora con patrón de bits usado por el de datos enlazado al valor de
el nombre A. traductor siempre que se dato 17.
usa en el programa el
número 17.
o¿y.e/o c7e c7oÍos para referirse a un agrupamiento en tiempo de ejecución de uno o más datos
en una computadora virtual. Durante la ejecución de un programa, existen muchos objetos de
datos de distintos tipos. Más aún, en contraste con la organización relativamente estática de
las áreas de almacenamiento subyacentes de una computadora real, estos objetos de datos y
sus interrelaciones cambian de manera dinámica durante la ejecución del programa.
Algunos de los objetos de datos que existen durante la ejecución de programas son c7e##i.c7os
por e/ programc}dor; es decir, son variables, constantes, arreglos, archivos, etc., que el pro-
gramador crea y manipula de manera explícita a través de declaraciones y enunciados en el
programa. Otros objetos de datos son c7e##z.cJos por e/ s;.s/emc}; es decir, son objetos de datos
que la computadora virtual construye para ``mantenimiento" durante la ejecución de programas
y a los cuales el programador no tiene acceso directo, como pilas de almacenamiento en tiem-
po de ejecución, registros de activación de subprogramas, memorias intermedias de archivos
y listas de espacio libre. Los objetos de datós definidos por el sistema se generan ordinariamente
de manera automática según se requieren durante la ejecución de programas, sin especificación
explícita por parte del programador.
Un objeto de datos representa un recj.pj.en# pcwc} vcr/ores c7e c7czÍos, un lugar donde los
valores de datos se pueden guardar y más tarde recuperar. Un objeto de datos se caracteriza
por un conjunto de oírj.bw/os, el más importante de los cuales es su Íj.po c7e c7ofos. Los atributos
determinan el número y tipo de valores que el objeto de datos puede contener y también pres-
criben la organización lógica de esos valores.
Un vcz/or c7e c7cr/os puede ser un sólo número, carácter o, posiblemente, un apuntador a
otro objeto de datos. Un valor de datos se representa ordinariamente por medio de un patrón
particular de bits en el almacenamiento de la computadora. Por ahora, consideramos que dos
valores son iguales si los patrones de bits que los representan en el almacenamiento son idén-
ticos; sin embargo, esta sencilla definición es insuficiente para datos más complejos. Es fácil
confundir objetos de datos con valores de datos, y en muchos lenguajes la distinción no se
hace con claridad. La distinción se aprecia tal vez con más facilidad advirtiendo las diferencias
de implementación: un objeto de datos se representa comúnmente como almacenamiento en
la memoria de la computadora. Un valor de datos está representado por un patrón de bits.
Decir que un objeto de datos Á contiene el valor 8 significa que se hace que el bloque de al-
macenamiento que representa a Á contenga el patrón particular de bits que representa a 8,
como se muestra en la figura 4.1.
Si se observa el curso de la ejecución de un programa, ciertos objetos de datos existen al
principio de la ejecución y otros se crean dinámicamente durante la misma. Algunos objetos
de datos se destruyen durante la ejecución; otros persisten hasta que el programa concluye.
Sec. 4.1. Propiedóc]es de tipos y objetos 109
Así pues, cada objeto tiene un /z.empo cíe vi.c7a durante el cual puede usarse para guardar
valores de datos. Un objeto de datos es e/eme#/c}/ si contiene un valor de datos que siempre se
manipula como una unidad. Se trata de una esfrztc/cm c7e c7c}/os si es un agregado de otros
objetos de datos.
Un objeto de datos participa en varios e#/czces durante su tiempo de vida. Mientras que los
atributos de un objeto de datos no varían durante su tiempo de vida, los enlaces pueden
cambiar de manera dinámica. Los atributos y enlaces más importantes son:
1. rí.po. Esta asociación se hace por lo común en el tiempo de traducción del programa
asociando el objeto de datos con el conjunto de valores de datos que el objeto puede
tomar.
2. £occ7/j.c7czcJ. El enlace con una localidad de almacenamiento en la memoria donde el
objeto de datos está representado no es ordinariamente modificable por el programador,
sino que es establecido y puede ser modificado por las rutinas de gestión de almacena-
miento de la computadora virtual, como se expone en la sección 5.4.
3. ycz/oJ'. Este enlace es por lo general resultado de una operación de asignación.
4. Nomóre. El enlace a uno o más nombres por medio de los cuales se puede hacer refe-
rencia al objeto durante la ejecución del programa se establece por lo común mediante
declaraciones y se modifica a través de llamadas y devoluciones de subprogramas, co-
mo se expone en el capítulo 7.
Variables y constantes
constante 30; tiene tanto sentido como escribir el enunciado de asignación 30 = 4.) A veces,
el compilador puede usar información acerca de valores de constantes para evitar generar
código para un enunciado o expresión. Por ejemplo, en el enunciado if:
el traductor ya tiene los valores de datos para las constantes A4Á.Y y 2, puede computar que es
falso que 30 sea menor que 2 y, por tanto, puede pasar completamente por alto cualquier
código para el enunciado if.
Persistencia. En la actualidad, casi todos los programas se desarrollan todavía usando el mo-
delo de "procesamiento por lotes" (sección 1.4.1). Es decir, el programador supone la serie
de sucesos siguiente:
Sec. 4.1. Propieclddes de tipos y objetos mu
N = 27;
N = N + MAX;
N es una variable simple; MAX, 27 y 30 son constantes. N, MAX, "27" y "30" son nombres
para objetos de datos de tipo enfero. La declaración de constante especifica que el objeto de
datos llamado MAX deberá enlazarse permanentemente (por el tiempo que dure la ejecución
del subprograma)` al valor 30. La constante MAX es una consíanfe defí.n/.da por e/ progra-
mac/or porque el programador define en forma explícita el nombre para el valor 30. El nombre
"27", por otra parte, es una /Í.Íera/ que nombra el objeto de datos que contiene el valor 27.
Estas literales se definen como parte de la definición misma del lenguaje. La distinción
importante, aunque confusa, es aquí entre el va/or27, que es un entero representado como
una serie de bits en almacenamiento durante la ejecución del programa, y el mombre "27",
que es una secuencia de dos caracteres "2" y "7" que representa el mismo número en forma
decimal, según está escrito en un programa. C tiene tanto declaraciones de constante, así en
este ejemplo, como definiciones de macro al igual que #define MAX 30, Ia cual es una
operación en tiempo de compilación que causa que todas las referencias a MAX en el programa
se cambien a la constante 30.
Adviértase que, en este ejemplo, la constante 30 tiene dos nombres, el nombre "MAX" definido
por el programador y el nombre de literal "30", y ambos se pueden usar para hacer referencia
a un obj.eto de datos que contenga un valor 30 en el programa.
#define MAX 30
es un mandato que el traductor usa para equiparar MAX con el valor 30, en tanto que el
atributo cons/ en C es una directiva de traductor que afirma que la variable MAX siempre
contendrá el valor 30.
El tiempo de vida de las variables del programa está determinado por el tiempo de ejecución
del programa; sin embargo, el tiempo de vida de los datos suele extenderse más allá de esa
única ejecución. Se dice que los datos son pers;.s/e#/es y continúan existiendo entre ejecuciones
del programa.
Muchas aplicaciones actuales no encajan fácilmente en este modelo. Considérese un sistema
de reservaciones de línea aérea. Para reservar un asiento se llama al agente de viajes, el cual
interroga al sistema de reservaciones e invoca programas que verifican horarios, precio y
destinos. Los datos y los programas coexisten esencialmente de manera indefinida. En este
caso, contar con un programa que represente datos persistentes permitiría proyectar con más
eficiencia este tipo de sistemas con base en transacciones. Con un lenguaje de este tipo, se
declararían variables cuyos tiempos de vida se extienden más allá del tiempo de ejecución del
programa. La programación sería más simple, puesto que no habría necesidad de especificar
la forma de los datos en un archivo externo antes de manipularlos. El traductor del lenguaje
ya sabría donde se guardaron los datos y conocería su forma. (Como se indicó en el capítulo
2, todavía sería necesario leer los datos en el almacenamiento secundario y procesarlos en los
registros internos de alta velocidad de la unidad central de procesamiento, pero eso sería un
nivel de detalle que maneja automáticamente el traductor del lenguaje y no un asunto del
programador.) En la sección 9.6.1 se analiza la investigación en curso sobre el desarrollo de
lenguajes persistentes. Sin embargo, en tanto esos lenguajes llegan a usarse ampliamente, en
la sección 4.3.12 se estudia el uso de archivos para transferir datos persistentes a variables
locales de programas.
4.1.2 Tiposdedatos
Un Íz.po de c7o/os es una clase de objetos de datos ligados a un conjunto de operaciones para
crearlos y manipularlos. Aunque un programa se ocupa de o¿y.e/os cJe dc}Íos particulares como
un arreglo Á, la variable entera X o el archivo F, un lenguaje de programación trata más co-
múnmente, por necesidad, con fJ.pos cJe dcííos como la clase de arreglos, enteros o archivos y
las operaciones que se suministran para manipular arreglos, enteros o archivos.
Todo lenguaje tiene un conjunto de tipos pr;.mj.Í;.vos de datos que están integrados al 1en-
guaje. Además, un lenguaje puede proveer recursos que permitan al programador definir
nuevos tipos de datos. Una de las diferencias principales entre lenguajes más antiguos como
FORTRAN y COBOL y lenguajes más modernos como C y Ada está en el área de tipos de
datos definidos por el programador, un tema que se trata en la sección 5 .3 . La tendencia más
reciente es dejar que los tipos mismos sean manipulados por el lenguaje de programación.
Esta es una característica importante incorporada al ML y es una de las que están presentes en
los modelos de programación orientada a objetos que se analizan en el capítulo 8.
Los elementos básicos de una especz#ccw;.óm de un tipo de datos son:
1. Los c}Írjówfos que distinguen objetos de datos de ese tipo,
2. Los vo/ores que los objetos de datos de ese tipo pueden tener, y
3. Las opercicí.ones que definen las posibles manipulaciones de objetos de datos
de ese tipo.
Sec 4.1 PÍopiec]dcles c]e tipos y objetos 113
1. La represe#/czcz.Ón cíe cz/moce#omz.e#/o que se usa para representar los objetos de datos
del tipo de datos en el almacenamiento de la computadora durante la ejecución del
programa, y
2. La manera en que las operaciones definidas para el tipo de datos se representan en
términos de cr/gorítmos o procec7z.mí.e#/os particulares que manipulan la representación
de almacenamiento elegida para los objetos de datos.
La especificación de un tipo de datos corresponde aproximadamente a la especificación
de esa parte de la computadora virtual que está definida por el tipo de datos, según se des-
cribe en el capi'tulo 2. La implementación de un tipo de datos define la s/.mc//czcJ.ó# de esas.
partes de la computadora virtual en términos de las construcciones más primitivas que sumi-
nistra la capa subyacente de computadora virtual, la cual puede ser directamente la computadora
de hardware o una combinación de hardware y software definida por un sistema operativo o
microcódigo.
El último aspecto conectado con un tipo de datos se refiere a su represe#/c7c;.ó# sJ.#ÍcícÍJ.ccr.
Tanto la especificación como la implementación son en gran medida independientes de las
formas sintácticas particulares que se usan en el lenguaje mismo. Los atributos de objetos de
datos se suelen representar sintácticamente mediante c7ec/czrczcj.ones o cíe/77/.cÍ.o#cs c7e /J.po.
Los valores se pueden representar como constantes literales o definidas. Las operaciones se
pueden invocar usando símbolos especiales, procedimientos integrados o funciones como sJ.#
(seno) o reoc7 (leer), o implícitamente a través de combinaciones de otros elementos del len-
guaje. La representación sintáctica particular hace poca diferencia, pero la información presente
en la sintaxis del programa proporciona información al traductor del lenguaje que puede ser
vital para determinar el tiempo de enlazamiento de diversos atributos y, por tanto, para permitir
al traductor establecer representaciones de almacenamiento eficientes o para ejecutar revisiones
en busca de errores de tipo.
Las secciones que siguen examinan con más detalle las áreas de especificación, implemen-
tación, representación sintáctica y verificación de tipos de datos. Las representaciones sintác-
ticas particulares varían con tanta amplitud entre los lenguajes que no se intenta aquí un
estudio exhaustivo, aunque los ejemplos que se ofrecen son representativos de los lenguajes
que se describen en este libro.
Un objeto de datos c/emen/cz/ contiene un solo valor de datos. Una clase de estos objetos de
datos en la cual se definen diversas operaciones se conoce como un ÍÍ.po e/emem/cz/ c7e cJa/os.
114 Tiposdec]dtos Cdp. 4
Aunque cada lenguaje de programación tiende a tener un conjunto algo diferente de tipos
elementales de datos, se suelen incluir los tipos e#/ero, rec7/, de ccrrác/er, óoo/ecr#o, c/e e#z¿-
mercrcz.ó# y czp%#ÍocJor, aunque la especificación exacta puede diferir en forma significativa
entre dos lenguajes. Por ejemplo, aunque casi todos los lenguajes incluyen un tipo booleano
de datos, éste se trata de manera muy distinta en FORTRAN y en C.
Atributos. Los atributos básicos de cualquier objeto de datos, como el tipo de datos y el
nombre, son ordinariamente invariables durante su tiempo de vida. Algunos de los atributos
se pueden guardar en un czescrJ.p/or (también llamado vecíor cJe c}rreg/os) como parte del
objeto de datos durante la ejecución del programa; otros se pueden usar sólo para determinar
la representación de almacenamiento del objeto de datos y no pueden aparecer de manera
explícita durante la ejecución. Adviértase que el vo/or cíe w# o/rz.bej/o de un objeto de datos es
diferente del vcr/or gwe e/ o¿y.e/o c7e cJc!/os co#/;.e#e. El valor contenido puede cambiar durante
el tiempo de vida del objeto de datos y siempre se representa en forma explícita durante la
ejecución del programa.
Valores. El tipo de objeto de datos determina el conjunto de valores posibles que puede con-
tener. En cualquier punto durante su tiempo de vida, un objeto elemental de datos contiene un
solo valor de este conjunto. Por ejemplo, el tipo en/ero de datos determina un conjunto de
valores enteros que pueden servir como valores para objetos de datos de este tipo. Este conjunto
de valores es por lo común el mismo que el conjunto de valores enteros que se pueden re-
presentar de manera conveniente en el almacenamiento en la computadora de hardware sub-
yacente, aunque no es necesario que así sea. Por ejemplo, C define las cuatro clases siguientes
de tipos enteros: z.#/ (ent), sÁor/ (corto), /o#g (largo) y cÁcw (car). Puesto que casi todo el
hardware implementa aritmética de enteros de precisión múltiple (por ejemplo, enteros de 16
bits y 32 bits o enteros de 32 bits y 64 bits), C permite al programador elegir entre estas
implementaciones definidas por el hardware. SÁor/ usa el valor más corto de la longitud de
palabra del entero, /ong usa el valor más largo que implementa el hardware, e ;.#Í usa el valor
más eficiente que el hardware implementa. Éste puede ser igual que sbor/, igual que /o#g o
algún valor intermedio entre éstos. Es interesante señalar que en C los caracteres se guardan
como enteros de s bits en el tipo c4ar, que es un subtipo entero.
El conjunto de valores que define un tipo elemental de datos es comúnmente un co#/.ct#Ío
orc7enoc7o, con un valor mínimo y un valor máximo, y para cualquier par de valores distintos,
uno es más grande que el otro. Por ejemplo, para un tipo entero de datos, existe ordinariamente
un valor máximo que corresponde al entero más grande que se puede representar en forma
conveniente en la memoria y, de manera similar, un entero mínimo, con los enteros intermedios
dispuestos en su ordenamiento numérico usual.
(b) El operador "=" que prueba en cuanto a igualdad de los valores de dos objetos de datos
enteros y produce un objeto de datos que contiene un resultado booleano (cierto o falso) se
especifica:
--.. entero x entero + booleano
(c) Una operación de raíz cuadrada, SQRT (square rooí), sobre objeto de datos de números
reales se especifica:
SQRT .. real + real
\. Operaciones que no están definidas para ciertas entradas. Una. operaLc.ióri que
aparentemente está definida en algún dominio puede no estar definida, de hecho, para
ciertas entradas en el dominio. Puede ser en extremo dificil especificar el dominio
exacto en el cual una operación no está definida, como, por ejemplo, los conjuntos de
números que causan exceso negativo o desbordamiento en operaciones aritméticas.
En la sección 9.4 se comenta la investigación reciente que está buscando formas de desarro-
llar especificaciones precisas de operaciones.
Sec. 4.1 Propiec]ddes c]e tipos y ob)etos 117
Subtipos. Cuando se describe un nuevo tipo de datos, se suele desear expresar que es "simi-
lar" a otro tipo de datos. Por ejemplo, C define los tipos ;.#/, /o#g, s¢or/ y cAc7r como variantes
de enteros. Todos se comportan en forma similar, y sería deseable que ciertas operaciones,
como + y x, se definieran de manera análoga. Si un tipo de datos es parte de una clase más
grande, se dice que es un sw¿/J.po de la clase mayor, y ésta es un sztpcr/;-po de este tipo de
datos. Por ejemplo, en Pascal se pueden crear subintervalos de enteros, como en:
1. Por razones de eficiencia, muchos lenguajes se proyectan de modo que los atributos de
datos sean determinados por el compilador. Los atributos mismos no se guardan en la re-
presentación de almacenamiento en tiempo de ejecución. És{a es ordinariamente una
representación que e] hardware proporciona directamente. Éste es el método usual en
C, FORTRAN y Pascal, donde la eficiencia en el uso del almacenamiento y la velocidad
de ejecución son objetivos primordiales.
118 Tiposc]ec]dtos Cdp. 4
2. Los atributos de un objeto de datos se pueden guardar en un c/escr;.píor como parte del
objeto de datos en el tiempo de ejecución. Éste es el método usual en lenguajes como
LISP y Prolog, donde la flexibilidad, más que la eficiencia, es el objetivo primario.
Puesto que casi ningún hardware suministra directamente representaciones de almace-
namiento para descriptores, los descriptores y las operaciones sobre objetos de datos
con descriptores se deben simular por software.
1. D;.rec/c}me#/e como operc}c/.Ó# c7e ¢czrc7wc]re. Por ejemplo, si se guardan enteros usando
la representación de hardware para enteros, entonces la adición y la sustracción se
pueden implementar usando las operaciones aritméticas integradas en el hardware.
2. Como un subprograma de procedimiento o de función. Por e.]emplo, una. operzLc.\ón de
raíz cuadrada no se suministra comúnmente en forma directa como una operación de hard-
ware. Se podría implementar como un subprograma de raíz cuadrada que calcule la
raíz cuadrada de su argumento. Si los objetos de datos no se representan usando una
representación definida por hardware, entonces todas las operaciones se deben simular
por software, por lo común en forma de subprogramas que se suministran en una
biblioteca de subprogramas.
3 . Como una secuencia de código en línea. Una secuencia de código en línea es tamb.ién
una implementación de la operación por software, pero, en vez de usar un subprograma
muy corto, las operaciones del subprograma se copian en el programa en el punto
donde de otra manera se habría invocado el subprograma. Por ejemplo, la función de
valor absoluto sobre números, definida por:
4.1.5 Declaraciones
float A, 8;
al principio del subprograma indica que se necesitan dos objetos de datos del tipo/oc7/ du-
rante la ejecución del subprograma. La declaración también especifica el enlace de los objetos
de datos a los nombres Á y 8 durante sus tiempos de vida.
La declaración anterior en C es una declaración exp/;'c;./c7. Muchos lenguajes también su-.
ministran declaraciones /.mp/;'c;./czs o por omi.sz'Ón, que son las declaraciones que valen cuando
no se da una declaración explícita. Por ejemplo, en un subprograma en FORTRAN, se puede
usar una variable simple /'ND/CE sin declaración explícita, y por omisión el compilador de
FORTRAN supone que es una variable entera`porque su nombre comienza por una de las
letras I-N. Alternativamente, el programa en FORTRAN podría declarar explícitamente que
/'jvD/CE es una variable real por la declaración:
REAL ÍNDICE
Una declaración también puede especificar el valor del objeto de datos si es una constante, o
el valor inicial del objeto de datos en caso contrario. También se pueden especificar en la
declaración otros enlaces para el objeto de datos: un nombre para el objeto de datos o la po-
sición del objeto de datos como componente de un objeto de datos más grande. A veces
también se especifican detalles de implementación, como el enlace a una localidad particular
de almacenamiento o a una representación particular especializada de almacenamiento. Por
ejemplo, la designación en COBOL de una variable entera como COMPUTATIONAL (de
computación) indica por lo común que se necesita una representación de almacenamiento
binaria, en vez de una de cadena de caracteres, para el valor de ese objeto de datos (para per-
mitir el uso de operaciones aritméticas más eficientes).
Dec]araciones de operaciones
Las declaraciones también se pueden usar para especificar información acerca de operaciones
en el traductor del lenguaje. La información que se requiere durante la traducción es princi-
palmente la signatura de cada operación. Por lo común no se necesita una declaración explícita
de tipos de argumentos y tipos de resultados para operaciones primitivas que están integradas
en un lenguaje. Estas operaciones se pueden invocar conforme se necesitan al escribir un
programa, y los tipos de argumentos y resultados son determinados de manera implícita por
120 Tposdeclótos Cdp. 4
el traductor del lenguaje. Sin embargo, es común que los tipos de argumentos y resultados
para operaciones definidas por el programador se tienen que dar a conocer al traductor del
lenguaje antes de que se pueda llamar el subprograma. Por ejemplo, en C, la cabeza de una
definición de subprograma, su pro/o/Í.po, proporciona esta información:
float Sub(int X, float Y)
la cual declara que S2jó tiene la signatura:
Sub .. int x float + float
11100101100...0011
Esta serie de bits podría representar un entero, un número real, una serie de caracteres o una
instrucción; no hay forma de saberlo. La operación primitiva de hardware para suma de enteros
no puede verificar si sus dos argumentos representan enteros; son simplemente series de bits,
y la operación de hardware debe suponer que representan enteros. Un error común de progra-
mación en lenguajes ensambladores o de máquina es invocar una operación como la suma de
enteros sobre argumentos del tipo erróneo. Es particularmente dificil encontrar estos errores
porque la operación no falla en alguna forma obvia. La operación "funciona", pero los
resultados que produce no tienen sentido. Sin embargo, puesto que el resultado es simplemente
otra cadena de bits sin información de tipo anexa, las operaciones subsiguientes pueden
continuar el cómputo con el resultado ``basura" para producir más "basura", hasta que fracasa
el cómputo completo. Así pues, al nivel del hardware, las computadoras convencionales son
particularmente poco confiables para detectar errores de tipo de datos.
yer/#cc7c/.ó# c7e /;.pos significa comprobar que cada operación que un programa ejecuta re-
cibe el número apropiado de argumentos del tipo correcto de datos. Por ejemplo, antes de
ejecutar el enunciado de asignación:
X-A+8*C
se debe determinar para las tres operaciones, suma, multiplicación y asígnacíón, que cada
una reciba dos argumentos del tipo de datos apropiado. Si + se define sólo para argumentos
enteros o reales, y Á nombra un objeto de datos de carácter, entonces existe un error c7e /f.po
122 Tiposdeddtos Cdp. 4
probación se debe hacer antes de cada ejecución de cada operación, es probable que la
velocidad de ejecución del programa se reduzca en forma considerable.
Casi todos los ]enguajes intentan eliminar o reducir al mínimo la verificación dinámica de
tipos efectuando la verificación de tipos durante la compilación. La ver/#cc7c/.Ón es/cí//.co
cJc /Í.pos se efectúa durante la traducción de un programa. Por lo común, la información ne-
cesaria se suministra en parte mediante declaraciones que proporciona el programador y en
parte por otras estructuras del lenguaje. La información que se requiere es:
\ . Para cada operación, el número, orden y tipos de datos de sus argurneníos y resultados.
Para operaciones primitivas, esta información es parte de la definición básica del len-
guaje, pero dentro de subprogramas y clases el programador la debe especificar de
manera explícita.
2. Para cada variable, el tipo de objeio de datos nombrado. E:l tipo del ob.]eto de daiios
asociado con un nombre variable debe permanecer también sin cam.bio durante la
ejecución del programa, de manera que al verificar una expresión como "A+B" se pue-
da suponer que el tipo objeto de datos que nombra A es el mismo en cada ejecución de
la expresión, aunque la expresión se ejecute repetidamente con distintos enlaces de A
hacia objetos particulares de datos (por ejemplo, en caso de que A sea un parámetro
formal de un subprograma).
3. E/ /Í.po c7e cczcJcz o¿y.e/o cJe c7c7/os co#s/c7#/e. La forma sintáctica de una literal indica
comúnmente su tipo; por ejemplo, "2" es un entero, "2.3" es un número real. Cada
constante definida debe equipararse con su definición para determinar su tipo.
bles y operaciones. Después de reunir toda la información de tipos, se verifica cada operación
que invoca el programa para determinar si el tipo de cada argumento es válido. Adviértase que
si la operación es polimórfica, como se expuso anteriorinente, entonces puede ser válido
cualquiera de varios tipos de argumentos. Si los tipos de argumentos son válidos, entonces se
determinan los tipos de resultados y el compilador guarda esta información para verificar
operaciones poste.riores. Obsérvese que, al mismo tiempo. el nombre de la operación polimór-
fica puede reemplazarse por el nombre de la operc7c/.Ó# espccí/7cc7 c/e /Í.po particular que usa
argumentos de los tipos designados.
Puesto que la verificación estática de tipos incluye todas las operaciones que aparecen en
cualquier enunciado de subprograma. se verifican todas las rutas de ejecución posibles y no
se necesita una revisión adicional en busca de errores de tipo. Por tanto. no se requieren mar-
cas de tipo en los objetos de datos en tiempo de ejecución y no es necesaria una verificación
dinámica de tipos; el resultado es una ganancia considerable en eficiencia de uso de alma-
cenamiento y velocidad de ejecución.
La preocupación por la verificación estática de tipos tiende a afectar miichos aspectos del
lenguaje: declaraciones, estructuras de control de datos y recursos para compilación de sub-
programas por separado. por citar unos cuantos. En casi todos los lengiiajes, la verificación
124 Tiposdeddtos Cdp. 4
estática de tipos no es posible para ciertas construcciones de lenguaje en ciertos casos. Estos
"defectos" en la estructura de verificación de tipos se pueden tratar de dos maneras:
1. Por verÉ#cc}cJ.Ó# c7j.#cím;.ccz c7e Í/.pos. El costo de almacenamiento de esta opción suele
ser alto porque las marcas de tipo para los objetos de datos se deben guardar en tiempo
de ejecución, no obstante que estas marcas se verifican muy raras veces.
2. Dejando las operaciones sin verficar. La,s opera,clones s.m veritiica.r pueden czLusa.r
errores de programa serios y sutiles, como ya se ha señalado, pero a veces se aceptan
cuando el costo de la verificación dinámica se considera demasiado alto.
Tipos fuertes. Si se pueden detectar estáticamente todos los errores de tipo de un programa,
se dice que el lenguaje es de /j.pos/wer/es. En general, los tipos fuertes proporcionan un nivel
de seguridad a los programas. Se dice que una función/, con signatura/: S + R, es segem e#
c%cz#Ío a /z.pos si la ejecución de/no puede generar un valor fuera de R. Para cualesquier
operaciones seguras en cuanto a tipos, se sabe estáticamente que los resultados serán del tipo
correcto y que no es necesario efectuar una verificación dinámica. Es obvio que, si todas las
operaciones son seguras en cuanto a tipos, el lenguaje es de tipos fuertes.
Pocos lenguajes son verdaderamente de tipos fuertes. Por ejemplo, en C, si .Yy y son de
tipo sÁor/ (es decir, enteros cortos), entonces .Y+ yy .Y * y pueden tener un resultado fuera
del intervalo permisible para enteros cortos y causar un error de tipo. Aunque es dificil tener
tipos verdaderamente fuertes, si se restrihge la conversión entre un tipo y otro, se está cerca
de alcanzar el objetivo. Se analiza esta clase de conversión en la subsección siguiente.
lníerencia de tipos. ML tiene un enfoque interesante hacia los tipos de datos. Las decla-
raciones de tipos no son necesarias si la interpretación no es ambigua. La imp]ementación del
lenguaje inferirá cualquier información faltante en cuanto a tipos a partir de otros tipos de-
clarados. El lenguaje tiene una sintaxis relativamente común para declarar argumentos para
funciones, como en:
que declara la función área para que devuelva el área (como un entero) si se dan los lados en-
teros de un rectángulo. En este caso, una vez que se determina el tipo ya sea de /o#gj./c¿cÍ,
o#c¢o o círec7, entonces los otros dos también están determinados. La exclusión de cualesquiera
dos de estas declaraciones deja todavía la función con una sola interpretación. Como sabe
que * puede multiplicar entre sí ya sea dos números reales o dos enteros, ML interpreta lo
siguiente como equivalente al ejemplo anterior:
puesto que ahora es ambiguo en cuanto al tipo de argumentos. Podrían ser todos Í.#Í o
todos reo/.
Si durante la verificación de tipos ocurre una discordancia entre el tipo real de un argumento
y el tipo esperado para esa operación, entonces:
1. La discordancia de tipos se puede marcar como un error y adoptarse una acción de
error apropiada, o
2. Se puede aplicar una cocrcj.ón (o co#vers;.ó# z.mp/j'c/./o c7e /Í.po) para cambiar el tipo del
argumento real al tipo correcto.
Una co#versj.Ón cJe f;.po es una operación con la signatura:
El principio básico que gobierna las coerciones es no perder información. Puesto que todo
entero corto (en C) se puede representar como un entero largo, no se pierde información al
invocar automáticamente una conversión de s¢or/ ;.#/ + /ong ¿.#/. Estas conversiones se llaman
e#scJ#cAczmJ.e#/os o promoci.o#es. De manera similar, puesto que los enteros (en casi todos los
lenguajes) se pueden representar de manera exacta como un objeto de datos de números reales,
los enteros de valor reducido se ensanchan comúnmente a números reales sin pérdida de
información.
Por otra parte, la coerción de un número real a un entero puede hacer que se pierda infor-
mación. Mientras que 1.0 es exactamente igual al entero 1,1.5 no es representable como
entero. Será conveiiido ya sea a 1 o a 2. En este caso, se describe la coerción como un es/recAc7-
mJ.enío, y se pierde información.
Con la verificación dinámica de tipos, las coerciones se hacen en el punto en que se detecta
la discordancia durante la ejecución. Para este tipo de lenguajes, las conversiones de estre-
126 Tiposdeddtos Cdp. 4
chamiento podrían permitirse si el objeto de datos tuviera un valor apropiado (por ejemplo,
1.0 se podría convertir al tipo entero, pero 1.5 no). Para la verificación estática de tipos, se in-
serta código adicional en el programa compilado para invocar la operación de conversión en
el punto apropiado durante la ejecución. Puesto que la ejecución eficiente es por lo general
un atributo deseable, las coerciones de estrechamiento se prohíben comúnmente para que no
tenga que ejecutarse código en tiempo de ejecución para determinar si la conversión sería
legal 0 no.
Una operación de conversión de tipos puede requerir cambios extensos en la representación
de almacenamiento del objeto de datos en tiempo de ejecución. Por ejemplo, en COBOL y
PL/I, los números se suelen guardar en forma de cadenas de caracteres. Para efectuar la suma
de estos números en casi cualquier máquina, la representación de almacenamiento de cade-
nas de caracteres se debe convertir a una representación de números binarios que maneje el
hardware, y el resultado se convierte de nuevo a la forma de cadenas de caracteres antes de
guardarse. En este caso, las operaciones de conversión de tipo pueden tomar cientos de veces
más tiempo que la suma real misma.
Sin embargo, los implementadores de traductores de lenguajes confunden a veces la se-
mántica de un objeto de datos y su representación de almacenamiento. Los datos decimales
en COBOL y PL/I son un caso a propósito. Los traductores de PL/I guardan normalmente los
datos FIXED DECIMAL (decimal fija) en un formato de "decimal empacado". Esto es una
representación de hardware, pero una que se ejecuta con bastante lentitud. En el compilador
de PL/C [CONWAY y WILCOX 1973], los datos FIXED DECIMAL se guardan como datos
de punto flotante de doble precisión y 16 bits. Al guardarlos como 16 dígitos, no se pierde
precisión (importante para guardar datos decimales). Por ejemplo, el cómputo de 123.45 +
543.21 requiere una suma bastante lenta de decimales empacados (o una simulación por soft-
ware aún más ]enta si los datos decimales empacados no se implementan por hardware), en
tanto que PL/C efectúa esto como una suma de punto flotante única, más rápida, de 12345 +
54321. El compilador sigue la pista del punto decimal (por ejemplo, dividir entre 102 para
obtener el valor verdadero) como un atributo del valor resultante en tiempo de compilación.
Existen dos filosofías opuestas en cuanto a la medida en que el lenguaje debe proporcionar
coerciones entre tipos de datos. En Pascal y Ada, casi no se proveen coerciones; cualquier
discordancia de tipo, con pocas excepciones, se considera un error. En C, las coerciones son
la regla; una discordancia de tipo hace que el compilador busque una operación de conversión
apropiada para insertarla en el código compilado para suministrar el cambio de tipo adecuado.
Sólo si no hay una conversión posible la discordancia se señala como un error.
La discordancia de tipo es un error menor común de programación y la conversión de
tipos es una necesidad también común, en particular en lenguajes que tienen un número grande
de tipos de datos. Existen también cuestiones sutiles en cuanto al significado de la noción de
"discordancia de tipos" (véase la sección 5.3). Las coerciones suelen liberar al programador
de preocupaciones respecto a lo que de otro modo serían detalles tediosos ---la invocación de
conversión de operaciones de tipos numerosos explícitamente en un programa. Sin embargo,
las coerciones también pueden enmascarar errores serios de programación que de otra manera
podrían hacerse notar al programador durante la compilación.
EI PL/I, en particular, tiene mala fama por la propensión de sus compiladores a tomar un
error menor de programaciún como un nombre de variable mal escrito y, a través de una
Sec. 4.1. Propieddcles de tipos yobietos 127
coerción a veces sutil, ocultar el error de modo que éste se convierte en un defecto del programa
que es difícil detectar. Puesto que las operaciones de conversión del PL/I permiten coerciones
de estrechamiento, los resultados son sorprendentes. Por ejemplo, i9+10/3 es ilegal! Para ver
esto, obsérvese que 10/3 es forzado a 3 .33333333... hasta el número máximo de dígitos definido
por la implementación. Pero 9 + 3.333... tiene un dígito adicional en el resultado, lo que da
origen a una excepción por OVERFLOW (desbordamiento). Si esta excepción se inhabilita
(es decir, se pasa por alto), entonces el resultado se convierte automáticamente a 2.333 ..., que
no es precisamente lo que se esperaba.
Casi todas las operaciones para los tipos elementales de datos comunes, en particular números,
enumeraciones, booleanos y caracteres, toman uno o dos objetos de datos de argumento del
tipo, efectúan una operación aritmética, relacional o de otro tipo relativamente simple, y pro-
ducen un objeto de datos de resultado, que puede ser del mismo tipo o de otro diferente. Sin
embargo, la operación de asignación es un poco más sutil y amerita atención especial.
La asignación es la operación básica para cambiar el enlace de un valor a un objeto de
datos. Este cambio, sin embargo, es un e/ec/o co/oíerc7/ de la operación. En ciertos lenguajes,
como C y LISP, la asignación también devuelve un valor, el cual es un objeto de datos que
contiene una copia del valor asignado. Estos factores se aclaran cuando se intenta escribir
una especificación para asignación. En Pascal, la especificación para asignación de enteros
sería:
con la acción: hacer que el valor contenido en el objeto de datos e#/ero, sea una copia del va-
lor contenido en el objeto de datos e#/ero2 y devolver el resultado no explícito. (El cambio a
íntegro 1 es un resultado implícito o efecto colateral.) En C, la especificación es:
asignación(--)..entero,xentero2+entero3
con la acción: hacer que el valor contenido en el objeto de datos e#/ero, sea una copia del va-
lor contenido en el objeto de datos e#/ero2 y también crear y devolver un nuevo objeto de
datos e#/ero3, que contenga una copia del nuevo valor de e#/ero2.
Considérese la asignación: .Y := jY. Lo interesante acerca de este enunciado es la difei.ente
interpretación que se da a ambas referencias de la variable Jr. La.X'de la derecha se refiere al
valor que contiene el objeto de datos nombrado. Estas referencias se suelen llamar el vc7/or
del lado derecho (del operador de asignación) o valor r de m okj;]e;+o de distos. De me[neTai sii-
milar, la Jr de la izquierda se refiere a la localidad del objeto de datos que va a contener el
nuevo vaLlor. Estais referenc,.iais se conocen c,omo valor del lado izquierdo (del operador de
czsj.g#acJ.ó#J o vcz/or /. Por tanto, se puede definir una operación de asignación como:
AHAH
t`
Después
Asignación de apuntador en C
Después
.4 + 2 + 3
¿Significa esto:
En lenguajes con tipos estáticos de datos, el tipo para Á decide cuál semántica se va a
seguir: si Á es de tipo entero, entonces sólo la asignación de 5 a Á tiene sentido; si Á es del
tipo opercrc/.Ó#, entonces sólo la segunda es apropiada. En cambio,`para un lenguaje con tipos
dinámicos, donde a Á se le confiere un tipo en virtud de la asignación de un valor a ella,
ambas semánticas pueden ser aplicables y la asignación precedente puede ser ambigua.
Esta situación se presenta exactamente dentro de Prolog. El operador Í.s significa asignar
el valor equivalente, en tanto que el operador = significa asignar el patrón. La igualdad está
determinada entonces por el valor presente de la variable asignada. Así pues, en Prolog, la
primera cláusula de más abajo tiene éxito porque a .Xse asigna primero el valor 5 y su tipo se
fija en entero, mientras que ]a segunda cláusula fracasa, puesto que a.Yse le asigna la operación
"2+3", que no es lo mismo que el entero 5:
1. X es 2 + 3, X = 5.
2. X = 2 + 3, X = 5.
Inicialización. Una vcz//.czb/e #o /.n/.c/.cz//.zczc/cz, o, de manera más genera], un o¿y.e/o c7c cJc7/os no
j.#Í.cJ.cz/;.zczcJo, es un objeto de datos que ha sido creado pero al que todavía no se asigna un
valor (es decir, un valor 1 sin un valor r correspondiente). La creación de un objeto de datos
implica ordinariamente sólo la asignación de un bloque de almacenamiento. Si no hay más
acciones, el bloque de almacenamiento retiene el patrón de bits, cualquiera que éste sea, que
contenía cuando se hizo la asignación. Por lo regular, se requiere una asignación explícita
para en]azar un objeto de datos a un valor válido. En ciertos lenguajes (por ejemplo, Pascal)
la inicialización se debe hacer de manera explícita con enunciados de asignación. En otros
lenguajes (por ejemplo, APL) se deben especificar valores iniciales para cada objeto de datos
cuando se crea el objeto; la asignación de valores iniciales se maneja en forma implícita sin
que el programador use la operación de asignación.
Las variab]es no inicializadas son una fuente seria de error de programación para progra-
madores tanto profesionales como principiantes. Con frecuencia, el patrón aleatorio de bits
130 Tiposclec]dtos Cdp 4
crea un arreglo Á y asigna a cada elemento, en forma explícita, un valor inicial en la declaración.
Puesto que ordinariamente el arreglo Á se crea de manera dinámica durante la ejecución del
programa, la implementación de la asignación de valor inicial requiere la generación de código
por parte del compilador, el cual, cuando se ejecuta, asigna de manera expli'cita los valores
iniciales especificados del objeto
p
n
en el hardware