Está en la página 1de 47

UNIDAD 1: EVOLUCION DE LOS LP

Definición
Un lenguaje de programación es un conjunto de símbolos y reglas para combinarlos (léxicas, sintácticas y
semánticas), que se usan para expresar algoritmos en forma legible, para computadoras y personas.

Clasificación

• Por su Nivel.
o Bajo Nivel: Son aquellos cuyas instrucciones ejercen control directo sobre el HW y están condicionados por
la arquitectura del procesador (cada tipo de procesador presenta su propio set de instrucciones). También
llamados ensambladores. Surgen para abstraerse del lenguaje máquina, con el uso de nemotécnicos y
abstracciones de direcciones de memoria (registros), permiten generar código más cercano a la máquina,
pero alejados de los 1s y los 0s. Tienen una mayor velocidad de ejecución al estar cercanos a la arquitectura
de la máquina, sin embargo, por esta misma razón carecen de portabilidad, y además las instrucciones están
bastante alejadas del lenguaje humano y tienen poco poder expresivo.
o Alto Nivel: Son los más utilizados para programar aplicaciones. Tienen un nivel de abstracción y estilo de
escritura fácilmente legible y comprensible (respecto a los de Bajo Nivel). Son independientes de la
arquitectura del procesador (y portables).

• Por su Jerarquización (cada generación sube su nivel de abstracción).


o 1°Generación: (En los 40) constituido por la aparición del Lenguaje de Maquina. Inentendible para los
humanos.
o 2°Generación: (En los 50) Aparece el Lenguaje Ensamblador facilitando la forma de programar. Utiliza
variables que el programador puede manipular. Pero tiene la desventaja de depender de la arquitectura
subyacente. Elimina un poco la propensión a cometer errores, y aumenta la eficiencia de programación.
o 3° Generación: (Fines de los 50s - actualidad) Aparecen los Lenguajes de Alto Nivel. Son independientes de la
arquitectura subyacente, y son más fáciles de entender. Se debate si los LOOs están en 3G o en 4G.
o 4° Generación: (Fines 80s): Proveen entornos de desarrollo constituidos por herramientas integradas tales
como compiladores, editores, depuradores. Dan apoyo a los LP de alto nivel. PL/SQL o 4GL de informix.
o 5° Generación: (80s) Se basan en la lógica y matemática, se utilizan para la inteligencia artificial y redes
neuronales. Usan una serie de instrucciones con limitaciones o restricciones, en lugar de usar algoritmos que
describen una serie de instrucciones. Ej. Prolog, Miranda, ML

• Por el estilo (forma) de programación


o Imperativos: Su ejecución se realiza paso a paso. Se define una secuencia de instrucciones a ejecutar.
o Declarativos. se define que es lo que se quiere resolver, pero no se declara cómo será calculado el resultado.
Los programas tienen declaraciones en vez de asignaciones y sentencia de control de flujo. Se ejecutan
verificando condiciones que, si se satisfacen o no, ejecutan una acción. Se debe proveer a la computadora de
información relevante y un método de inferencia.
• Por el manejo de las Instrucciones.
o Imperativos (PROGRAMA = ALGORITMOS+ESTRUCTURAS DE DATOS): Se caracteriza por el uso de variables y
asignaciones a memoria. Se definen paso a paso las instrucciones que, a medida que se van ejecutando, van
cambiando el estado de las variables. Se basan en la arquitectura Von Neumann (CPU + Memoria + I/O). Ej.
Fortran, Algol, C, Pascal y Ada.
o Orientados a Objetos (PROGRAMA = OBJETOS+MENSAJES): Incorporan conceptos de clases, herencia,
polimorfismo. Los objetos, instanciados a partir de las clases, se comunican entre si a través del paso de
mensajes. Ventaja de reutilización. Ej. Simula 67, Smalltalk, C++, Ada 95, Java.
o Concurrentes, Paralelos y Distribuidos. Proveen sincronización y pasaje de mensajes. Permiten realizar
operaciones diferentes al mismo tiempo (concurrencia), o una misma operación con varios procesadores
simultáneamente (paralelos), o la ejecución en paralelo con procesadores separados geográficamente
(distribuidos). Ej. Pascal Concurrente, Modula, Ada y OCCAM.
o Funcionales (PROGRAMA = FUNCIONES+ESTRUCTURAS DE DATOS): Se basan en funciones matemáticas. Un
programa se considera una gran función compuesta de funciones simples que se aplican a los datos de
entradas pasados como parámetros. No se usan variables ni asignación a memoria. Ej.: Lisp (primeras
versiones), ML, Haskell.
o Lógicos (PROGRAMA = LOGICA+CONTROL+ESTRUCTURAS DE DATOS): Se basan en el cálculo de predicados.
La programación se realiza a través de enunciados lógicos de los cuales se va comprobando su veracidad. Se
manejan hechos y reglas, sobre los cuales se realizan consultas. Utilizan variables en los parámetros, pero no
asignación de memoria. Ej.: Prolog.

Los LP en el proceso de desarrollo de software


• Especificación y Análisis de requerimientos: Se define cual es el problema y QUE debe hacer el sistema.
Usuarios y analistas elaboran en conjunto el Documento de Especificación de Requerimientos. El éxito del
sistema se mide en base al cumplimiento de los requerimientos.
• Diseño: En base al documento de la etapa anterior, se elabora el Documento de Especificación de Diseño, donde
se determina COMO se resolverá el problema: se identifican los módulos en el sistema y sus interfaces, que LP se
va a usar, entre otras cosas. El desarrollo de SW debe influir en la elección del LP y no al revés (según la
metodología elegida, voy a elegir el lenguaje).
• Implementación: se elige la forma de codificación ajustada al diseño (el LP debe admitir la metodología). Se
codifica el sistema según las especificaciones. El resultado es el sistema codificado y documentado.
• Verificación y Evaluación: se testea el rendimiento del sistema integralmente y por módulos. Se verifica si se
construyó el sistema de manera correcta y si resuelve el problema. Esta fase debería realizarse durante cada una
de las etapas para chequear que las entregas intermedias cumplan sus objetivos.
• Mantenimiento: se modifica el sistema ante cualquier malfuncionamiento o para adicionar nuevas funciones y/o
mejoras. Esta etapa tiene más costo que sumadas las 4 anteriores.

LP, Metodologías de desarrollo y Arquitectura de computadoras:


En los años 50 y 60, las primeras generaciones de LP no estaban orientados a ninguna metodología. Las aplicaciones
eran simples y se ponía énfasis en la eficiencia. Predominaban FORTRAN (cálculo numérico), Cobol (orientado a
negocios), LISP (IA).
A finales de los 60, los programas y los requerimientos se iban haciendo cada vez complejos. Se pone énfasis en la
legibilidad y en el manejo de estructuras de control. Surge la programación estructurada y el diseño TOP-DOWN.
A finales de los 70, se pasó de la orientación a procesos (la resolución se define paso a paso) a la orientación a datos,
que destaca el diseño de los datos usando TDA para resolver el problema. Surgen lenguajes que soportan esta
abstracción, como Simula 67.
A mediados de los 80, surge la orientación a objetos, a la abstracción de datos se agregan herencia, polimorfismo,
encapsulamiento. Surge Smalltalk.
En los 90, surgen herramientas como las CASE, para automatizar y hacer más rápida la producción de SW,
En la actualidad, surgen las metodologías ágiles (Scrum y XP), el desarrollo de aplicaciones concurrentes y la
orientación a eventos (la ejecución de los programas es determinada por los sucesos que ocurran en el sistema).

Estas evoluciones en las metodologías, llevaron a que los LP incorporen nuevas características para soportarlas. Las
metodologías influyen en los LP porque establecen requisitos que éstos deben cumplir para apoyar el desarrollo.
Mientras que la arquitectura, restringe el diseño de los LP para que se implementen de manera eficiente en las
maquinas actuales. Los LP convencionales pueden verse como una abstracción de una arquitectura subyacente de
Von Neumann (CPU + Memoria + I/O).
LP y los entornos de desarrollo
Un IDE es un conjunto integrado de herramientas (editores, depuradores, simuladores, interpretes, compiladores) y
técnicas usadas en todas las etapas del ciclo de desarrollo, que facilitan la tarea de programar. Ej. NetBeans, Eclipse.

LP y Dominios de aplicación.
Es el área para la cual fue pensado el LP en el momento en que se diseñó. Es un conjunto de requerimientos que
debe reunir el LP para poder resolver cierto tipo de problemas. El LP que se utilice para programar, va a depender
del dominio de aplicación del problema.
• De Aplicación Científica: Los primeros programas se escribieron durante la Segunda Guerra Mundial. Se
caracterizan por requerir cálculos complejos de manera rápida, precisa y eficiente. Usan números de punto
flotante, arreglos y matrices. FORTRAN y MatLab
• De Aplicaciones de Negocios: son programas diseñados para que las empresas manejen sus sistemas de
información. Los lenguajes desarrollados para este campo incorporan facilidades para generar informes y
planillas, operan de forma precisa con números decimales y operaciones aritméticas, manejo de caracteres y
archivos. COBOL, SQL.
• De Inteligencia Artificial: desarrolla programas que imitan el comportamiento de la inteligencia humana. Se usa
programación lógica y funcional. Son lenguajes capaces incluso de crear código en ejecución; toman decisiones
en base a patrones/argumentos preestablecidos. LISP, PROLOG, ML.
• De Programación en Sistemas: son LP que permiten construir sistemas operativos de forma eficiente, permiten
interactuar con instrucciones a bajo nivel. Ej. Assembler, C, C++
• De Aplicaciones Web: Son interactivos, donde domina la programación orientada a eventos y a objetos. Los LP
deben tener herramientas para acceder a servidores y bases de datos, y para entregar datos a los usuarios. Ej.
(X)HTML (de marcado); JAVA SCRIPT, PHP (de scripting); JAVA (de propósito general)

La Abstracción y los LP (presentada por Louden).


Consiste en “ocultar” las características de un objeto y obviarlas. Permite dar legibilidad al programa

• Abstracción de Datos: permite al obviar los detalles de la representación interna de los datos, tomando solo sus
características esenciales (atributos y operaciones). NIVELES:
o Básicas: Abstrae la representación interna de valores de datos. Las variables abstraen una dirección de
memoria a través del uso de un nombre (identificador) Ej. “int x”; int abstrae el tipo de valor, rango y
operaciones validas de x; x abstrae una posición de memoria.
o Estructuradas: Abstrae una colección de valores de datos relacionados entre sí. Ej. un array (int a [10]) o un
registro empleado que puede incluir nombre, dirección.
o Unitarias: Permite reunir códigos, relacionados entre sí, y datos. Tiene que ver con el encapsulamiento de
datos y ocultamiento de la información. Son importantes porque permiten la reutilización de datos en
programas diferentes. Ej. Paquetes en ADA y Java, Modulo en ML y Haskell, biblioteca

• Abstracción de Control: consiste en abstraer las propiedades de las transferencias de control. NIVELES:
o Básicas: sentencias que combinan varias instrucciones de maquina en una sentencia abstracta más sencilla.
Ej. enunciado de asignación x = x + 1, resume una operación y almacenamiento de un valor en la localización
de una variable. El GO TO, resume la operación de transferencia de control a otra parte del programa.
o Estructuradas: Dividen un programa en grupos de instrucciones que pueden estar anidadas. Ej., Instrucciones
de selección (if, case, switch), Instrucciones de repetición (While, for y do), Subprogramas (procedimientos y
funciones).
o Unitarias: Incluyen un conjunto de procedimientos que pueden ser vistos por otras partes del programa.
Permiten entender la totalidad del programa sin conocer los detalles de los servicios que brindan. Ej. tareas
en ADA.

Perspectiva histórica.
La evolución de los LP se vio afectada por las aplicaciones que se les fueron dando: de tener costos y tamaños
extremos, a tener computadoras personales de escritorio económicamente accesibles para cualquiera

Aplicaciones Militares - Eficiencia - Implementación.


Al principio, se hacía énfasis en la eficiencia de ejecución. Las computadoras costaban millones de dólares, por lo
que su uso era más bien científico y militar. El desarrollo de SW consistía solo en la implementación (codificar). Los
problemas eran bien entendidos, no se necesitaba análisis de requerimientos ni especificación de diseño.
Aplicaciones Comerciales - Mantenimiento - Análisis y Diseño.
A partir de los 60, las maquinas costaban menos, y los costos de programación aumentaron (programas más
grandes, necesidades de SW mayores). Se empezaron a desarrollar aplicaciones de otras ramas, como la comercial.
Las etapas de análisis y diseño de requerimientos empezaron a importar debido a que debían brindar apoyo a
grupos de programadores. También tomó importancia la etapa de mantenimiento.

Aplicaciones para Áreas Criticas - Confiabilidad - Verificación y Validación.


Las aplicaciones se empezaron a implementar en áreas críticas (aviones, maquinaria de salud). La confiabilidad se
transformó en algo fundamental, y para asegurarla, las etapas de verificación y validación son vitales.

Crisis de Software - Primeras Metodologías


Se produjo un salto exponencial en el HW que no fue acompañado por el desarrollo de los programas, lo que dio
lugar a la crisis de software, que dio lugar a las primeras metodologías y la Ingeniería de SW, para maximizar la
calidad de los desarrollos

Paradigmas de Programación
Los LP comienzan a agruparse bajo ciertas reglas, patrones y estilos de programación dando origen a los paradigmas.

Paradigmas de programación
Son modelos de diseño e implementación, que permiten generar programas siguiendo ciertas reglas, patrones y
estilos de programación, es decir, son métodos que orientan el proceso de programación.
• Un lenguaje soporta un paradigma si provee mecanismos que facilitan su implementación eficiente, es decir, si
fue diseñado para ese paradigma. Ej. Eiffel y Smalltalk son orientados a objetos; Fortran y Pascal son imperativos.
• Un lenguaje admite un paradigma si es posible escribir programas siguiendo el paradigma, pero con un notable
esfuerzo (no provee facilidades para programar en el paradigma). Ej. C++ admite orientación a objetos.
Los paradigmas se clasifican en Convencionales (Imperativo y Orientado a Objetos) y No Convencionales (Lógico y
Funcional). Los convencionales son más cercanos a la arquitectura Von Neumann y se centran en cómo resolver
determinados problemas; los No Convencionales están más cercanos al lenguaje humano, se especifica que se desea
calcular, pero no cómo.

Paradigma Imperativo: Algoritmos + Estructura de Datos = Programa.


Describe la programación en términos del estado del programa y sentencias que cambian dicho estado.
Básicamente, existe un programa que se carga en memoria, se ejecuta secuencialmente, toma datos que también
están en memoria, efectúa operaciones y actualiza dichos datos. Ej. C, Pascal, Fortran, Cobol.

Características:
• Uso de Variables: que apuntan a celdas de memoria que se van modificando durante la ejecución.
• Operaciones de asignación: que permiten modificar el valor de las variables.
• Estructuras de control (aportan legibilidad y facilita el mantenimiento): como las iteraciones, que permiten que
una o más sentencias se ejecuten varias veces, o las sentencias de selección que permite elegir que sentencias
queremos ejecutar (es decir, que permiten elegir uno entre varios caminos de ejecución).

Se solía hacer uso indiscriminado del GOTO (salto incondicional), lo que dificultaba la lectura y mantenimiento de los
programas. Por esto surge la programación estructurada, que usa un número limitado de estructuras de control que
minimizan la complejidad de los problemas y que reducen los errores.
La PE incorpora el diseño modular (o Top-Down), que consiste en descomponer el programa en módulos para
reducir la complejidad, y con esto surgen los conceptos de programa principal y subprogramas, manejo de variables
locales y globales, parámetros, recursividad). Se busca estructurar el control y la abstracción de datos para fomentar
la reusabilidad y la extensibilidad de los sistemas

Limitaciones:
1. Es difícil razonar una solución porque se tiene alta dependencia de la arquitectura.
2. No cuenta con el concepto de transparencia referencial
3. Efectos colaterales (menos seguridad), por su gran y extendido uso de variables globales.
4. Estructuras de control, que desdibujan la lógica del programa y crean potenciales problemas de mantenimiento
y seguridad.
Paradigma Orientado a Objetos: Objetos + Mensajes = Programa.
Surge debido a las limitaciones del paradigma imperativo, sumado a la necesidad de producir SW de manera rápida,
segura y confiable. Introduce los conceptos de: herencia, polimorfismo, objetos, clases, concurrencia, etc.
Los programas son un conjunto de objetos que se comunican entre sí mediante paso de mensajes (petición a un
objeto para solicitar la ejecución de uno de sus métodos). Su gran ventaja es la reusabilidad de código, la
abstracción, el mantenimiento y el trabajo en equipo. Ejemplos: Smalltalk, C++, ADA, Java, Eiffel.

Los objetos son instancias de clase (se crean en ejecución). Las clases son modelos de creación de objetos, donde se
definen atributos (datos de los objetos) y métodos (operaciones para manipular datos) de objetos comunes
Un objeto esta caracterizado por:
• Estado: formado por el conjunto de atributos (estructura estática) y los valores que pueden tomar esos atributos
(estructura dinámica). Los atributos describen el estado del objeto
• Comportamiento: son las operaciones que pueden realizarse sobre el objeto, que hacen que cambie de estado.
• Identidad: propiedad que lo distinguen de los demás, como el nombre, las direcciones de memoria.

Propiedades
• Abstracción: implica concentrarse en los aspectos que importan de un objeto, dejando de lado los detalles, para
el dominio del problema
• Modularidad: permite simplificar la complejidad del problema, descomponiendo en módulos que realicen tareas
específicas. Dividir el problema en subproblemas.
• Encapsulamiento: tiene que ver con el ocultamiento del estado de un objeto, de manera tal que solo se pueda
modificar mediante los métodos definidos como públicos dentro de la clase que es instanciada por el objeto.
• Jerarquía: permite clasificar u ordenar las abstracciones. Existen 2 tipos: de clases (herencia) y de partes
(agregación)
o Herencia: mecanismo que permite definir nuevas clases partiendo de otras ya existentes. Las clases que
derivan de otras heredan su comportamiento (atributos y operaciones), y pueden introducir características
propias particulares que las diferencian. Esto facilita la reutilización del código. Existen dos tipos de herencia:
▪ Por especialización: cuando necesitamos crear una clase nueva que disponga de las mismas
características que otra pero que le añada funcionalidades
▪ Por generalización cuando tenemos muchas clases que comparten unas mismas funcionalidades,
entonces se decide crear una clase que implemente toda esa parte común y se dejan solo las partes
especificas en cada clase.
También puede clasificarse a la herencia como:
▪ Simple: una clase base hija tiene solo una clase padre.
▪ Múltiple: una clase base hija tiene solo varios padres. Toma las características de ambas
• Polimorfismo: capacidad de que objetos de diferentes clases respondan al mismo mensaje. Permite que un
mensaje se comporte de diferentes formas según el objeto sobre el que actúa. Por ejemplo, en el caso del
operador “+” que puede sumar dos enteros o concatenar dos Strings

Paradigma Funcional: Funciones + Estructura de Datos = Programa.


Se basan en el modelo de composición funcional, donde el resultado de un calculo es la entrada del siguiente, y así
sucesivamente hasta producir el resultado deseado. Se basa en el cálculo Lambda.
Un programa se considera una gran función compuesta de funciones simples que se aplican a los datos de entradas
pasados como parámetros. No se usan variables ni asignación a memoria. Ej. Lisp, ML, Haskell, Perl, Miranda

Componentes:
• Conjunto de objetos: miembros del dominio y rango de las funciones
• Funciones primitivas: funciones predefinidas que permiten la construcción de funciones más complejas. El
exceso puede ir en contra de la simplicidad, sin aumentar la expresividad (capacidad de expresar ideas)
• Formas funcionales: son mecanismos que permiten combinar funciones y/u objetos para construir nuevas
funciones
o Funciones de primer orden: cuyos parámetros y resultados son No-funcionales;
o Funciones de orden superior: que tienen un parámetro o resultado funcional
• Operación de aplicación: mecanismo de control que determina como se llaman las funciones entre sí y como se
aplican los argumentos.
Propiedades
• Semántica de valores: no existen los conceptos de locación de memoria y sentencias de asignación Existen
valores intermedios que son el resultado de cálculos anteriores y las entradas a cálculos subsiguientes.
• Transparencia referencial: una expresión puede ser sustituida por un valor, y ese valor es el resultado de evaluar
dicha expresión (la semántica no se altera). El resultado de una expresión depende únicamente de sus
subexpresiones. Ante la misma entrada, se obtiene siempre la misma salida. No depende de la historia del
programa en ejecución ni del orden de evaluación de las subexpresiones. No tiene efectos colaterales.
• Función como objeto de primera clase: las funciones se pueden tratar como datos, es decir, que pueden pasarse
como parámetros o pueden ser el valor de una expresión
• Currying: proceso de representar una función de múltiples argumentos como una función de un solo argumento.
Es transformar una función para que se puede llamar como una cadena de funciones, cada una con un único
argumento. Ej. tenemos f (x, y), para evaluarla la llamamos f (2,3) por ejemplo. Suponiendo que no conocemos y,
se podría definir g(y) = f (2, y). De esta forma cuando llamamos a g (3) = f (2,3)
• Evaluación perezosa: se evalúan los argumentos solo cuando sea indispensable. Una expresión no se evalúa
hasta que sea necesario conocer su resultado. Se recuerdan los argumentos ya calculados para evitar la
reevaluación. Ej. If (a>0 and 3==4), y a<0, no hace falta evaluar el resto de la expresión porque la misma será
falsa sin importar lo que ocurra con el segundo miembro.

Paradigma Lógico: Lógica + Control + Estructura de Datos = Programa.


Basado en el Cálculo de predicados. Los programas están compuestos por hechos (proposiciones lógicas) y reglas,
sobre los cuales se realizan consultas para obtener conclusiones. Se utilizan variables en los parámetros, pero no
representan direcciones de memoria. Ej. Prolog. Se usa en sistemas expertos y reconocimiento de lenguaje natural
Las cláusulas de Horn son fbf constituidas por disyunciones de fbf con, como máximo, una fbf positiva (b ∨ c ∨ a)
Un programa es una secuencia finita de reglas. Una regla tiene cabeza y cuerpo, siendo la cabeza una proposición, y
el cuerpo una secuencia finita de proposiciones, separadas por comas. Las reglas sin cuerpo son los hechos
(sentencias). Las reglas sin cabeza son consultas (son las preguntas que se le hacen al programa, basándose en las
reglas, los hechos y el método de inferencia).

Diseño de los LP
Un buen diseño de un LP es aquel que cumple con los principios que son requeridos tanto por las aplicaciones, los
dominios de aplicación y los paradigmas, en el momento en el que fue diseñado.
Cuando un diseñador se propone incluir ciertas características para cumplir con un principio en particular, va a estar
dejando de cumplir con otro principio. No se pueden incluir todos los principios en un único LP

Simplicidad.
Se refiere a la cantidad de componentes básicos que lo conforman (y la potencia de estos).
Los lenguajes grandes y complicados pertenecen más al problema que a la solución, porque son más difíciles de
dominar. El trabajo del diseñador es desechar elementos redundantes, propensos a errores, difíciles de leer.
Es difícil pensar en un lenguaje de propósito general, que sirva para distintos dominios de aplicación, sin ir en contra
de este principio, porque estaríamos creando un lenguaje demasiado complicado
La simplicidad está estrechamente relacionada con la legibilidad, que se ve afectada por:
• Número de componentes básicos: cuantos más componentes ofrezca el LP, menor será su simplicidad (de
legibilidad). Un programador puede no conocer los componentes con los que se hizo el código, porque él maneja
otros. Sin embargo, un set pequeño de instrucciones (mucha simplicidad) y de combinaciones (poca
ortogonalidad), conlleva un mayor esfuerzo de codificación, disminuye la facilidad de escritura, pero aumenta la
legibilidad (hay pocas formas de escribir un mismo código o estructura). Se necesita de un equilibrio en la
simplicidad y ortogonalidad.
• Multiplicidad de expresiones: varias formas de codificar una misma operación. Ej. en Java a=a+1; a++; ++a; a+=1.
Uno puede estar acostumbrado a usar a=a+1 y si encontramos escrito a++ nos costará interpretarlo.
• Sobrecarga de operadores: disminuye la simplicidad cuando un operador es utilizado para distintas cosas.

La base de todo es la abstracción. Si el LP no permite definir nuevos procedimientos y estructuras, no estaría


cumpliendo con este principio. Estaríamos generando un LP demasiado grande, si como diseñadores tuviéramos que
pensar y diseñar todas las facilidades que suponemos que necesita el programador.
Lo ideal sería que el LP brinde un pequeño conjunto de tipos primitivos y compuesto de datos y darle al programador
la posibilidad de crear nuevos tipos de datos, con las operaciones adecuadas para esos tipos.
◦Pascal: tiene demasiada simplicidad. Es fácil de leer. No es posible la sobrecarga de operadores definidos. Un
usuario no puede definir operaciones que posean como nombre palabras claves o reservadas
◦Ada: No es simple de dominar debido a su amplitud de capacidades. Tiene muchos componentes básicos Permite
sobrecarga de subprogramas y de operadores.

Expresividad.
Es la facilidad con la que un LP le permite al programador expresar procesos y estructuras complejas, lo más
natural posible en el dominio de aplicación.
Un LP expresivo debe incorporar notaciones que sean consistentes con las que se usan en el dominio de aplicación
para el que ha sido diseñado.
"Si diseñas un LP con un dominio de aplicación relacionado con las matemáticas, tenes que usar sintaxis y semántica
acorde; un signo '+' tiene significar una suma entre operandos numéricos".
La expresividad puede entrar en conflicto con la simplicidad: LISP, Prolog y Algol68 son LP sumamente expresivos,
pero no simples. Permiten expresar las ideas del programador de manera versátil, pero requieren mayor
concentración al programar, por que resultan muy simples y tendientes a errores).

Seguridad.
Propiedad que desalienta la posibilidad de escribir programas con errores.
El diseño de un LP no debe proporcionar características que posibiliten la escritura de programas defectuosos.
Las características que ayudan a cumplir con la seguridad, están contrapuestas con las características que facilitan la
expresividad y flexibilidad. Un LP con mucha seguridad, obliga al programador a especificar tantas cosas como sea
posible (afecta a la expresividad). En un LP seguro, no se podría utilizar variables que trabajen con cualquier tipo de
datos, como requiere LISP (afecta la flexibilidad).
La definición de tipos de datos y el chequeo de tipos son características que aumentan la seguridad. Por el contrario,
el “GOTO”, el mal manejo de Alias, el uso de punteros, variables globales y el pasaje de parámetros por referencia
van en detrimento con la seguridad.
◦ Java: Es seguro. Impide que sus aplicaciones puedan acceder a zonas delicadas de memoria o del sistema
◦ Cobol: No es seguro debido a que no posee variables locales. Solo utiliza variables globales, lo cual permite a una
subrutina cualquiera, acceder y alterar variables que no le corresponde.
◦ Pascal: Permite el uso de alias. A pesar del chequeo estático pueden surgir errores en tiempo de ejecución.
◦ Ada: Posee tipado fuerte y chequeos en tiempo de ejecución. Las funciones reducen los posibles efectos
colaterales, pues no pueden tener parámetros in-out. (estrechamente relacionado con la confiabilidad)

Regularidad.
Refleja la adecuada combinación (integración) de los conceptos que caracterizan al LP.
Una mayor regularidad implica pocas restricciones innecesarias ni interacciones raras entre dichos conceptos.
Para entender esto mejor, la podemos dividir en tres conceptos:
• Generalidad: se logra al eliminar casos especiales en los constructores, y combinando los constructores similares
en uno más general y abarcatvio. Evitar especificidades
Ejemplos de no generalidad:
o Pascal no permite expresiones en las constantes. No tiene arreglos de longitud variable.
o C no permite funciones anidadas. Con el operador de igualdad == no se pueden comparar dos estructuras o
arreglos, sino que ha de hacerse elemento a elemento (solo funciona con tipos escalares).
Ejemplos de generalidad:
o C y Ada tienen arreglos de longitud variable.
o Haskell permite extender operadores predefinidos o que el usuario cree los suyos.
o Ada permite que las constantes puedan incluso ser cantidades dinámicas.
o En Pascal las funciones y procedimientos se pueden anidar.
o C permite parámetros, variables y valores devueltos de procedimientos (puntero o referencia a función).

• Ortogonalidad: es la capacidad de combinar estructuras y tipos de datos para formar estructuras más complejas.
Un LP ortogonal es más fácil de aprender porque hay menos excepciones y casos especiales que recordar (ligado
a la simplicidad).
Ej. un array puede admitir cualquier tipo de datos (int, char) o admitir un solo tipo, esto último es una excepción.
Un LP muy ortogonal permite combinar las estructuras de manera tal que se transforman en estructuras muy
complejas, por ende, difíciles de leer, de escribir y de entender. Un LP con poca ortogonalidad presenta
demasiadas excepciones y hace más compleja la legibilidad porque forman estructuras menos naturales.
o Pascal: sus BEGIN y END violan la ortogonalidad ya que se usan tanto para encerrar sentencias compuestas
como para delimitar bloques de código. Dificulta la legibilidad.
o Ada: No es altamente ortogonal. Los parámetros pueden ser de entrada, salida o de E/S, pero en los
subprogramas no pueden utilizarse parámetros de salida o de E/S.
o C: Los registros pueden ser devueltos por una función, pero los vectores no. Los parámetros son pasados por
valor, a menos que sean vectores en cuyo caso se pasan por referencia.

• Uniformidad: implica que, a construcciones semánticas similares, deben corresponderse estructuras sintácticas
similares (y viceversa con cosas distintas). Las no uniformidades ocurren en contextos particulares por lo que
pueden verse como no ortogonalidades (excepciones).
Ej. en C++ podemos definir FUNCIONES y CLASES: en las clases es obligatorio el uso de un punto y coma al
finalizar la definición; sin embargo, en las funciones está prohibido el punto y coma al finalizar la definición.

La generalidad y la ortogonalidad hacen mucho mas complejo un LP; como diseñadores hay que tener cuidado con la
incorporación de características.

Robustez.
Habilidad del LP para tratar eventos no deseados durante la ejecución, haciendo que el sistema sea predecible.
Se ve afectada principalmente por el sistema de gestión de excepciones. Un LP robusto debe tener la capacidad para
hacer frente a eventos no deseados durante la ejecución (overflows, entradas no validas, hardware no encontrado,
etc.). El LP debe permitir que estos eventos sean atrapados y que se programe una respuesta adecuada para
responder a ellos. Esto permite que se pueda predecir el comportamiento del sistema, aun en situaciones anómalas.

Flexibilidad.
Facilidad que permite que un mismo programa pueda realizar variaciones sobre una misma entidad sin necesidad
de modificaciones (en el código).
Los tiempos de ligadura en un lenguaje imperativo son una característica que afectan directamente la flexibilidad. Un
LP que permita ligadura dinámica (en ejecución) es más flexible que uno que no.
La flexibilidad afecta a la eficiencia y la seguridad (mientras más flexible, es probable menos eficiente y seguro).
• Ada fue diseñado para ofrecer ejecución eficiente y a la vez flexible: permiten elegir tiempos de ligadura.
• LISP es muy flexible: al estar basado enlistas, los propios programas se puedan modificar a sí mismos. Se pueden
crear fácilmente estructuras de datos adaptadas al tipo de problema del que se trate.
• Java: se pueden tener punteros a objetos de un tipo específico y punteros a objetos de cualquier tipo.

Eficiencia.
Refleja la relación entre los recursos utilizados y los objetivos conseguidos.
Louden expande un poco más esto, discriminando distintas acepciones del concepto de eficiencia, a saber:
• Costo beneficio: usar menos recursos, para lograr el mismo objetivo (o al revés)
• Código eficiente: optimizado. Relacionado con el tamaño del código, tipos de ligaduras (una variable con tipo
estático permite generar código que las asigna y referencia de manera eficiente).
• Eficiencia de traducción: relacionada con la rapidez del proceso de traducción y el tamaño del traductor. Esto se
ve afectado por cómo están establecidas las reglas del lenguaje (si son muy complejas, va a ser más compleja la
traducción). También está relacionado con la verificación de errores, ya que verificar la existencia de un error en
tiempo de traducción puede hacer ineficiente al traductor
• Eficiencia de implementación: relacionada con la eficiencia con la que se puede escribir un programa
determinado en determinado domino de aplicación. Se ve afectado por la complejidad inherente a un LP (por su
gran cantidad de componentes, o sus grandes requerimientos de HW). Ej. no es lo mismo usar una lista enlazada
que una estructura secuencial (la primera es de acceso más lento, pero permite mayor flexibilidad en memoria).
• Eficiencia de programación: ¿Qué tan rápido y fácil se pueden escribir programas en el LP? Tiene que ver con la
capacidad de expresión del LP: ¿qué tan fácil es expresar procesos y estructuras complejas? Está relacionado con
la potencia y la generalidad de los mecanismos de abstracción

◦ Pascal: Se puede implementar de manera eficiente en hardware. Durante la traducción, es posible la verificación
estática de tipos para casi todas las operaciones, de esta manera se requiere poca verificación dinámica. La
traducción es originariamente a código de maquina ejecutable.
◦ Ada: soporta asignación estática de tipos, lo cual permite generar código con asignación de memoria y referencia a
las variables en forma eficiente
Reusabilidad.
Tiene que ver con usar partes de SW en distintos proyectos (estructuras independientes de un programa). No se
puede pensar en un nuevo LP que no cumpla con este concepto.
Está relacionado con los conceptos de abstracción de datos y procesos, tipos genéricos, polimorfismo. Se ve afectada
por las reglas de alcance (variables globales) y la estructura del programa (monolítica, bloques anidados, etc.)

Extensibilidad.
Hace referencia a la posibilidad de hacer cambios respecto de las especificaciones iniciales del LP. Es decir, debe
facilitar mecanismos para que el programador pueda añadir nuevas funciones. La idea es que el diseñador elija un
conjunto de características que van a conformar el núcleo del lenguaje a través de bibliotecas estándares, y cuales
no se van a especificar, dejando que el programador que use el LP las defina.
El diseño del LP debe permitir definir nuevos operadores, tipos de datos, palabras claves y constructores
No se puede pensar en un lenguaje simple sin el concepto de extensibilidad
• En Haskell, el programador puede definir sus propias estructuras de control.
• En POO, gracias a la modularidad y a la herencia una aplicación puede ser fácilmente extensible
• En Ada es extensible, gracias a la genericidad y el uso de bibliotecas definidas por el usuario

Portabilidad.
Es la capacidad crear programas que se compilen y ejecuten en distintas maquinas, sin necesidad de hacer
cambios en el código fuente. La definición del LP debe tener un alto grado de independencia del HW (usar tipos de
datos que no involucren detalles de asignación de memoria o de la arquitectura de la máquina) y el grado de
estandarización.

Relaciones entre los distintos principios de diseño.


• Si se incrementa la eficiencia del programa, se debe adaptar el programa a los recursos hardware disponibles, y
esto supone una degradación de la portabilidad, extensibilidad y reusabilidad.
• La seguridad puede significar la introducción de código extra que sea capaz de cazar los fallos si se producen, y
reconducir su efecto, lo cual se podría decir que afecta la eficiencia… más código = más recursos.
• La simplicidad se relaciona directamente con la ortogonalidad.
• La seguridad ha llevado a los diseñadores de LP a introducir tipos, verificación de tipos y declaración de variables.
Esto afecta a la expresividad (y flexibilidad), al agregar al programador la costosa tarea de tener que especificar
tantas cosas como sea posible, logrando así la seguridad.
• La sobresimplicidad puede hacer que un LP sea difícil de usar, y que sea carente de expresividad, legibilidad,
seguridad, o sujeto a muchas restricciones (excepciones).

Estandarización
Un estándar es una norma, un conjunto de reglas que se deben cumplir para escribir un programa valido en un LP.
Si diseñamos un LP, y le decimos a dos personas que escriban un traductor, es probable que las interpretaciones de
estas personas sean diferentes, por lo tanto, la ejecución de un programa escrito en este nuevo lenguaje, arroje
resultados diferentes o compile con errores. Esto pasa cuando hay varias interpretaciones de un mismo LP
Para resolver este problema, la mayoría de los LP tiene definiciones estándares, entonces todas las
implementaciones deben adaptarse a dicho estándar. Hay dos tipos de estándares:
• Estándar patentado: es la definición elaborada por la empresa que desarrollo el LP y del cual es propietario.
• Estándar por consenso: elaborado por distintas organizaciones en base a un acuerdo por lograr uniformidad
entre varias implementaciones del LP. Sirven para decir “esto plasmado acá, está probado que funciona y está
documentado”. El organismo normativo (IRAM, ISO, IEEE) organiza un grupo de trabajo de voluntarios para
desarrollar esa norma, cuando llegan a un acuerdo, se somete a votación por parte de un bloque más grande de
interesados. Los desacuerdos se resuelven y se produce el estándar.

Para definir un estándar hay que tener en cuenta tres consideraciones importantes:
• Oportunidad: ¿Cuándo estandarizar un LP? Lo ideal sería en un tiempo no muy corto (ADA cuando se hizo el
estándar no estaba claro si el lenguaje iba a funcionar), como para que se haya alcanzado suficiente experiencia de
uso, pero tampoco demasiado tarde, para no alentar muchas implementaciones incompatibles (caso FORTRAN).
• Conformidad: Un Programa es conforme, si utiliza solo características definidas por el estándar. Un compilador es
conformable, cuando toma un programa conforme y produce un programa ejecutable que genera la salida correcta.
Si un compilador agrega características, cualquier programa que use esas características, sería NO conformado
• Obsolescencia: Los estándares se revisan aproximadamente cada 5 años, ya sea para renovar la estandarización
(agregar, quitar o modificar capacidades) o para descartarse. El problema que surge es qué hacer con los programas
existentes escritos por el estándar anterior. Por esta razón, los estándares requieren compatibilidad hacia atrás (el
nuevo estándar debe incluir versiones más antiguas del LP). Cuando una característica va a ser descartada en la
próxima versión del estándar, se la llama obsolescente. Esto advierte a los usuarios que la característica todavía está
disponible, pero va a ser desechada entre los 5 y 10 años. Una característica que es desaprobada se puede volver
obsolescente en el próximo estándar por lo tanto puede ser descartada después de 2 versiones.

Implementación de LP.
Debido a que las computadoras solo pueden interpretar y ejecutar código máquina, es necesario contar con
programas que permitan traducir programas escritos en lenguajes de programación (alto nivel) a lenguaje máquina
• Los diseñadores se encargan de definir las estructuras legales de los programas y de especificar reglas de alcance,
precedencia, asociatividad, Etc., NO especifican como debe hacerse la implementación.
• Los implementadores se encargan de desarrollar traductores (intérpretes o compiladores) para poder realizar la
traducción, respetando las especificaciones de los diseñadores. Determina el momento en que se van a establecer
las ligaduras (asociaciones de entidades con sus respectivos atributos, por ejemplo, variable y tipo de dato).

Si la mayoría de las ligaduras se produce de manera estática (antes de la ejecución del programa) se va a optar por
usar un compilador. En cambio, si la mayoría de las ligaduras se producen en ejecución, se va a usar un intérprete.
A partir del surgimiento de JAVA, surgen los traductores híbridos

Intérprete
Es un programa que simula una máquina virtual que ejecuta las sentencias de un programa escrito en alto nivel.
Repite la secuencia: obtener la siguiente sentencia a ejecutar, determinar que acciones se van ejecutar, y ejecutar
dichas acciones. Traduce y ejecuta cada instrucción en el momento en que la encuentra. Para cada sentencia, existe
un subprograma que ejecuta acciones, y para ejecutar dichas acciones, es necesario invocar al subprograma.
Ejemplos: PROLOG, LISP, PHP y JavaScript.
• Ventajas: fácil depuración (identificar y corregir errores), ya que la ejecución puede interrumpirse fácilmente,
modificarse y ser ejecutado nuevamente. Mayor flexibilidad.
• Desventajas: Hay que interpretar cada vez que se quiere ejecutar. Es más lento y consume más y recursos (el
intérprete ocupa tiempo y memoria). Los LP interpretados usan ligadura dinámica, entonces deben guardar la tabla
de símbolos en ejecución para hacer controles (lo cual ocupa espacio).

El PF es sometido a un traductor, que toma el código fuente, lo analiza y lo transforma a Representación Interna
(por ejemplo, árbol sintáctico). En el proceso de traducción también se genera la Tabla de Símbolos, que es una
estructura de datos que tiene toda la información relativa a los identificadores que se usan dentro del PF. Esta tabla
se crea durante la traducción y se mantiene en memoria todo el tiempo que dure la ejecución del programa.
A partir de la RI y los datos de entrada, el Evaluador de RI va a realizar las acciones indicadas para obtener los
resultados, es decir, se van a invocar los subprogramas que ejecuten las acciones necesarias por el PF.
En el proceso de evaluación pueden aparece diferentes tipos de errores (desbordamiento de pila, divisiones por
cero) que el interprete debe contemplar y determinar como va a solucionarlos (tratarlos)
Compilador
Traduce un programa escrito en lenguaje de alto nivel a uno equivalente en lenguaje máquina (Programa Objeto),
pero no lo ejecuta, sino que lo guarda en disco. También realiza optimizaciones sobre el código, como eliminar
variables y constantes no utilizadas, o ciclos inútiles, de manera que ocupe menos espacio y sea más rápido de
ejecutar. Ejemplos: Pascal, C, C++, Ada, COBOL.
El proceso de compilación se realiza por varios programas, no sólo el compilador. El compilador no produce un
programa directamente ejecutable, sino que genera un Programa Objeto. Los distintos PO que se generan tienen
información relativa al código de maquina correspondiente al programa y a la tabla de símbolos que va almacenando
la información sobre variables y tipos usados en el PF.
¿Por qué no se genera directamente un ejecutable? Porque de esta forma se permite la compilación separada.
Varios programadores puedan desarrollar diferentes partes del programa que pueden compilarse y probarse de
forma independiente. Una vez que todas las partes están depuradas, el Enlazador toma todos los PO y resuelve las
referencias externas o cruzadas (objetos o identificadores que pueden ser utilizados o estar declarados en otros
archivos, en otros PO o en Librerías), produciendo un Programa Ejecutable o Código Reubicable, es decir un código
que puede cargarse en memoria y ejecutarse. El Cargador carga el ejecutable en memoria, donde el sistema
operativo le indique y asigna los registros base a cada una de sus posiciones, de manera que las direcciones relativas
se resuelvan correctamente.

Fase de análisis (Léxico, Sintáctico, Semántico)


Divide el PF en componentes y produce una representación intermedia (código intermedio).
Si detecta que el PF está mal formado sintácticamente, o que no tiene una semántica consistente, entonces debe
proporcionar mensajes informativos, para que el usuario pueda corregirlos
Recolecta información sobre el PF y la almacena en la tabla de símbolos, la cual pasa con la representación
intermedia a la fase de síntesis

Fase de síntesis (Optimización, Generación de Código)


Construye el programa destino a partir de la representación intermedia y de la información de la tabla de símbolos.
Traduce el código intermedio en el programa destino.

• Análisis léxico: descompone el PF en componentes léxicos (lexemas y tokens) y se empieza a completar la Tabla de
Símbolos, se determina de que tipo es cada elemento (número, identificador, delimitador, operador, Etc.)
adjuntando una marca de tipo. Por ejemplo: posición = inicial + velocidad * 60 → (id,1) (=) (id,2) (+) (id,3) (*) (60)

1. posición es un lexema que se asigna a un token (id, 1), en donde id es un símbolo abstracto que representa la
palabra identificador y 1 apunta a la entrada en la tabla de símbolos para posición. La entrada en la tabla de símbolos
para un identificador contiene información acerca de éste.
2. El símbolo de asignación = es un lexema que se asigna al token (=). Este token no necesita un valor-atributo, por
eso se omite el segundo componente, pero se podría haber utilizado cualquier símbolo abstracto como asignar para
el nombre
7. 60 es un lexema que se asigna al token (60). También se podría formar un token (número, 4), en donde 4 apunta a
la tabla de símbolos para la representación interna del entero 60
También se suele hacer una conversión a una representación interna de los elementos (los números se convierten a
una forma binaria interna con punto fijo o flotante) y se eliminan los comentarios y todos aquellos elementos que no
sean relevantes para el programa (palabras pregonadas, los espacios en blanco, Etc.).
Para realizar esta tarea se usa un Autómata Finito, un modelo computacional conformado por un alfabeto, un
conjunto de estados y un conjunto de transiciones entre estados. Su funcionamiento se basa en una función de
transición, a partir de un estado inicial y una cadena de entrada, se desplaza de un estado a otro según va leyendo la
cadena, para finalmente detenerse en un estado final o de aceptación, que representa la salida.

• Análisis sintáctico: Partiendo de lo recibido del analizador léxico, identifica estructuras más complejas (enunciados,
expresiones, subprogramas, Etc.), agrupando los componentes léxicos, y así construir un árbol de análisis sintáctico,
que define la estructura jerárquica del programa. Cada nodo interior del árbol representa una operación, y los hijos
del nodo representan los argumentos de dicha operación
El analizador sintáctico trabaja en forma conjunta con el analizador semántico y se comunican a través de una pila. El
analizador sintáctico vuelca en la pila los datos a analizar, y el semántico los recupera y procesa. El An. Sintáctico
trabaja con las reglas sintácticas del lenguaje (GLC), y el An. Semántico trabaja con la Gramática de Atributos (que
trabaja con aspectos sensibles al contexto)
El análisis se realiza con un AP (tiene memoria), ya que la gramática suele ser independiente al contexto

• Análisis semántico: Utiliza el árbol sintáctico y la información de la tabla de símbolos para comprobar la consistencia
de la semántica del PF con la definición del lenguaje (verifica si lo que está escrito está semánticamente correcto).
Reúne información de tipos, realiza la verificación y busca errores semánticos. Verifica si cada operador tiene
operandos permitidos por la especificación del LP, si detecta error debe generar un mensaje y tratarlo de alguna
manera que permita continuar con el análisis. Mantiene actualizada la tabla de símbolos y anexa información
adicional útil para la ejecución (marca de tipo, ambiente de referenciamiento)
Toda información implícita debe quedar explicitada: caso FORTRAN, el programador no declara explícitamente el
tipo de una variable, pero el compilador asume que es “entera” si empieza con alguna letra entre “i” y “n”.
También la especificación del lenguaje puede permitir coerciones. Si el operador se aplica a un número de punto
flotante y a un entero, el compilador puede convertir el entero a punto flotante.
También se encarga del procesamiento de Macros. Las macros son fragmentos de código, como los subprogramas,
pero en vez de traducirse y llamarse durante la ejecución, el cuerpo de la macro se sustituye en cada llamada en el
momento de la traducción (compilación)
Se obtiene como resultado código objeto intermedio (representación intermedia).

• Optimización (opcional): consiste en mejorar el código intermedio, de modo que resulte un código más rápido de
ejecutar o que ocupe menos lugar en memoria. Esto se hace mediante técnicas como sustitución de invariante,
factorización, reemplazo de rutinas repetidas

• Generación del código: una vez optimizado, se transforman los enunciados a lenguaje ensamblador o máquina, y se
obtiene finalmente el programa objeto. Es decir, se encarga de generar las instrucciones en código objeto.

Ventajas: la ejecución es más rápida y la decodificación se realiza una sola vez. Los errores se encuentran antes que
el programa se ejecute
Desventajas: es grande y complejo.

Tabla de Símbolos
Son estructuras de datos que guardan información acerca de las construcciones de un PF. La información se
recolecta mediante las fases de análisis, y las fases de síntesis la utilizan para generar el código objeto. Es la
estructura más importante de todo el traductor.
Tiene una entrada por cada lexema encontrado en el PF. Cada entrada contiene información acerca del lexema,
como su nombre y tipo, ambiente de referenciamiento.

Híbridos.
Traducen los programas escritos en alto nivel a un lenguaje intermedio, que permita una fácil interpretación. Es
decir, traduce un programa fuente en un programa objeto, que sirve como entrada a un intérprete. Son más rápidos
que la interpretación pura, ya que las sentencias están previamente decodificadas. Ej.: Java, Perl, C#.
El proceso de una implementación hibrida es el siguiente: al recibir el
programa fuente realiza el análisis léxico, donde se obtienen los
componentes léxicos. Luego se hace el análisis sintáctico que toma los
componentes léxicos y genera el árbol de análisis sintáctico. Después
interviene el generador de código intermedio, cuya salida es el código
intermedio que se le pasa al interprete, que también recibe los datos de
entrada para producir los resultados.

Implementación de unidades
Se basa en el llamado y retorno a una unidad.
• La llamada a una unidad incluye: métodos de pasaje de parámetros, asignación de memoria para variables locales,
guardar el estado de ejecución de las llamadas a subprogramas, la transferencia de control y la coordinación para el
retorno, y garantizar el acceso a variables no locales.
• El retorno incluye: los valores de retorno en el caso de que el pasaje de parámetros sea out-mode o in-out-mode, la
desasignación de variables locales, restaurar el estado de ejecución, y devolver el control a la unidad llamadora.

Gestión de Memoria
Para poder ejecutar el programa, es necesario ubicarlo en un bloque de memoria. Al activarse (ejecutarse) una
unidad, se le asigna un espacio físico de memoria que contiene:
• Segmento de código, formado por las instrucciones maquina a ejecutar.
• Memoria estática, donde se almacenan constantes y variables locales cuyo valor se
mantiene entre llamadas.
• Pila, que mantiene las instancias de los Registros de Activación de los procedimientos que
han sido llamados. La pila crece cada vez que se hace una llamada a un procedimiento y decrece
al momento del retorno.
• Heap, que guarda los datos cuyo tamaño varían en tiempo de ejecución

El área intermedia entre la pila y el heap permite a ambos crecer y usar más espacio del
asignado.

Las técnicas para el manejo de memoria durante la ejecución, varían dependiendo del LP y el compilador.

Registro de Activación (RA)


Es una pila que contiene toda la información necesaria para que la unidad pueda ser ejecutada. Se crea al momento
en que empieza a ejecutarse y se destruye cuando termina (en los LP basados en pila o dinámicos en heap).

• Dirección de retorno (DR): contiene la dirección de la última instrucción ejecutada en la unidad llamadora.
Cuando se crea o se destruye un RA, el CIP modifica su contenido en función de lo que contenga este puntero en
el RA que esté activo en ese momento.
• Enlace Dinámico (ED): apunta a la base del RA de la unidad llamadora. Permite seguir la secuencia de flujo de
llamado de los subprogramas establecidas en tiempo de ejecución. Nos permite armar el ambiente de
referenciamiento de la unidad y actualizar el CEP ante cualquier cambio en el mismo.
• Enlace Estático (EE): puntero a la base del padre estático de la unidad. Permite tener la estructura de los
subprogramas conformadas en tiempo de compilación.
• Parámetros Formales (PF): son inicializados por la unidad llamadora siguiendo la política de algún
determinado método de pasaje de parámetros (por valor, referencia, nombre, Etc.)
• Variables locales (VL): en esta área se almacenan todas las variables propias del subprograma.
• Resultado de la Función (RF): en donde se almacena el valor de retorno. Es con lo que la unidad llamadora altera
el ambiente de la unidad llamada
Además, existen para todos los RA dos punteros más, los cuales comparten:
▪ CIP (Counter Instructión Point): contiene la dirección de la última instrucción ejecutada en la unidad
llamadora.
▪ CEP (Counter Enviroment Point): contiene la dirección a la base del RA que está activo en ese momento.

Lenguajes Estáticos: Fortran


• La asignación de memoria se calcula estáticamente, en tiempo de compilación, entonces el espacio requerido se
conoce de antemano
• Cada segmento de código queda ligado a una única instancia de RA, por lo que cantidad de RA es fija (uno por cada
segmento de código). Por esto que no permiten recursividad (no permiten ligar un mismo segmento a varios RA)
• Son lenguajes sensibles a la historia (mantienen los datos): dos ejecuciones con las mismas entradas para variables
globales y parámetros pueden producir resultados diferentes. El tiempo de vida de las variables es el tiempo de
ejecución de la unidad.
• En este tipo de esquema el RA está formado por la dirección de retorno, los parámetros, las variables locales y el
valor de retorno (o Valor de función) en caso que sea una función.
• Ventajas: Esquema sencillo de implementar. Simple.
• Desventajas: No permite modelar recursividad; mantiene ocupada la memoria con la totalidad de las unidades en
todo momento, aun cuando las unidades no están activas. La carga al inicio y las descarga una vez que finaliza el
programa.

La semántica de la llamada establece: La semántica del retorno establece:


• Guardar el estado de ejecución de la unidad llamadora • Mover los valores de los parámetros formales a los
• Pasar los parámetros parámetros reales (si la semántica es out/in out)
• Pasar la dirección de retorno a la unidad llamada • Si es una función, colocar el valor de la función a un
• Transferir el control a la unidad llamada espacio accesible por la unidad llamadora
• Restaurar el estado de ejecución de la unidad
llamadora
• Transferir el control a la unidad llamadora

Lenguajes basados en pilas: Algol


• La cantidad de memoria requerida no puede calcularse estáticamente. Se calcula cuando alcanza la ejecución de la
unidad. Cuando una unidad se activa, se crea una instancia de un RA.
• Cuando se termina de ejecutar una unidad, se destruye la instancia del RA y se libera espacio en memoria.
• Si empieza una nueva ejecución de la misma unidad, antes de que termine, se crea otra instancia del RA totalmente
independiente de la anterior, es decir, que SI permite recursividad (en el estático si empieza otra ejecución antes
que termine la primera, se usa el mismo RA) y con un nuevo ambiente de referenciamiento local. NO es sensible a la
historia.
• La estructura del RA es estática, es decir siempre va a estar formada por: dirección de retorno, enlace dinámico,
enlace estático, parámetros, variables locales y el valor de función. Las instancias de los RA son dinámicas, es decir,
los valores que se van a almacenar en cada una de las instancias

Lenguajes dinámicos: ADA


• Hacen uso impredecible de la memoria. Ya que al permitir un tamaño dinámico del RA no se puede realizar la
reserva de memoria exacta cuando la unidad se carga en memoria.
• Se utiliza una estructura del tipo heap que es un área de la memoria de tamaño variable, que mantiene estos datos
dinámicamente. El tamaño de los datos almacenados acá puede variar (arboles, listas, cadenas de caracteres de
longitud variable)
• La estructura del RA es la misma que la basada en pila.
• Se reserva, en compilación, espacio para lo que se sabe que se va a usar y que se puede determinar cuánto espacio
ocupa y el resto en ejecución.

¿Cuál es la diferencia entre los esquemas de los lenguajes basados en pila y los dinámicos?
La diferencia más importante es que en los LP basados en pila, el cálculo de memoria necesaria para almacenar las
variables utilizadas se realiza en el momento en que la unidad alcanza la ejecución, mientras que en los LP dinámicos
se realiza a medida que se necesita durante la ejecución. Es decir, no existe una reserva de memoria previa, sino que
se va alocando memoria arbitrariamente. Generalmente esto se realiza utilizando punteros.
UNIDAD 2: Sintaxis y Semántica
Introducción
Los lenguajes de programación son un conjunto de símbolos y de reglas para combinarlos, que se usan para
expresar algoritmos. Al estudio de los LP, los podemos dividir en sintaxis y semántica.

La Sintaxis especifica la FORMA o ESTRUCTURA de todas las construcciones permitidas por el LP (expresiones,
sentencias, unidades de programa). Son reglas que especifican si una sentencia está bien formada o no, por lo tanto,
definen la forma de los programas gramaticalmente válidos.
Está definida por:
● Reglas léxicas: especifican el conjunto de caracteres que constituyen el alfabeto del lenguaje y la forma en que
éstos se combinan para formar palabras válidas
● Reglas sintácticas: especifican la forma en la que se constituyen las estructuras válidas de un programa (a partir
de la combinación de elementos léxicos)

La Semántica especifica el SIGNIFICADO que tienen las diversas estructuras sintácticas que conforman al programa
(expresiones, sentencias, unidades de programa)
Tiene que ver con cómo se espera que un programa bien formado se comporte cuando se ejecuta.

TERMINOLOGÍA
• Lenguaje: conjunto de cadenas de caracteres válidos, pertenecientes a algún alfabeto (ASCII, Unicode, Español,...)
• Sentencia: cada una de esas cadenas.
• Lexemas: unidades sintácticas de más bajo nivel (+, *, else, begin, contador, suma, -palabras reservadas-...)
• Token: categoría de lexemas (identificador, simbolo_de_operador...). Los lexemas se agrupan en tokens.

Criterios Generales de Sintaxis


El propósito primordial de la sintaxis es proveer una notación que facilite la comunicación entre el programador y
traductor del LP.

Legibilidad: La estructura subyacente del algoritmo y los datos que el programa representa deben quedar en claro al
leer el programa. Se dice que un programa legible es autodocumentable, o sea, es entendible sin necesidad de leer
la documentación. Las grandes diferencias sintácticas subyacentes deben reflejar diferencias semánticas.

Facilidad de Escritura: Especifica la facilidad con que un lenguaje puede usarse para crear programas para un
dominio de problema elegido. La mayoría de las características del lenguaje que afectan la legibilidad también
afectan la facilidad de escritura, debido a que son criterios que se contraponen. Más fácil de escribir y más difícil de
leer y viceversa. Las estructuras sintácticas concisas, claras y regulares mejoran la facilidad de escritura.
• Concisas: que no sean muy extensas.
• Claras: entendibles, se escriben más naturalmente.
• Regulares: que no presenten variantes o excepciones dependiendo del contexto. Ejemplo, el IF de Java
permite obviar las llaves en caso de que haya una sola sentencia dentro de la estructura (no regular).
Las conversiones sintácticas implícitas permiten hacer declaraciones implícitas (dejar sin especificar declaraciones y
operaciones), lo que hace a los programas más cortos y fáciles de escribir, pero dificultan su lectura.
Ciertas redundancias son útiles en la sintaxis porque facilita la lectura del programa y la búsqueda de errores en la
traducción, aunque dificulta la escritura. Ejemplo: declaración implícita en Fortran, si pones la variable pepe y
después escribís pepo por equivocación, considera un nuevo nombre de variable. En cambio, en Pascal que requiere
declaraciones explícitas, en el caso anterior detecta el nombre de variable mal escrito como un error.
• Una sintaxis es redundante si comunica el mismo elemento de información en más de una forma.
◦ Ventaja: facilita la lectura del programa y permite buscar errores durante la traducción. Ejemplo, Pascal (debo
declarar) int pepe; int pepo;
◦ Desventaja: hay que escribir mas (--Facilidad_Escritura).

Facilidad de verificación: está relacionada con la legibilidad y la facilidad de escritura. No es difícil entender cada
enunciado, lo difícil es el proceso de crear programas correctos. Lo importante es darnos cuenta de los errores
lógicos. En cuanto a los errores sintácticos se ocupa el compilador. Los entornos de programación facilitan la
verificación de errores.
Facilidad de traducción: La clave para una traducción fácil es la regularidad de la escritura (menos excepciones a
verificar). La traducción se dificulta conforme aumenta el número de construcciones sintácticas. Ej. Lisp no es muy
legible ni fácil de escribir, pero resulta muy fácil de traducir, a causa de sus pocas construcciones (regularidad). Por
otro lado, Cobol es difícil de traducir por el gran número de formas de enunciados y declaraciones que se permiten.
add 1 to A
move to A A++ → más fácil de traducir.
compute A = A + 1

Carencia de ambigüedad: la ambigüedad se da al tener una construcción con 2 o más interpretaciones. La carencia
de ambigüedad ayuda a la legibilidad y a la facilidad de escritura y simplifica el proceso de detección de errores
debido a que las construcciones difieren a nivel sintáctico unas de otras. Cuando hay ambigüedad se soluciona con el
uso adecuado de delimitadores o convenciones (el end cierra el primer begin encontrado hacia arriba).

Elementos sintácticos
Conjunto de caracteres: son los símbolos permitidos por el LP. La elección de este conjunto es lo primero que se
hace al proyectar una sintaxis. Está constituido por: letras (mayúsculas /minúsculas), dígitos (del 0 al 9) y caracteres
especiales: (+, -, *, /, etc. Se eligen para formar los operadores, delimitadores, Etc.)

Delimitadores: elemento sintáctico que se usa para señalar el principio o el final de alguna construcción. Algunos se
usan en pares (las llaves) y otros solos (el “;”). Se usan para:
• Eliminar ambigüedades en estructuras de apariencia semejantes a nivel sintaxis concreta.
• Simplificar el proceso de traducción.
• Mejorar la comprensión y legibilidad.
• Alterar el orden de evaluación de los operadores modificando las reglas de precedencia y asociatividad.

Identificadores: son cadenas de letras y dígitos. Las variaciones en el lenguaje se deben principalmente a la inclusión
de caracteres especiales (. o -) para mejorar la legibilidad y en restricciones de longitud. Los identificadores de poca
longitud, no son muy nemotécnicos y restringen la legibilidad del programa. Ejemplo.

Símbolos de operadores: muchos LP usan el + y el – para representar las operaciones aritméticas básicas o alguna
combinación de ellos, en las demás no hay uniformidad. (Ej. EQ. y ** de Fortran, para igualdad y exponenciación)

Palabras claves y reservadas: estas palabras tienen un tratamiento particular dentro del LP. Las palabras reservadas
son aquellas que el LP se reserva para su uso y no pueden ser usadas como nombre de identificadores. Las palabras
claves si pueden ser usadas como identificadores. Permitir el uso de palabras claves hace más complejo el proceso
de traducción debido a que se debe considerar el contexto en el que se define la palabra. Ej. en Fortran, DO e IF no
son palabras reservadas, por lo que pueden ser nombres de variables, dependiendo el contexto.

Palabras pregonadas: son palabras opcionales que se insertan en los enunciados para mejorar la legibilidad. Ej. en
Cobol el GOTO (TO es opcional); también THEN y PERFORM en Cobol.

Comentarios: la inclusión de comentarios en los programas es importante para su documentación. Permitir


documentación, favorece el mantenimiento.

Espacios en blanco: en algunos LP tienen un papel sintáctico importante, se usan como separadores u operadores y
en otros, no tienen utilidad. Ej. en Snobol 4, la concatenación se representa con un espacio en blanco, que también
se usa como separador entre elementos de un enunciado (ambiguo). En FORTRAN, no son significativos.

Formato de campos libres y fijos: una sintaxis es de campo libre si los enunciados se pueden escribir en cualquier
parte de un renglón sin que importe la posición sobre el renglón. Los campos libres permiten mayor facilidad de
escritura. Una sintaxis de campo fijo utiliza una posición particular dentro del renglón para escribir los enunciados
(común en lenguaje ensambladores). Los campos fijos brindan mayor facilidad de traducción. Ej. En Fortran los 5
primeros caracteres de un renglón, están reservadas para cada rotulo de enunciado.

Expresiones: Son construcciones sintácticas compuestas de operadores y operandos, de cuya evaluación se obtiene
un valor. En Lenguajes Imperativos constituyen las operaciones básicas que modifican el estado de la máquina. Son
los bloques sintácticos básicos de construcción a partir de los cuales se construyen enunciados.

Enunciados: Son las sentencias que permiten realizar operaciones. Se puede hacer que cada estructura sintáctica,
exprese de manera natural las operaciones que intervienen. X=X+1
Sintaxis Abstracta y Concreta
Algunas construcciones en diferentes LP tienen la misma estructura conceptual (subyacente) pero difieren en su
apariencia a nivel léxico.
Cuando dos construcciones difieren sólo a nivel léxico (begin...end vs. {..} y <> vs. !=), podemos decir que tienen la
misma sintaxis abstracta, pero difieren a nivel de sintaxis concreta.
• Sintaxis abstracta: representa la estructura que subyace bajo las construcciones. Ej. una iteración
• Sintaxis concreta: la manera en que se escribe en cada lenguaje. EJ. iteración en C o Pascal.
Conceptualmente la sintaxis concreta puede ser irrelevante, pero puede afectar la legibilidad de los programas. Ej.
No es lo mismo usar un espacio en blanco para denotar una suma, que el operando “+”

Métodos (o notaciones) formales para describir la sintaxis de los LP:


Son un conjunto de reglas (producciones) que especifican la serie de caracteres que forman palabras y
construcciones validas dentro del LP. Estas notaciones son de gran ayuda para usuarios e implementadores porque
brindan un documento con información confiable, concisa y no ambigua (útiles para estandarizar)
El problema/dificultad que surge con las notaciones formales es poder distinguir los elementos del LP descrito, con
los elementos del lenguaje que se usa para hacer la descripción. También, a la complejidad de aprender un nuevo LP,
se le suma el de aprender y comprender la notación formal

Gramática libre de contexto.


Define un conjunto de reglas que definen todas las construcciones validas que puede aceptar un LP. Es una
gramática formal, formada por un conjunto de símbolos No Terminales, un conjunto de Terminales, un Símbolo
Inicial, y un conjunto de reglas de producción, en la que cada regla es de la forma: NT → w, donde w es una cadena
de T y/o NT. El término libre de contexto se refiere al hecho de que el NT puede siempre ser sustituido por w sin
tener en cuenta el contexto en el que ocurra. Un lenguaje formal es libre de contexto si hay una GLC que lo genera.
La notación más frecuentemente utilizada para expresar gramáticas libres de contexto es la BNF.

Derivación.
Es una aplicación repetida de reglas de producción, que empieza por el Símbolo Inicial y termina con una sentencia.
Todas las cadenas de símbolos en la derivación son formas sentenciales. Una sentencia es una forma sentencial que
tiene únicamente símbolos terminales.

BNF.
Es un metalenguaje (lenguaje usado para definir otro) muy utilizado para definir la sintaxis de un LP. Permiten
generar el conjunto de todas las cadenas que pueden constituir un programa en el lenguaje descrito y así decidir si
un programa es válido o no, de acuerdo a dichas cadenas. Las reglas están formadas por: palabras y símbolos
propios del LP (descrito), metasimbolos (símbolos propios de BNF y EBNF, no son parte del LP descrito) y
metavariables (secuencia de símbolos que se usan para describir palabras <>)
Las áreas de sintaxis que no pueden definirse con una gramática BNF son aquellos que implican dependencia
contextual. Por ejemplo, las restricciones “el mismo identificador no puede declararse 2 veces en el mismo bloque”

E(Extended) BNF
Es una extensión de BNF que, si bien no aumenta su poder expresivo, permite generar especificaciones más
reducidas y claras. Aporta mayor legibilidad y facilidad de escritura. Agrega:
• Alternativas de una regla: en vez de escribir varias reglas con la misma parte izquierda, se escribe una sola regla,
separando las alternativas con un | (pipe). Ej. <digito>→ 0|1|2|3|4|5|6|7|8|9
• Corchetes []: lo que encierran es opcional
• Llaves {}: lo que encierran se puede repetir
o +: 1 o más iteraciones
o *: 0 o más iteraciones
Diagramas (grafos) Sintácticos
Son una representación visual de la información descripta en BNF y EBNF, solo que más legibles y fáciles de visualizar
Cada regla está representada por un camino, donde los símbolos Terminales se representan encerrados en círculos y
los No Terminales, con rectángulos.
Una cadena es válida si puede ser generada cruzando un diagrama de sintaxis desde la entrada hasta la salida.

Árbol de Análisis Sintáctico


Permiten la representación gráfica de las derivaciones de una gramática. Para determinar si una cadena representa
un programa sintácticamente valido, se deben usar las reglas gramaticales para construir un Árbol de Análisis
Sintáctico de la cadena. Si la cadena se puede analizar en forma satisfactoria, entonces está en el lenguaje. Podemos
decir que un árbol sintáctico es una representación gráfica del proceso de “parsing” de una sentencia.

Ambigüedad
Una cadena asociada a más de un árbol de derivación es ambigua, y la regla que permite construirla también es
ambigua. A veces, la ambigüedad no afecta la semántica y puede tolerarse, en otros casos se debe eliminar porque
puede producir derivaciones que no son semánticamente equivalentes.

Límite entre la Sintaxis y la Semántica


El límite exacto entre estos términos es arbitrario. Existen diferentes criterios:
• Considerar sintácticos aquellos aspectos que se resuelven en compilación y semánticos todos los demás (en
ejecución)
• Asociar sintaxis con todos los aspectos libres de contexto y semántica con los sensibles al contexto.

Elementos de la semántica
Variable: es una región de memoria que se utiliza para almacenar valores que manipula el programa. Las reglas
sintácticas especifican como pueden nombrarse las variables. Una declaración introduce una variable dándole un
nombre y especificando algunas de sus propiedades semánticas (las operaciones válidas con ella).

Valores y Referencias. Se refiere a qué valor se asocia a una variable. Ej. la asignación X = Y:
• El lado derecho (RValue) denota el contenido de celda de memoria (valor almacenado).
• El lado izquierdo (LValue) denota la dirección de memoria a la que hace referencia la variable X.
Las variables tienen tanto LValue como RValue. Los literales (números, caracteres, etc) no tienen LValue.
Una de las reglas semánticas indica que para que una sentencia de asignación sea válida, a la izquierda debe haber
un objeto que tenga LValue porque si no da error de compilación. Ej. 3=X no es válida porque el 3 no tiene LValue.

Expresiones. Estructuras sintácticas que permiten combinar valores y operaciones para calcular nuevos valores. El LP
especifica las reglas sintácticas y semánticas que permiten construir las expresiones. Dentro de dicha especificación,
puede definir el orden en que se van a realizar las operaciones dentro de una expresión.

Semántica formal
Es la descripción rigurosa y no ambigua del significado o comportamiento de las construcciones de un LP.
Importancia:
• Facilitar a los diseñadores de LP una forma no ambigua y consistente de definir un lenguaje.
• Estandarizar un LP por medio de una definición semántica no ambigua para que no sufra diferentes
implementaciones.
• Facilitar la comprensión del LP por parte del programador. Deja en claro el comportamiento del LP, el significado
de las estructuras.
• Proporcionar los fundamentos para prueba de correctitud tanto de compiladores como de programas.
Se distinguen dos tipos: semántica estática y semántica dinámica
Semítica estática
Sirve para definir aspectos que no pueden ser representados por BNF pero que pueden evaluarse en tiempo de
compilación. Sirve para definir aspectos sensibles al contexto como chequeo de tipos, o la regla que especifica que
todas las variables deben ser declaradas antes de ser referenciadas, o que el end de un subprograma en Ada debe
ser seguido por un nombre, que debe ser el mismo nombre que el del subprograma. Utiliza como herramienta la
gramática de atributos.

Gramática de atributos
Es una notación más poderosa que BNF porque permite formalizar aspectos sensibles al contexto. Básicamente es
una GLC a la que se le han agregado tres elementos:
• Atributos: propiedades semánticas de las estructuras. Valores asociados a cada símbolo de la gramática. EJ. Tipo
de variable, valor de una expresión, dirección de memoria
o Sintetizados: la información semántica que describen es pasada de abajo hacia arriba entre los nodos del
árbol sintáctico
o Heredados: la información fluye de arriba hacia abajo
o Intrínsecos: son un tipo de atributos sintetizados, cuyo valor se calcula fuera del árbol sintáctico.
• Reglas: funciones para definir y calcular los valores de los atributos. Expresan las relaciones entre los atributos.
• Condiciones: conjunto de predicados para evaluar la consistencia de los atributos asociados a las reglas de la
gramática

Algunas características:
• Cada símbolo No Terminal de una GLC puede ser asociado a un conjunto finito de atributos.
• Un atributo puede estar vinculado a varios símbolos No Terminales.
• Cada aparición de un atributo puede estar ligada a una condición lógica que exprese una restricción que debe ser
satisfecha por el valor de ese atributo. Estas condiciones expresan restricciones sobre los constructores definidos
por los No Terminales

Reglas sintácticas Atributos posibles


<assign> -> <var> = <expr> Actual_type: atributo sintetizado para <var> y <expr>
<expr> -> <var> + <var> | <var> Expected_type: atributo heredado para <expr>
<var> -> A|B|C

Información a representar
• Las variables son de tipo enteras o reales • El tipo del lado izquierdo de la asignación debe ser
• Cuando hay dos variables del lado derecho de la del mismo tipo que del lado derecho
asignación, pueden no ser del mismo tipo • Los tipos de los operandos en el lado derecho
• Si los operandos son de distinto tipo, el tipo de la pueden ser mixtos, pero la asignación es valida
expresión es real solo si el lado izquierdo y el valor resultante de la
• Si los operandos son del mismo tipo, el tipo de la evaluación del lado derecho son del mismo tipo
expresión es igual al de los operandos
Semántica dinámica:
Determina y describe el significado de las expresiones, sentencias y unidades de programas en tiempo de ejecución.
Para definir la semántica dinámica de un LP, se pueden utilizar la semántica operacional, axiomática y denotacional.

Semántica operacional
Describe el significado de un programa usando un LP de bajo nivel, y ejecutando sus sentencias en una máquina
abstracta. Los cambios que se producen en el estado de esa máquina, cuando se ejecuta una sentencia del LP,
definen el significado de esa sentencia. Ej. una sentencia FOR, se describe por medio de bajo nivel en términos de
saltos condicionales e incondicionales
Se usa en los textos de programación y en los manuales de lenguajes. De utilidad para los implementadores, debido
que necesitan conocer exactamente qué significan las estructuras para diseñar una implementación correcta y
mostrar que esas estructuras producen programas que muestran el comportamiento que se ha especificado en la
definición

Semántica axiomática
Describe las acciones de un programa determinando expresiones lógicas (aserciones) que especifican restricciones
sobre las variables del programa antes y después de su ejecución. Basada en el Cálculo de Predicados
Las precondiciones indican el estado que se satisface antes de ejecutar una sentencia; las postcondiciones indican el
estado que se debe satisfacer después de ejecutar una sentencia. Para {P} S {Q}, siempre que la ejecución del
programa (o sentencia) S comienza en un estado que satisface P, entonces termina en un estado 0 que satisface Q
De utilidad para el programador, porque necesita conocer el significado de las sentencias antes de poder usarlas
Ej. {x>0} x:= x + 1 {x>0},

Semántica Denotacional
Es el método más riguroso para describir el significado de los programas. Define el comportamiento de un LP
aplicando funciones matemáticas a programas o a construcciones del LP, para representar su significado.
Para cada entidad se define un objeto y una función que mapea las instancias de dicha entidad en instancias del
objeto matemático. Esta basada en la teoría de funciones recursivas
Usada por el diseñador para descubrir ambigüedades o inconsistencias que puedan presentarse en el LP que se está
diseñando
UNIDAD 3: Variables y Constantes
Entidades
Son las unidades sobre las cuales trabaja un programa. Variables, subprogramas, sentencias... son tipos de
entidades, y cada una está caracterizado por una serie de atributos. Por ejemplo, las variables tienen nombre, tipo,
rango de valores, etc.; un subprograma tiene nombre, parámetros, tipo de valor de retorno, Etc.

Ligadura
Es la asociación entre una entidad y sus atributos. Por ejemplo: int a; (variable)
◦ Nombre: a
◦ Tipo: entero ← determina el comportamiento de la entidad, el rango de valores que puede tomar y las operaciones
que se pueden efectuar.
La ligadura se puede establecer antes de la ejecución (Lig. Estática) o durante la ejecución (Lig. Dinámica)

Tiempos de Ligadura
Ligadura estática:
Se establece antes de la ejecución y permanece sin alteraciones durante la misma. Es menos flexible, pero es más
eficiente y segura al momento de detectar errores. Se puede dar:
• Durante la definición del lenguaje: se especifica cuando se diseña el LP. Ej. el símbolo “+”, relacionado con la
operación suma. Todas las posibles formas opcionales de enunciados, tipo de estructura de datos, Etc.
• En tiempo de implementación: por ejemplo, cuando se establecen las reglas de precedencia y asociatividad. O
cuando se establecen los rangos de valores para un tipo de datos (entero que acepte valores entre -2000 y 2000).
• En tiempo de compilación: mejoran la ejecución, pero quitan flexibilidad al lenguaje. Ej, una ligadura entre una
llamada y un procedimiento, o una definición de variable (int a). Se pueden distinguir dos tipos, cuando:
o Enlaces son elegidos por el programador: Al escribir un programa, el programador define nombres y tipos de
variables, estructura de enunciados, Etc., que representan enlaces durante la traducción. (Ej. int a.).
o Enlaces son elegidos por el traductor: sin que el programador los especifique explícitamente. Ej. la localidad
relativa de un objeto de datos en memoria o la forma como se guardan los arreglos y como se crean descriptores
para los arreglos.
• En tiempo de Carga: Un programa se compone de varios subprogramas que se deben agrupar en un programa
ejecutable único. El vinculador enlaza variables con direcciones relativas, y asume que cada segmento va a ser
colocado en la dirección 0 de memoria. Sin embargo, este almacenamiento debe tener asignado direcciones reales
dentro de la computadora física que se va a ejecutar el programa. (la variable “a” con su celda de memoria)

Ligadura dinámica:
Se establecen durante la ejecución y pueden modificarse conforme a ciertas reglas predefinidas por el LP. Esta
ligadura provoca cierto costo de implementación, debido al chequeo de tipos en ejecución (necesita guardar el
descriptor), pero aporta flexibilidad (permite programar en forma genérica). Se pueden realizar:
• Al iniciar la ejecución de la unidad: El enlace ocurre en el momento de entrar a un subprograma. Ej, C y Pascal el
enlace de parámetros formales a reales solo pueden ocurrir al entrar al subprograma.
• En cualquier punto de la ejecución: Ej. el enlace de variables a valores a través de la asignación (a= 2;)

Declaraciones
Son enunciados que crean una ligadura entre una entidad, un nombre (identificador) y un tipo de dato. El
identificador permite nombrar a la entidad cada vez que deba ser usada o referenciada. Los tipos pueden ser
especificados por una declaración explícita o implícita. Existen lenguajes que soportan ambos tipos de declaraciones.

Tipos de declaraciones:
• Implícitas: la ligadura se establece la primera vez que el identificador es utilizado, de acuerdo a las reglas implícitas
del LP. Aumenta la facilidad de escritura, y disminuye la legibilidad de los programas. Perjudican la confiabilidad
porque impiden al traductor descubrir errores tipográficos. Ej. en FORTRAN el traductor considera que todas las
variables cuyo nombre comienza con I, J, K, L, M, N (y que no fueron declaradas explícitamente) son INTEGER
• Explícitas: se especifica el tipo junto al nombre de la entidad mediante una declaración (Ej., int a;).
o Definiciones: ligadura entre un identificador y su tipo de datos. Ej. const pi= 3.14; int a;
o Secuenciales: composición de declaraciones que permite que una declaración use ligaduras establecidas
anteriormente por otras declaraciones. Ej. type cadena = array [1..30] of char nombre: cadena;
o Recursivas: Usan ligaduras producidas por ellas mismas. Por Ejemplo:
type Celda = record
Elemento : integer;
Siguiente : Celda;

Propósitos de las Declaraciones.


• Elegir representaciones de almacenamiento: El compilador puede determinar la mejor representación de
almacenamiento para esa entidad. (Ej, decidir el rango de dígitos que puede ocupar una variable entera).
• Gestión de almacenamiento: las declaraciones dan información sobre el tiempo de vida de las entidades, lo que
hace más eficiente la gestión de almacenamiento durante la ejecución. Asignar y reasignar memoria, Java tiene
el Recolector de basura).
• Operaciones polimórficas: Permiten al compilador determinar en tiempo de compilación cuál es la operación
particular que designa un operador determinado. Ej. el “+” permite sumar enteros, reales, o concatenar strings
• Verificación de tipo: Permiten la verificación estática de tipos antes que la verificación dinámica

Alcance y visibilidad.
• Alcance: es la porción del programa en donde una entidad es visible, es decir, donde puede ser referenciada.
• Visibilidad: una entidad es visible en una sentencia si puede ser referenciada (usada) en esa sentencia.
Una entidad es visible dentro de su alcance e invisible fuera de él.
Si una entidad es visible dentro de una unidad, pero no está declarada en él, es una entidad no local.

Alcance estático:
Las referencias se resuelven en tiempo de compilación. Cuando se encuentra una referencia a una entidad, los
atributos de dicha entidad se determinan buscando la declaración: la búsqueda empieza en el subprograma donde
ocurre la referencia, si no está declarada ahí, se sigue con su padre estático.
Los programas con alcance estático son más fáciles de leer, más confiables y se ejecutan más rápido que los
programas con alcance dinámico.
• Antepasado estático: dada una unidad “P” sus antepasados
estáticos son el conjunto de subprogramas que contienen a
P en el código fuente (padre estático, abuelo estático, etc.)
• Grado de anidamiento (GA): es el nivel en el cual se
encuentra el subprograma e indica la cantidad de
antepasados estáticos que tiene. Por ejemplo, el bloque
principal tiene grado de anidamiento 0 porque no tiene
antepasados estáticos.

big es el padre estático de sub1 y sub 2

En Ada, las variables ocultas de los alcances ancestros pueden ser accedidos con referencias selectivas. Por ejemplo,
la x de big puede ser referenciada en sub1 con big.x.

Alcance dinámico:
Las referencias se resuelven en tiempo de ejecución siguiendo el flujo de activación de unidades. Se basa en la
secuencia de llamadas de subprogramas. Ej. APL, Snobol 4 y las primeras versiones de Lisp.

Suponiendo que big llama a sub2 y sub2 llama a sub1


→ la referencia de x en sub1 es la declarada en sub2.

Suponiendo que big llama directamente a sub 1 →la


referencia de x es la declarada en big
Ambiente de Referenciamiento
Es el conjunto de todas las entidades que pueden ser referenciadas (que son visibles) en una porción del código.
Dicho ambiente se crea cuando se empieza a ejecutar la unidad y permanece invariante durante todo su tiempo de
vida. Lo que puede variar son los valores contenidos en los objetos de datos. Para cada unidad existe un ambiente
global (o no local) y un ambiente local.
El ambiente local: formado por todas las entidades que pueden ser referenciadas dentro del subprograma en
cuestión y están declaradas dentro del mismo.
El ambiente global/No Local: formado por todas las entidades que pueden ser referenciadas dentro del
subprograma pero que no están declaradas dentro del mismo. Es decir, todas las entidades que están ligadas afuera.
Acá entran en juego las reglas de alcance del lenguaje, para determinar como un subprograma resuelve una
referencia a una entidad no-local.
En LP con alcance estático, el ambiente de referenciamiento está compuesto por todas las variables declaradas
localmente y todas las variables visibles de sus antepasados estáticos
En LP con alcance dinámico, el ambiente de referenciamiento está compuesto por todas las variables declaradas
localmente y todas las variables visibles del resto de subprogramas activos

Estructura general de un programa.


• Monolítica: el alcance de cada declaración es el programa completo. Hay un único ambiente de
referenciamiento. Inseguro porque la propagación de errores afecta a todo el programa. Ejemplo: Cobol.
• Bloques Chatos: divididos en un programa principal y un subconjunto de programas separados. NO PERMITE
DEFINIR UN SUBPROGRAMA DENTRO DE OTRO. Existe un ambiente local (del subprograma) y uno global (del
programa principal). Ejemplo, C, Fortran.
• Bloques anidados: como el anterior, pero permite definir un subprograma dentro de otro. Existe un alcance
local, no local y global. Permite construir programas como una jerarquía de bloques. El ambiente se define
dependiendo de si usa alcance estático o dinámico. Es necesario establecer/definir reglas de alcance

Variables y Constantes.
Variable: Es una abstracción de una celda de memoria. Es un objeto cuyo valor almacenado cambia durante la
ejecución. El nombre y la dirección permanecen inalterables durante todo su tiempo de vida.
Constante: es un objeto de datos que está ligado a un nombre y a un valor durante todo su tiempo de vida. Pero no
está ligado a una dirección de memoria. Son útiles para mejorar la legibilidad, fiabilidad y mantenimiento del código.
Pueden ser literales (ej. 42) o nominadas (ej. pi=3.1415…)

Atributos de las variables y constantes.


Nombre: cadena de caracteres que se usa para referenciar a una variable o constante, y se define por medio de una
declaración. Es deseable que sea representativo al valor al que está asociada. Al declararlas hay que considerar la
longitud máxima, cuales son palabras especiales para el LP (claves y reservadas), si es caseSensite, etc.

Alcance: es la porción del programa en donde una entidad es visible, es decir, donde puede ser referenciada. Pueden
ser: globales, cuyo tiempo de vida equivalente a la ejecución de todo el programa y son visibles en cualquier parte
del mismo; o locales a un método, cuyo tiempo de vida y visibilidad están limitada a una unidad.
Tipo: determina rango de valores que puede tomar y el set de operaciones disponibles para esa entidad. Ej. integer
puede tomar valores entre [-32768; 32767] y se puede sumar, restar, dividir, multiplicar, Etc. La ligadura puede ser:

Tiempo de vida: intervalo de tiempo que una entidad está ligada a un área de almacenamiento específico. El tiempo
de vida de una variable empieza cuando se liga a una celda de memoria y termina cuando se desliga. En el caso de
variables locales se da cuando se declara dentro del subprograma y cuando se sale del mismo; y en el caso de las
globales, cuando se declara hasta que termina el programa
• Alocación: obtención de una celda de memoria de un grupo de celdas disponibles.
• Desalocación: devolver una celda a un grupo de celdas.

De acuerdo al tiempo de vida, podemos tener 4 categorías de variables:


• Estáticas: se ligan a la celda de memoria antes de la ejecución del programa y permanece ligada hasta que finaliza.
Se define tipo y tamaño durante la compilación. Ej. las variables de FORTRAN, COBOL y las de C y C++ declaradas
static. Los subprogramas que usan variables locales estáticas se denominan sensibles a la historia, porque retienen
el valor de esas variables entre una ejecución y otra.
o Ventajas: Eficiencia, ya que al estar cargadas en memoria se usan rápido (direccionamiento directo); no hay
costo en tiempo de ejecución para la asignación o desasignación de memoria (insignificante)
o Desventajas: falta de flexibilidad, no permiten recursividad. Puede haber un desperdicio de memoria, en caso de
que no se use la variable durante la ejecución.

• Dinámicas en pila: la ligadura de almacenamiento se produce cuando se alcanza la ejecución de la declaración,


aunque los tipos están ligados estáticamente. Ej., la declaración de una variable que aparece en el inicio de un
método en Java es elaborada cuando se llama al método, cuando el método termina se produce la desasignación.
o Ventaja: permite la recursividad, cada copia activa del subprograma recursivo tiene su propia versión de
variables locales. No desperdicia memoria, se asigna y desasigna conforme aparezcan las variables.
o Desventajas: costo de tiempo de ejecución en asignación y desasignación (insignificante); los subprogramas no
son sensibles a la historia; acceso ineficiente o lento (direccionamiento indirecto).

• Dinámicas en heap explícito: se asignan y desasignan en memoria durante la ejecución de forma explícita (new y
delete en C++; new y Recolector de Basura en JAVA). Son referenciadas usando punteros o referencias. La ligadura
del tipo es estática. El espacio se gestiona dinámicamente (recordar que el heap es una colección de celdas de
almacenamiento que están desorganizadas, es imprevisible el uso que se va a hacer de esas locaciones).
o Ventajas: Ofrece una gestión de almacenamiento dinámico. Se usan para gestionar listas enlazadas y árboles
o Desventaja: ineficiente (costo de referenciar las variables y la complejidad de administrar el almacenamiento)

• Dinámicas en heap implícito: se asignan y desasignan en memoria a través de sentencias de asignación. Tipo y
espacio se asignan en ejecución. Ej.: todas las variables de APL, todos los strings y arrays en PERL, JavaScript y PHP.
o Ventajas: flexibilidad; es bueno para definir listas, árboles.
o Desventajas: ineficiente, porque todos los atributos son dinámicos (sobrecarga en ejecución); no es confiable,
por pérdida de detección de errores.

Valor: contenido de la variable (celda de memoria) o constante. En la mayoría de los LP, la ligadura entre una
variable y su valor es dinámica. En las constantes la ligadura no cambia una vez establecida. ¿Cuál es el R-value? La
mayoría de los diseñadores no resuelven esto al momento de diseñar el LP, sino que se lo dejan al implementador
que debe proveer una estrategia de inicialización (establecer la ligadura entre una variable y un valor). Algunos LP
permiten inicializar una variable en la declaración (C y Ada), otros no (Pascal).
Locación: dirección de memoria a la que está asociada la variable. La ligadura entre una variable y su locación no es
modificable por el programador, de esto se encarga el cargador cuando carga el programa en memoria. Esto es el
L-value, esto es lo que se requiere cuando una variable aparece en el lado izquierdo de una sentencia de asignación.

Alias: dos o más nombres diferentes de variables están asociados a la misma dirección de memoria en el mismo
momento. Son creados usando punteros o por variables de referencias, o por uniones en C y C++. Disminuyen la
legibilidad y la seguridad
ITEM: integer = 1;
ONE: integer = renames ITEM;

Representación de Variables y Constantes en Memoria


Lenguajes Estáticos
La asignación de memoria se calcula estáticamente, en tiempo de compilación, entonces antes de
la ejecución se puede conocer la cantidad total de memoria que va a ser usada. Acá se incluyen
las constantes, variables globales, variables estáticas

Lenguajes Basados en Pila


La representación se realiza de acuerdo a la demanda de memoria durante la ejecución. La
cantidad de memoria requerida no puede calcularse antes de la ejecución, recién se conoce
cuando se carga el programa en memoria. Las instancias de RA (IRA) se van apilando a medida
que se activan las unidades, y se desapilan (eliminan) cuando termina su ejecución. En memoria
estática se almacenan las variables locales a los procedimientos
En muchos LP, la estructura y el tamaño del RA se conoce en compilación (en general todas las
variables locales tienen un tamaño fijo).
En otros LP, los RA pueden variar en tamaño (no en estructura), ej. en los LP que permiten definir
el tamaño de un arreglo a partir de un parámetro, esto es posible porque la IRA de la unidad que está activa (en
ejecución) se ubica siempre en el tope de la pila, por lo tanto, puede variar según lo requiera.
En el caso de arreglos y matrices, el contenido se asigna a otros lugares de memoria, y en la IRA solo se mantienen
los descriptores de tipo y los punteros a memoria donde están en realidad los datos de la estructura.

Lenguajes Semi-Dinámicos
La cantidad de memoria requerida varía en ejecución (el tamaño de las variables puede cambiar) y
los LP implementan una estrategia de almacenamiento combinada entre pila y heap
Los descriptores se mantienen en el heap. Para cada variable se mantiene un puntero en la IRA que
apunta al descriptor de la variable en el heap, y a su vez ese descriptor puede tener un puntero al
objeto dentro del mismo heap.

Lenguajes Dinámicos
La cantidad de memoria requerida varía en ejecución (el tamaño de las variables puede cambiar)
y los LP implementan una estrategia de almacenamiento puramente en heap
Es difícil mantener el control de los bloques de memoria, porque los objetos pueden ocupar y
liberar espacio en cualquier momento (hacen un uso impredecible de memoria). Se usa mucho en
LP Orientados a Objetos.

La diferencia más importante es que en los LP basados en pila el cálculo de memoria necesaria para alocar las
variables utilizadas se realiza en el momento en que la unidad alcanza la ejecución; y en los LP dinámicos, se realiza a
medida que se necesita durante la ejecución (no existe una reserva de memoria previa, sino que se va alocando
memoria arbitrariamente, generalmente usando punteros)
Implementación de ambiente de referenciamiento: Reglas de alcance
Determinan como se resuelven las referencias a variables no locales (con sus declaraciones y atributos)

• LP con alcance Estático: las referencias no locales se resuelven en compilación. Cuando el compilador encuentra una
referencia a una entidad, los atributos se determinan buscando una declaración de dicha entidad siguiendo la
cadena sus antepasados estáticos. Los programas con alcance estático son más fáciles de leer, más confiables y se
ejecutan más rápido que los programas con alcance dinámico.
• LP con alcance dinámico: las referencias no locales se resuelven en ejecución, siguiendo la secuencia de llamadas a
subprogramas (siguiendo el flujo de activación de las unidades).

Implementación de referencias no locales


Las variables no locales residen en alguna instancia del RA en la pila. Para poder localizarlas es necesario: encontrar
la IRA en la que está alojada la variable y usar el desplazamiento local (en pila) para acceder a ella. Hay dos técnicas:

Cadena Estática
Se usa para implementar el acceso a variables no locales en LP de alcance estático. Consiste en una cadena de
enlaces estáticos que vincula IRA en la pila. Su función es conectar todos los antepasados estáticos de una unidad.
Para esto se tiene en cuenta la profundidad estática, que es un valor entero que indica el nivel de profundidad de
cada unidad.
Una referencia a una variable no local puede ser representada por el par: desplazamiento de cadena (es la diferencia
entre la profundidad estática donde está referenciada, y la profundidad estática de la unidad donde está declarada),
desplazamiento local en la pila (es decir, la posición en el RA).

Display
Es una pila en donde se van a colocar los enlaces estáticos, es decir, las entradas en la pila son punteros a las bases
de las IRA en el orden en que las instancias fueron anidadas.
Una referencia no local se representa por el par: desplazamiento en el display (igual que el desplazamiento de
cadena), desplazamiento local.

MAIN_3 llama a BIGSUB; BIGSUB llama a SUB2; SUB2 llama a SUB1


Acceso profundo:
Las referencias de las variables no locales se encuentran buscando en las instancias de RA en la cadena dinámica
(siguiendo el flujo de activación de las unidades). La longitud de la cadena no puede determinarse estáticamente (no
puede saberse mirando la estructura del programa). Cada instancia de RA debe tener los nombres de las variables

Acceso superficial:
Las variables no están almacenadas en las instancias de RA. Se puede usar una pila para cada nombre de variable
(muy costoso de mantener) o una tabla central con una entrada para cada nombre de variable (acceso más rápido).
Unidad 4: Tipos de datos
Abstracción
Consiste en “ocultar” las características de un objeto y obviarlas, es decir el usuario no necesita mencionar todas las
características y funciones de un objeto cada vez que éste se utiliza, sino que son declaradas por separado en el
programa y simplemente se utiliza el término abstracto.
Esta técnica aporta mayor legibilidad, ayuda al mantenimiento de los programas (si hay que cambiar algo se hace en
un solo lado), permite dividir un problema en módulos y reusabilidad de código.

La abstracción de datos es la abstracción aplicada a la forma de representación de los datos. Es la representación de


una entidad que incluye solamente los atributos más significativos dentro de un contexto particular

Tipo de datos
Representa un conjunto de valores y un conjunto de operaciones sobre dichos valores. Debe exhibir un
comportamiento uniforme bajo ciertas operaciones. Un tipo de datos puede estudiarse en 3 niveles de definición:
• A nivel sintaxis: Refiere a los mecanismos que usa el LP para definir variables, constantes y demás estructuras.
En esencia, se define el TD por cómo se lo usa a fines prácticos (cómo son las declaraciones, como se invocan sus
operaciones). Brinda al compilador información para establecer las ligaduras correctamente, realizar chequeos
• A nivel de especificación: Implica establecer:
o Atributos: que distinguen el tipo. Como el nombre del tipo (que son invariantes) y si el valor se puede
cambiar o no durante su tiempo de vida.
o Valores: conjunto de valores posibles que puede contener.
o Operaciones: como se pueden manipular los objetos de datos de ese tipo.
• A nivel implementación: se define cómo será el almacenamiento interno de los objetos de datos, y cómo serán
implementadas las operaciones que los manipularán (algoritmos).

Clasificación
Desde el punto de vista de su origen (quien lo define):
• Predefinidos por el lenguaje: son los tipos primitivos, su combinación permite elaborar tipos estructurados.
Ejemplo: entero, caracteres, booleanos.
• Definidos por el usuario: (estructurados) muchos LP ofrece una diversidad de maneras para construir tipos de
datos más complejos, basándose en los tipos básicos (como registros, listas, árboles).

Desde el punto de vista de su complejidad:


Primitivos:
Vienen predefinidos por el LP y son los que permiten definir tipos estructurados. Son atómicos y no pueden
descomponerse en elementos más simples (no están definidos por otros tipos de datos). El conjunto de valores y
operaciones es usualmente determinado por el HW, aunque hay veces que hay que simularlo por SW. Se dividen en:

• Numéricos.
o Enteros: son un conjunto finito y ordenado con atributos máximo y mínimo. Están representado en una por
una cadena de bits, con el bit de la izquierda representando el signo. Son soportados directamente por el
hardware. Se pueden usar distintas cantidades de bits para representarlos. Ej. int (32), long (64), short (16)
▪ Aritméticas,
◦ Unaria: int → int.
◦ Binaria: int x int → int.
▪ Relacionales.
◦ Binaria: int x int → boolean.
▪ Asignación.
◦ Binaria: int x int → int.
o Reales o Punto Flotante. Conjunto finito y ordenado cuyo rango de valores y su precisión viene dada por la
cantidad de bits que se utilizan para representarlos: float (32) y double (64). Una de sus limitaciones son los
errores de redondeo y truncamiento que se acarrean con las operaciones aritméticas. El bit de la izquierda
representa el signo y los restantes representan el exponente y la mantisa del número. Se implementan
directamente por HW. Las operaciones son las mismas que los enteros

o Decimales o Punto Fijo: El HW de las computadoras que han sido diseñadas para soportar aplicaciones
comerciales, suele dar soporte a este tipo (ideal para Cobol). Almacena un número fijo de dígitos decimales
con el punto decimal como una posición fija en el valor. Su ventaja es que se puede precisar la cantidad de
decimales. Su desventaja es que el rango de valores está restringido porque no se permiten los exponentes y
su representación en memoria es ineficiente. Se declara explícitamente cuanto espacio se destina a la parte
entera y cuanto a la decimal. Ej. en Cobol: dato pic 9999V99 (4 bytes para la parte entera y 2 para la decimal.
El “V” es el separador). Operaciones: las mismas que para los enteros.

o Complejos. Se componen de un par de números que representan las partes reales e imaginarias. Puede
representarse como un bloque de dos ubicaciones que contienen un par de valores: una para la parte real y
otra para la imaginaria. Generalmente se simulan por software.

o Racionales. Un racional es el cociente entre dos enteros. Se incluyen para evitar problemas de redondeo y
truncado presentes en presentaciones de punto fijo y flotante

• Caracteres: Es un conjunto finito y ordenado que se toma como una enumeración de algún alfabeto (ASCII,
Unicode). Se implementan directamente en HW, excepto que Si el SO y el HW tengan tablas distintas, en ese
caso se debe hacer una conversión. Sus operaciones son:
o Relaciónales: o Sucesor y Predecesor:
• Binaria: char x char → boolean. • Unaria: char → char.
o Asignación: o Ordinal:
• Binaria: char x char → char. • Unaria: char → entero.

• Booleanos. Su rango de valores tiene solo dos elementos. Se utiliza solo un bit de almacenamiento (aunque
suelen almacenarse en un byte, porque es la mínima unidad direccionable en memoria). Sus operaciones:
o Asignación. • o: booleano x booleano → booleano
o Lógicas: AND, NOT y OR. (disyunción inclusiva).
• y: booleano x booleano → booleano • no: booleano → booleano (negación o
(conjunción). complemento)
Derivados.
Son aquellos que se construyen a partir de establecer restricciones a los primitivos.
• Enumerados. Conjunto finito y ordenado de constantes. Sirven para ejemplificar un conjunto de valores en los
cuales importa el orden y estos son distintos unos de otros. El programador define el nombre de los elementos y
el orden que ocuparán dentro de la enumeración. Ej. enum Clase {Primero; Segundo; Tercero}.
Se utilizan sólo los bits suficientes para el intervalo que se requiere (se omite el bit del signo). Tomando el ej.
anterior Clase tiene sólo cuatro valores posibles, representados en ejecución como: 0 = Primero, 1 = Segundo, 2
= Tercero y 3 = Cuarto. Por lo tanto, sólo se necesitan 2 bits para esta representación
Aumentan legibilidad y seguridad (no puede asignarse un valor fuera de rango).
Operaciones:
o Asignación:
• Binaria: enum x enum → enum
o Sucesor y predecesor:
• Unaria: enum → enum
o Relacionales:
• Binaria: enum x enum → booleano

• Subrango. Consiste en definir un subtipo de otro ya predefinido, utilizando un dominio acotado que pertenece y
está incluido al dominio del tipo original. Tienen las mismas operaciones y representación que el tipo original.
Mejoran la legibilidad, hacen un uso más eficiente de memoria y permiten obviar ciertos aspectos de control que
se debería tener con el tipo original (ej. que no supere “x” rango de valor). Ej. type LetraMayuscula = ‘A’..’Z’

Estructurados
Son construidos a partir de otros datos, los cuales pueden ser primitivos o estructurados. Hay que tener en cuenta:
cantidad de componentes (longitud fija/variable), tipo de componentes (homogéneo/heterogéneo), organización
(lineal/no lineal), operaciones (Inserción/Eliminación de componentes, Creación y Destrucción de estructuras),
representación (secuencial: se guarda en un solo bloque contiguo de almacenamiento; vinculada: se guarda en
bloques no contiguos, con punteros al siguiente elemento).
Tener en cuenta que su implementación (representación dentro del almacenamiento) va a estar dada (va a
depender) por el tipo de los componentes de la estructura (si almacena int, char,etc.)

• Arreglos. Es una estructura de datos con un número fijo de componentes del mismo tipo organizados como una
serie lineal simple. Todos los elementos del arreglo tienen el mismo nombre, y un elemento individual es
identificado por su posición en la estructura (subíndice). Según sus índices se clasifican en:
o Unidimensionales (vectores): se requiere de un solo índice para referenciar a un dato.
o Bidimensionales (matrices): dos índices para referenciar un valor. Tienen sus componentes organizados en
una cuadricula rectangular de filas y columnas.
o Multidimensionales: tienen 3 o más dimensiones. Se requieren de tantos índices como dimensiones tenga.

Atributos
o Numero de componentes: se indican con una serie de intervalos de subíndices, uno para cada dimensión
▪ Estáticos: la cantidad de componentes y espacio de almacenamiento se ligan antes de la ejecución.
▪ Dinámicos en pila sin modificación: la cantidad de componentes se liga estáticamente y el espacio de
almacenamiento se liga dinámicamente.
▪ Dinámicos en pila: la cantidad de componentes y espacio de almacenamiento se ligan dinámicamente.
▪ Dinámicos en heap sin modificación: es similar al dinámicos en pila sin modificación, salvo que queda fijo
una vez que se le asigna el almacenamiento.
▪ Dinámicos en heap: la cantidad de componentes y espacio de almacenamiento se ligan dinámicamente y
pueden cambiar las veces que sean necesarias.
o Tipos permitidos.
o Formato de subíndice (paréntesis, corchetes..., y tipo del mismo)

Operaciones:
o Inicialización: en algunos LP, se pueden o Creación y destrucción de arreglos
inicializar en su declaración, y en otros no o Aritmética entre arreglos
o Inserción y eliminación de componentes o Descomposición de arreglos
o Subindicación: selección de un componente
• Registros. Es una estructura de datos de longitud fija, cuyos componentes pueden
ser de diferentes tipos. Los componentes o elementos se identifican por los nombres
o identificadores, en vez de con subíndices. Cada componente se accede a través del
nombre de campo más el nombre del registro
Cada campo se puede usar como cualquier dato correspondiente al tipo, incluyendo
las operaciones que éstos permiten (es decir, las que le atribuye el tipo de datos).

• Unión: Es una estructura que puede almacenar diferentes tipos de valores en diferentes momentos, durante la
ejecución de un programa. Permite especificar una selección entre alternativas excluyentes, de tipos diferentes.
Uno de los campos, llamado tag o discriminante, permite distinguir las partes variantes del registro. Si existe o
no el campo discriminante, pueden existir dos tipos:
o Unión libre: No tiene un campo de marca, por lo tanto, se puede acceder al dato incorrecto. No existe
chequeo de tipos.
o Unión discriminada: Existe un tag que discrimina la clase de variante a la cual pertenece cada objeto. Existe
chequeo de tipos.

• Strings: Es un tipo de dato en el que sus valores consisten en una secuencia de caracteres del mismo tipo base.
Se pueden definir cadenas de longitud fija (Cobol, Pascal, Ada); cadenas de longitud variable con un máximo
establecido (C, C++ y Ada); o cadenas de longitud ilimitada (Ada, Java)
Se puede hacer varias operaciones: asignación, concatenación, selección de subcadenas (cortarlas), relacionales

• Sets (conjuntos). Es un objeto de datos que contiene una colección no ordenada de valores. Son distintos de las
enumeraciones porque no tienen un orden Operaciones: pertenencia; inserción y eliminación de valores
individuales; unión, intersección y diferencia de conjuntos.

Recursivos.
Son tipos estructurados en el los cuales al menos uno de sus componentes es del mismo tipo
• Lista: es una estructura compuesta de una serie elementos, donde cada uno tiene un puntero al siguiente (cada
nodo está formado por el dato y un puntero al siguiente nodo) Son de longitud variable y sus elementos pueden
ser heterogéneos. Operaciones: inserción, eliminación y selección de elementos.
• Pila: EDD en el que se pueden añadir/quitar elementos siguiendo la mecánica LIFO
• Cola: EDD en el que se pueden añadir/quitar elementos siguiendo la mecánica FIFO

Punteros.
Es un tipo de dato cuyo valor es la dirección de otro objeto de datos. Permite el direccionamiento indirecto y hacer
una gestión dinámica de la memoria (estructuras enlazadas en vez de secuenciales). Sus variables tienen un rango de
valores que consisten en direcciones de memoria y el valor especial nulo (no es una dirección valida, es usado para
indicar que un puntero no puede ser usado para referenciar alguna celda de memoria).
Especificación: se pueden tratar de 2 maneras:
• Cuando hacen referencia a objetos de un solo tipo: Lo utilizan C, Pascal y Ada donde se utilizan declaraciones de
tipo y verificación estática. Por Ej., si tenemos la declaración: P: ^Vector donde Vector fue definido previamente.
La creación se hace a través de una operación: new(P). A partir de ese momento el vector se referencia como P^
y sus componentes como P^[i]
• Cuando hacen referencia a objetos de cualquier tipo: el puntero se almacena con un descriptor que contiene el
tipo del dato referenciado y permite efectuar controles dinámicos. Es utilizado por Smalltalk.
Operaciones:
• Creación: asigna almacenamiento para un objeto de datos y crea un puntero al nuevo objeto de datos.
• Asignación: asigna el valor de una variable puntero a alguna dirección útil (o nula).
• Selección: seguir un valor de puntero, para acceder al objeto que apunta.
• Desreferenciamiento. El valor de un puntero apunta a una dirección no deseada. Pueden provocar riesgos de:
o Basura: locaciones de memoria reservadas o inaccesibles.
o Punteros Dañinos: punteros referenciando locaciones de memoria cuyo contenido no es el esperado

Implementación
Se representa como una dirección de almacenamiento que contiene otra dirección de almacenamiento. son
referenciados como valores simples almacenados en dos o cuatro bytes de celdas de memoria, dependiendo del
espacio de almacenamiento de la máquina. Se utilizan dos representaciones para valores de punteros:
• Direcciones absolutas. Corresponde a la dirección de memoria real del bloque de almacenamiento para el objeto
de dato.
• Direcciones relativas. Se puede representar como un desplazamiento respecto a la dirección base de algún
bloque de almacenamiento.

Archivos.
Es una estructura de datos lineal compuesta por registros, potencialmente infinita, y cuenta con dos características:
• Almacenamiento: permite almacenar grandes volúmenes de datos.
• Tiempo de vida: va más allá del tiempo de ejecución del programa ya que se almacena en memoria secundaria.
Su implementación depende del SO. Las operaciones se implementan por medio de llamadas primitivas del SO.
Operaciones: Apertura, Lectura, Escritura (añadir o modificar alguno de los registros), Borrado, Cierre, Prueba de
final de archivo (EOF).

Clasificación:
• Secuenciales: los registros se organizan uno después de otro, ocupando posiciones contiguas. Para acceder a
cualquier registro particular hay que posicionarse en el primer registro y leer todos los registros subsiguientes
hasta que encontrar el que uno quiere o alcanzar el fin del archivo. Este tipo de organización no prevé espacios
para inserción de nuevos registros. Sólo se agregan al final del archivo.
• De accedo directo: están formados por componentes homogéneos de tamaño fijo que se pueden acceder
directamente a través de una clave.
• Secuenciales indexados: son una alternativa intermedia, capaz de soportar acceso secuencial y directo. Está
formado por componentes homogéneos de tamaño fijo. La organización está basada en índices. Conserva las
ventajas de la organización secuencial, permitiendo el acceso directo a los registros. Constan de dos áreas:
o Área de Datos: contiene todos los registros de datos grabados secuencialmente en orden creciente.
o Área de Índices: facilita el acceso directo. Cada registro consta de la clave más grande existente en el bloque,
el cilindro respectivo y dentro de este el número de pista que contiene el registro buscado
IMPLEMENTACION de tipos de datos en memoria: OBJETO DE DATOS
Es la forma en que se representan internamente los tipos de datos con los que trabajan los LP. Los programas
trabajan con objetos de datos, es decir con las implementaciones de los tipos de datos. Pueden existir distintas
implementaciones de un tipo de dato, y un tipo de dato tiene dos representaciones:
• La Representación de almacenamiento es la manera en que el objeto de datos es representado en memoria.
• La Representación de las operaciones son los algoritmos para manipular la representación de almacenamiento.

Sistema de tipos
Es el conjunto de reglas que indican si una construcción (correctamente sintáctica) es válida en un LP desde el punto
de vista semántico, básicamente indican si lo que está escrito tiene significado. Asocian entidades con tipos de datos.
Son reglas que estructuran y organizan el uso de los tipos de datos dentro de un LP
Las reglas restringen el uso de los elementos de cada uno de los tipos de datos. Ej. Si la operación “+” denota la suma
de números enteros, entonces no se puede usar el signo “+” para otra cosa, más que para sumar enteros.
El objetivo del sistema de tipos es lograr que los programas sean tan seguros como sea posible.
Los LP con un sistema de tipos estricto, ofrecen mayor seguridad, por lo general, están orientados al desarrollo de
aplicaciones complejas. Otros LP con un sistema de tipos permisivo, buscan mayor flexibilidad, aunque pierdan un
poco la seguridad, estos están orientación al desarrollo rápido de aplicaciones

Chequeo de tipos.
Consiste en comprobar la consistencia entre el tipo de un dato y el tipo esperado por su contexto, es decir, asegurar
que los operandos de un operador sean de tipos compatibles.
Si los tipos no son compatibles, se puede marcar como un error (cuando se intenta aplicar un operador a un
operando inapropiado, como multiplicar dos letras) o hacer una coerción (conversión automática) del tipo en la cual
se convierte el mismo (transformar un decimal en entero).
Algunos LPs exigen que el chequeo de tipos sea exhaustivo, esto implica que el tipo de dato de los operandos tiene
que ser exactamente el mismo, es decir que todas sus expresiones sean de tipos consistentes.
Otros LPs intentan que sea posible convertir (hacer una coerción).
Los LP pueden ser de:
• Tipado estático: cada entidad tiene un tipo fijo (ya sea porque fue declarado explícitamente o porque lo infirió el
compilador). Las ligaduras y el chequeo de tipos se realizan en compilación, esto permite que los errores de
programación sean detectados antes, y que la ejecución del programa sea más eficiente. Ej. C, C++ y Java
• Tipado dinámico: las entidades no tienen un tipo fijo. Ligaduras y chequeos de tipos se realizan en ejecución. Es
más flexible, a pesar de ejecutarse más lentamente y más propensos a contener errores de programación.
Requiere más espacio de almacenamiento para guardar los descriptores de tipos, y genera sobrecarga en
ejecución, ya que por cada operación debe buscar el descriptor y realizar el chequeo. Ej. Perl, y Lisp.

Hay dos definiciones para decir que un LP es fuertemente tipado:


• Si su sistema de tipos asegura que no se producirán errores de tipo en ejecución. Para ello solo debe usar tipos
predefinidos y especificar las operaciones, definiendo con exactitud los tipos requeridos por los operandos y el
resultado
• Si todos los errores de tipo se detectan. Para esto su sistema de tipos debe especificar con precisión reglas para
las coerciones y la compatibilidad
Todo LP estáticamente tipado es fuertemente tipado, pero no siempre es así a la inversa. Ej. RUBY tiene tipado
dinámico y es fuertemente tipado, es decir, no permite errores ya que devuelven excepciones. Por otro lado,
JavaScript tiene tipado dinámico pero débil, porque deja mezclar tipos en sus expresiones sin arrojar una excepción.

Conversión de tipos (coerción).


Si durante el chequeo de tipos se produce una incompatibilidad, es decir una discordancia entre el tipo real de un
argumento y el tipo esperado para la operación, puede marcarse como un error o aplicarse una coerción.
La conversión consiste en tomar una entidad de un tipo y producir otra entidad de un tipo distinto. Ej. transformar el
entero “4” en el real 4,0. Hay dos formas de aplicarse:
• La coerción o conversión implícita es una conversión que hace de forma automática el compilador. El principio
que las rige es no perder información. Es decir, transformar un tipo más pequeño en uno más grande (a nivel de
especificación). No se puede transformar el real 4,2 al int 4 (truncamiento), pero si el int 4 en el real 4,0.
• También existe la conversión explícita, que es un mecanismo ofrecido por los LP para permitir al programador
hacer conversiones de manera explícita. Ejemplo, “cast” en C
Compatibilidad de tipos.
Dos tipos T1 y T2 se dicen equivalentes si cualquier valor del tipo T1 puede ser asignado a T2 y viceversa, es decir
son equivalentes si cualquier valor de uno de los tipos puede ser asignado a una variable del otro tipo. En los LP
fuertemente tipado, las reglas de compatibilidad de tipos queda establecidas en la etapa de diseño del LP.
En los LP los criterios de compatibilidad están ligados con las reglas de conversiones que usa el LP
Existen dos criterios de compatibilidad o equivalencia de tipos:
• Por nombre: si fueron declarados en la misma sentencia (int a, b, c;) o con el mismo nombre de tipo (int a; int b;)
• Por estructura: si los datos tienen la misma estructura (representación interna). Es más difícil de implementar.
registro 1 int X array [1..5];
int a; int Z array [1..5];
registro 2 Estos arreglos son compatibles por nombre y por
int b; estructura.

Inferencia de tipos.
Es el proceso por el cual el tipo de una expresión se infiere (en vez de especificarse explícitamente) a partir de los
tipos que la componen. Los LPs con tipado estático no realizan inferencia, el tipo de cada entidad debe establecerse
explícitamente y no es posible que el mismo se infiera a partir de otra entidad.
Por ejemplo en ML: fun even(n) = (n mod 2 = 0);
A partir de mod 2, ML infiere que el tipo de n es entero, e infiere que el valor producido por la función es booleano
La excesiva dependencia de la inferencia hace a los programas difíciles de entender, por ende, difíciles de mantener

Polimorfismo.
Según la restricción de sus elementos, los LP pueden clasificarse en:
• Lenguajes monomórficos: cada entidad se liga (se declara) a un único tipo. En estos LP los chequeos y las
ligaduras se establecen estáticamente. Una función solo admite operandos de un tipo.
• Lenguajes polimórficos: las entidades pueden estar ligadas a más de un tipo y modifican su comportamiento
dependiendo de qué tipo sea los operandos. Las funciones polimórficas son aquellas que aceptan operandos de
dos o más tipos diferentes (la función suma en C, por ejemplo)

Existen diferentes polimorfismos:


• Universal: permite que una única función opera uniformemente sobre un conjunto de tipos que tienen la misma
estructura. Dentro de este tipo de polimorfismo tenemos:
o Polimorfismo paramétrico: cuando la uniformidad está dada por pasaje de parámetros. La función se comporta
de acuerdo a los parámetros que recibe (genericidad, métodos genéricos).
o Polimorfismo por inclusión: se consigue a través de subtipos, subclases y herencia. Consiste en redefinir el
método de una superclase en la subclase. Es posible porque las subclases tienen la misma estructura que las
superclases, seguida de los atributos y métodos propios. Facilita la reusabilidad y el poder expresivo del LP.
• Ad-Hoc: se obtiene cuando una función puede aplicarse a distintos tipos y el comportamiento puede diferir de un
tipo a otro.
o Sobrecarga: puede haber dos métodos en distintas clases que se llamen igual y responden al mismo mensaje. No
existe una función que acepte distintos parámetros, sino distintas funciones adaptadas a cada tipo de parámetro
pero que dan la impresión de ser la misma porque comparten el nombre. Ej. operador “+”
o Coerción: permite convertir un argumento al tipo esperado por una función para evitar que se produzca un error

Encapsulamiento (recordar MODULOS con INTERFAZ publica/ IMPLEMENTACION privada)


Es la propiedad/técnica que permite mantener un conjunto de módulos de manera tal que desde el exterior solo se
vea la interfaz del mismo. Solo se conoce el propósito del módulo, no como lo hace. En POO, es el ocultamiento de
métodos (implementación) y datos miembros en una clase, de manera que solo pueda cambiarse el estado de un
objeto de una clase, mediante los métodos públicos que esta provee (evitar accesos indeseados)
Tipos de Datos Abstractos.
Es un tipo definido por el usuario cuya implementación, está oculta para el resto de las unidades de programa, y la
única manera de manipular los objetos que pertenecen al TDA, es a través de su interfaz pública (métodos públicos)
La mayoría de los LP brindan facilidades para soportar la abstracción de datos ya que permite reducir la complejidad,
facilitar el mantenimiento y favorecer la reusabilidad.
Por ejemplo: la interfaz Lista, cuya implementación puede ser un arreglo o lista enlazada.
Con el tema de la REUSABILIDAD: “pensá que hoy implementás un TDA, cuyos métodos siguen una determinada
lógica; ese TDA se usa en un programa, que se usa extensivamente por un tiempo; si surge una implementación que
se considera más eficiente o mejor, solo hay que modificar el TDA, procurando que la interfaz permanezca
invariante. El TDA va a seguir siendo compatible con el resto del programa que lo usó anteriormente.

Diseño de Tipos de Datos.


El LP debe tener ciertas características que permitan definir TDA, como ser: soporte para la modularidad y
encapsulamiento, polimorfismo.
Al definir un TDA, se define su interfaz, es decir el header o nombre visible (para poder declarar instancias de ese
tipo), y las operaciones que se podrán hacer con esas instancias de ese TDA. Y se oculta la representación de los
elementos del tipo, de modo que sólo se pueda actuar sobre ellos a través de su interfaz
Entre los tipos básicos de operaciones están:
• Constructores: Crean una nueva instancia del tipo.
• Transformación (set): Cambian el valor de uno o más elementos de una instancia del tipo.
• Observación (get): Nos permiten observar el valor de uno o varios elementos de una instancia sin modificarlos.

Una vez definido el TDA se elige una representación interna utilizando los tipos que proporciona el LP. Ej. para
representar una Pila mediante un array o una lista enlazada de nodos con punteros.
La representación deberá ocultarse utilizando los mecanismos provistos por el LP, como ser:
• Clases en Java o C++, que permite encapsular, pero no ocultar la información
• Módulos en Modula-2, Es un fragmento que se desarrolla de forma independiente del resto del programa.
• Paquetes en Ada, que cuentan con dos partes: la especificación (interfaz) y el cuerpo (implementación); ambas
tienen el mismo nombre y pueden compilarse por separado. Un paquete puede ser enteramente visible, o
puede hacer privada su implementación.

Genericidad y tipos parametrizados. (Tipos genéricos y parametrizados)


Los tipos genéricos o parametrizados son TDAs en los cuales su implementación no hace referencia a un tipo
específico de elementos componentes. Acepta parámetros en la propia definición, dejando que el usuario indique
los tipos concretos a los que se aplicará. EJ. en lugar de hacer una pila que contenga solamente enteros, se puede
hacer una pila genérica en la que posteriormente se puedan guardar cualquier tipo de datos, lo que permitiría que se
guarden tanto enteros, como reales, cadenas, etc.
Los TDAs se usan para definir un nuevo tipo a partir del cual se pueden crear instancias. Algunas veces estas
instancias deberían operar del mismo modo sobre otros tipos de datos. Por ejemplo, uno puede pensar en listas de
manzanas o autos. La definición semántica de una lista siempre es la misma. Solamente el tipo de los elementos de
datos cambia de acuerdo al tipo sobre el cuál debía operar la lista. Esta información adicional podría ser especificada
por un parámetro genérico que es especificado al momento de la creación de la instancia. Así, una instancia de un
TDA genérico es en la práctica una instancia de una variante particular del TDA. Una lista de manzanas puede ser por
lo tanto declarada como sigue:
List<Apple> listOfApples; los corchetes angulares encierran ahora el tipo de datos para el cuál una variante del TDA
genérico List sería creada. listOfApples ofrece la misma interface que cualquiera otra lista, pero opera en instancias
del tipo Apple.
Unidad 5: Expresiones y Sentencias
Expresiones
Son construcciones sintácticas que combinan operadores y operandos y de cuya evaluación se obtiene un valor. El
orden de evaluación está dado por las reglas de precedencia y asociatividad. Se clasifican en:
• Aritméticas: resuelven cálculos aritméticos. Consisten en operadores, operandos, paréntesis y llamadas a funciones.
Los operadores pueden ser:
o Unarios: con un solo operando. Ejemplo: signo (x++)
o Binarios: con dos operandos. Ejemplo: suma, resta. (x+6)
o Ternarios: requieren de tres operandos. Ejemplo C, C++ if (condición) ? TRUE: FALSE; Condicionales

• Condicionales: tienen varias subexpresiones de las cuales una es elegida para ser evaluada, según se cumpla una
determinada condición. Ejemplo, if-then-else; el ternario de acá arriba.

• Relacionales: sirven para comparar valores (<, >, =). 2 operandos, 1 operador. Su resultado es booleano, excepto que
no sea un tipo predefinido por el LP. La sintaxis de los operadores difiere entre los LP: Ej. para desigualdad, C usa !=,
Ada usa /=, Fortran usa .NE o <>. JavaScript y PHP tienen dos operadores relacionales adicionales, === y !==. Son
similares a sus “==” y “!=”, pero previenen que sus operados sean convertidos implícitamente.
La expresión “7” == 7 es TRUE en JavaScript, porque el string es coercionado a un número. Sin embargo, “7” === 7 es
FALSE, porque no se aplica ninguna coerción ante este operador.

• Booleanas: sirven para escribir condiciones más complejas a partir de expresiones relacionales simples. Consisten en
variables, constantes y operadores booleanos y expresiones relacionales, de cuya evaluación se obtiene un valor de
verdad. Los operadores booleanos usualmente son: NOT, AND, OR, XOR

Notación: es la forma en que se pueden representar las expresiones. Existen tres tipos:
• Infija: los operadores van entre medio de los operandos. Es la más utilizada a la hora de codificar, pero es la más
compleja de traducir. Necesita de paréntesis cuando se evalúan signos de igual prioridad. Ej. (A+B) *(C-A)
• Prefija: se colocan primero los operadores (signos) y luego los operandos. Compleja de entender. Utilizada para un
proceso de compilación más directo. Hay tres versiones:
o Común: *(+(A, B), -(C, A)) ← Operandos separados con comas; paréntesis opcional para más legibilidad.
o Polaca: *+A B - C A ← Sin paréntesis, sin coma.
o Polaca Cambridge: (*(+ A B) (-C A)) ← encierra operandos y operadores entre paréntesis. Lisp usa esta
• Postfija: primero los operandos y luego los operadores. Utilizada para representación en tiempo de ejecución.
o Sufija: ((A, B)+, (C, A)-)* ← Paréntesis, comas. Como prefija común, pero al revés
o Inversa: A B + C A - *. Es la inversa de la prefija polaca

Ventajas Desventajas
Se pueden representar operaciones con
Difíciles de leer
Prefija cualquier número de parámetros, sin
La falta de paréntesis es un problema para determinar la
Postfija necesidad de paréntesis
aridad de los operadores
Fáciles de codificar
Su traducción es más compleja
Más legible
Requieren paréntesis cuando aparecen varios operadores
Infija Más cercana a la notación aritmética
Pueden dar lugar a ambigüedades si no se tienen en claro
lógica y relacional
las reglas de precedencia y asociatividad

Implementación de instrucciones.
La mayoría de los LP usan notación infija para escribir expresiones y esto genera cierta dificultad al decodificarlas.
Por eso se las traduce a una forma ejecutable que facilite la decodificación. Esto se puede hacer de diversas maneras
• Traducir las expresiones a código máquina, donde el orden de la instrucción refleja la secuencia de control y al
ser interpretadas por el hardware la ejecución es más rápida.
• Mediante estructuras de árbol, es decir las expresiones se ejecutan siguiendo una estructura de árbol; esta
ejecución es realizada mediante un intérprete de software que va recorriendo la estructura del árbol. LISP
• Traducir las expresiones a la forma prefija o postfija, que son más fáciles de ejecutar. La postfija es más directa.
Estructuras de control para las expresiones
Para evaluar expresiones, existen reglas que establecen el orden en que se van evaluar los operadores. Pueden ser:
• Explícitas: son especificadas por el programador, le permiten alterar el orden por defecto impuesto por el LP
(generalmente los paréntesis). Desventaja: hace que las expresiones se tornen más largas, y complicadas de seguir
(disminuye Facilidad de Escritura y potencialmente puede disminuir la Legibilidad)
• Implícitas: son los proporcionados por el LP
o Reglas de precedencia: establecen la prioridad de los operadores. Definen el orden en que serán evaluados
según su nivel de importancia (precedencia). Cuanto mayor nivel de precedencia tengan, antes serán resueltos.
o Reglas de asociatividad: definen el orden en que serán evaluados los operadores del mismo nivel de
precedencia. Por ejemplo, de izquierda a derecha.

Evaluación de los operandos:


• Variables: se evalúan obteniendo su valor desde memoria.
• Constantes: algunas veces se obtiene su valor desde memoria; otras veces la constante puede ser parte de las
instrucciones del lenguaje máquina.
• Expresiones entre paréntesis: se evalúan los operadores y operandos dentro de los paréntesis y el valor obtenido
es usado como un operando.
Si ninguno de los operandos u operadores tiene efectos colaterales, entonces el orden de evaluación es irrelevante.

Efectos colaterales.
Una expresión tiene efecto colateral si además de devolver el resultado, modifica el entorno. El efecto colateral de
una función ocurre cuando una función cambia un parámetro de E/S o una variable global. Ej: C + fun(C).
Si fun no tiene efectos colaterales que cambien a C, entonces el orden de evaluación no es relevante, ya que no
tiene efecto en la expresión. Pero, si fun cambia a C, entonces hay un efecto colateral.
Supongamos que fun retorne 10 y cambie el valor de su parámetro a 20. Y ahora tenemos: C = 10; B = C + fun(C)
Luego, si el valor de C es evaluado primero, su valor es 10 y el de B es 20. PERO si se evalúa primero la función y
luego el valor de C, entonces el valor de B va a ser 30.
En los LP funcionales puros, no existen los efectos colaterales, porque no existe la noción de variable
Hay dos posibles soluciones al problema de la evaluación de operados y efectos colaterales:
• Deshabilitar efectos colaterales en la evaluación de funciones; que no haya parámetros de I/O en las funciones,
negando el acceso a variables globales (esto resta flexibilidad). Prohibir el pasaje de parámetros por referencia
• Imponer claramente el orden de evaluación, y “obligar” a los implementadores a que garanticen ese orden (lo
que puede afectar algunas optimizaciones que estos puedan llegar a plantear).

Transparencia referencial.
Un programa tiene transparencia referencial si dos expresiones que tienen el mismo valor, pueden sustituirse entre
sí en cualquier parte del programa, sin afectar a la acción del mismo. El resultado de evaluar una expresión
transparente depende únicamente del resultado de evaluar las subexpresiones que la componen (no depende de la
historia del programa en ejecución ni del orden de la evaluación de las subexpresiones).
Esto es posible, solo en el caso de que las expresiones no tengan efectos colaterales
La transparencia es importante porque nos permite hacer modificaciones a los programas sin la necesidad de
preocuparnos de que dichas modificaciones afecten indeseadamente los valores o los cálculos en otras áreas.
Los programas escritos en LPs funcionales puros son referencialmente transparentes. (El valor de una función
depende de sus parámetros y posiblemente de una o más constantes globales.)

Sobrecarga de operadores.
Significa utilizar operadores para más de un propósito. Un mismo operador puede realizar diferentes operaciones
según el tipo de datos de los operandos. Ej. el operador + para suma de enteros, reales, y concatenación de strings.
La sobrecarga de operadores es aceptable, siempre que no ponga en compromiso la legibilidad ni la confiabilidad. Ej.
el operador ampersand (&) en C: Como un operador binario, especifica la operación lógica AND, pero como operador
unario está haciendo referencia a la dirección de memoria del operando, es decir se usa como puntero
La sobrecarga amplía las capacidades de un lenguaje, pero potencialmente puede provocar la pérdida de la
detección de errores por parte del compilador (disminución de confiabilidad) y pérdida de legibilidad. Esto puede
evitarse con el uso de nuevos símbolos, en lugar de sobrecargar.
Otro punto a considerar, es si el LP permite definir o redefinir sus propios operadores sobrecargados (C++). Esto
puede provocar que los usuarios definan operadores sin sentidos, y se pierda legibilidad.
Evaluación en corto circuito.
Una expresión se evalúa en corto circuito cuando se llega a un resultado sin evaluar todos los operandos/operadores
Ej: (13*a) *(b/13-1). Si a=0, no hay necesidad de evaluar el resto de la expresión, porque el resultado siempre será 0
Su ventaja es que permite ahorrar tiempo al evaluar expresiones muy complejas, pero tiene potenciales problemas,
relacionados con los efectos colaterales: supongamos que la evaluación en cortocircuito es usada en una expresión y
parte de la expresión que contiene efectos colaterales no es evaluada; si la correctitud del programa depende de ese
efecto colateral, entonces circuito causaría un serio error.

Asignación
Es un mecanismo que permite al usuario cambiar el valor de las variables. Existen distintos tipos:
• Asignación simple: es la básica, soportada por todos los LP imperativos. Ejemplo: a = 5.
• Asignación Múltiple: no soportada por todos los LP. Es la capacidad de que en una misma sentencia se les asigne
el mismo valor a varias variables de manera simultánea. Ejemplo: a, b, c= 4.
• Asignación condicional: Perl permite una estructura del tipo ($flag ? $count1 : $count2) = 0; es equivalente a...
if ($flag) {
$count1=0;
} else {
$count2=0;
}
• Operadores compuestos: se utilizan cuando la variable destino también aparece como operando en la expresión.
a = a + b → a += b.
• Operadores unarios: combinan operaciones de incremento y decremento con asignación.
a = a + 1 → a++

Para su implementación se evalúa la expresión y el valor calculado se almacena en la dirección en donde se


encuentra ligado en memoria el identificador. Los valores temporarios se mantienen memoria.

Asignación en modo mixto.


Se produce cuando en la expresión aritmética a la derecha del operador de asignación (R-Value), los operandos son
de distinto tipo. Para este caso, el diseñador del LP debe resolver si el tipo de la expresión tiene que ser del mismo
tipo que la variable que está siendo asignada, es decir, obligar al programador a hacer una conversión de tipos
explícita; o si pueden aplicarse reglas de coerción en los casos en los que haya incompatibilidad de tipos.
• Fortran, C, C++ y Perl usan reglas de coerción libremente, permitiendo que la mayoría de las mezclas de tipos sea
legal, y se pueden presentar dos casos:
o Por ampliación: a:=3; a es una variable real y 3 es una constante entera → se transforma el lado derecho, o
sea, el entero 3 se transforma en el real 3,00.
o Por reducción: a:=8.75; a es una variable entera y 8.75 una constante real → se realiza un truncamiento, el
real 8.75 se transforma en el entero 8 (truncamiento, pérdida de información).
• Pascal usa conversión explicita: el redondeo o truncamiento debe ser realizado explícitamente por el usuario
• Ada no permite asignación en modo mixto (se puede forzar una conversión).
• Java y C# solo permiten las coerciones que se dan por ampliación, a menos que se haga una conversión explícita.

Asignación como expresión.


El resultado de una sentencia de asignación puede usarse como operando en una expresión. El operador de
asignación se trata como un operador binario, esto disminuye la legibilidad del código, arrastra efectos colaterales y
existe pérdida de detección de errores.

Estructuras de control
Son mecanismos de control que permiten modificar el flujo de ejecución de las instrucciones de un programa
• Sentencias de selección: proporciona los medios para elegir entre dos o más caminos de ejecución
o Bidireccionales: tienen la estructura clásica if (control)-then(sentencias)-else (sentencias). Dada una condición, se
determina un salto a un determinado bloque de código. Se pueden anidar selectores bidireccionales para armar
estructuras más complejas (tener en cuenta la ambigüedad, en caso de que el LP no provea delimitadores o
palabras pregonadas que faciliten la interpretación de las estructuras).
o Multidireccionales: Dada una expresión, se evalua y su valor es comparado con todos los casos planteados. Si se
da una correspondencia, se sigue por ese flujo; de lo contrario, se toma un camino default. El formato típico es
switch-case1-case2-...-caseN-default.

• Sentencias de iteración: permiten que una sentencia o grupo de sentencias se ejecute cero o más veces. Hacen que
los programas sean más fáciles de escribir, más legibles, menos extensos y más flexibles. El constructor de iteración
se denomina loop o for. Se deben considerar: dónde estará ubicado el mecanismo de control (si al principio o al final
del loop), y cómo se controlará el loop. En base a esto, se pueden dividir los loops en tres tipos:
o Controladas por contador: se tiene una variable (bucle), la cual se analiza para saber si se sigue o no con la
siguiente iteración. Se debe tener un valor inicial, uno final y un incremento especificado para el bucle.
Es el típico caso del for en muchos LP.
▪ En C: for (expr_inicial ; cond_control_final ; aumento) sentencia.8
▪ En Pascal: for var := valor_inicial (to|downto) valor_final do sentencia.
A nivel diseño hay que considerar
• El tipo de la variable bucle (que será un escalar, excepto real).
• Si los parámetros del loop pueden modificarse dentro del propio loop (lo cual puede ocasionar resultados
inesperados, y potenciales problemas de legibilidad y confiabilidad en los programas)
• Si los parámetros del loop se evalúan en cada iteración o si solo se evalúan una vez al ingresar por primera
vez al bucle (sin posibilidad de cambio). ← Vendría, se evalúa la primera vez y dice “son X iteraciones”, y ya
queda establecido ese número.
o Controladas por condición: útiles para cuando no se conoce con anticipación la cantidad de ingresos exactos al
bucle. En el diseño, se debe tener en cuenta si la evaluación de la condición se hace antes (Mientras
CONDICIÓN=TRUE) o después de ejecutarse el bucle (Repetir-Hasta que).
▪ Pascal tiene evaluación previa y posterior por separado (while-do y repeat-until).
▪ C y C++ manejan ambas con las mismas instrucciones (while-do y do-while).
o Controladas por el usuario: Algunos LP permiten sentencias iterativas controladas por el usuario, de modo que
los programadores puedan incluir controles de loop, además de los previstos por la estructura.

• Sentencias de bifurcación incondicional (GOTO): permiten transferir el control de la ejecución a alguna parte
específica del programa. Su uso es muy criticado ya que dificulta la legibilidad y por lo tanto el mantenimiento.

Implementación
• Selección: se implementan usando las instrucciones de salto manejada por HW y una tabla de saltos para evitar
el control repetido del valor de la misma variable. La tabla de saltos es un arreglo almacenado secuencialmente
en memoria, donde cada uno de sus componentes es una instrucción de salto no condicional. Se evalúa la
expresión condicional del CASE y el resultado se va a devolver un entero que va a representar el desplazamiento
desde la dirección base de la tabla. La instrucción de salto en ese desplazamiento apunta al principio del bloque
que representa el código a ejecutarse por esta alternativa
• Iteración: también se implementan usando la instrucción GO TO manejada por hardware
o Controlada por contador: las expresiones que definen el valor final y el incremento, se deben evaluar y
calcular antes del inicio de la iteración y guardarse en áreas de almacenamiento temporal
o Controlada por condición: la condición debe evaluarse por cada iteración del bloque. No requiere de
almacenamiento temporal.
• Bifurcación incondicional: se implementa directamente mediante la instrucción GO TO proporcionada por HW.

Las distintas construcciones se pueden ejecutar mediante secuencias de operaciones de un SIMPLESEM


Unidad 6: UNIDADES
ABSTRACCIÓN: mecanismo que permite manejar la complejidad de los problemas, prestando atención solamente a
aquellos aspectos que se consideran relevantes para la resolución del mismo, dejando de lado los detalles que
pueden considerarse secundarios. Se puede clasificar en:
• Abstracción de Datos: permite al obviar los detalles de la representación interna de los datos, tomando solo sus
características esenciales (atributos y operaciones)
• Abstracción de Control: consiste en abstraer las propiedades de las transferencias de control.
• Abstracción de Procesos: se aplica a las operaciones necesarias para resolver un problema. Favorece la eficiencia
de la estrategia de la solución porque permite factorizar el programa en unidades (procedimientos y funciones).
También facilita la legibilidad y verificación de los programas

Subprogramas / unidades:
Son bloques de instrucciones que resuelven un subproblema bien definido dentro de un programa.
Un programa se compone de un programa principal que, durante su ejecución, puede llamar a varios subprogramas,
que a su vez pueden llamar otros y así sucesivamente.

Procedimiento
Al ser invocado evalúa una o varias instrucciones y modifica su propio ambiente y eventualmente el de la llamada.
NO devuelve un resultado. En su declaración se define nombre, cuerpo y nombre y tipo de parámetros de entrada

Función
Al ser invocada, evalúa expresiones y devuelve un resultado. El valor devuelto va a depender del valor de los
parámetros recibidos. No tiene efectos colaterales.
En su declaración se define nombre, cuerpo, nombre y tipo de parámetros de entrada y tipo del valor de retorno.

Diferencias
• Una función es invocada siempre desde una expresión, es decir, necesita un L-Value (x = fun Algo(x);), la llamada
a un procedimiento puede aparecer como una instrucción independiente, (proc algo (int b)).
• En la declaración de una función debe incluir el tipo de dato del retorno.
• Las funciones devuelven un valor mientras que los procedimientos pueden devolver 0, 1 ó n valores

Invocación
Es un llamado explícito a la ejecución de una unidad. Se invoca una unidad al enunciar su nombre, junto con los
argumentos (parámetros). La implementación de las unidades se basa en el llamado y retorno a una unidad
• La llamada: Cuando una unidad llama a otra, se deben asociar los PR con los PF, guardar el estado de ejecución
de la unidad llamadora, detener su ejecución, ceder el control a la unidad llamada y garantizar el acceso a
variables no locales
• El retorno: Cuando la unidad llamada completa su ejecución (excepto en unidades concurrentes), devuelve un
resultado (si es función) y devuelve el control a la unidad llamadora, que reanuda su ejecución en la instrucción
siguiente a la llamada. Dependiendo del modelo de implementación de pasaje de parámetros pueden o no
modificar el ambiente de la unidad llamador
Una unidad en ejecución está representada en memoria por un segmento de código y una instancia de registro de
activación (IRA), que es una pila que contiene toda la información necesaria para que la unidad pueda ser ejecutada.
La estructura del RA varía según los diferentes LP

Para poder ejecutar el programa (no la unidad), es necesario ubicarlo en un bloque de memoria que contiene:
• Segmento de código, formado por las instrucciones maquina a ejecutar.
• Memoria estática, se almacenan constantes, variables locales cuyo valor se mantiene entre
llamadas.
• Pila, que mantiene las instancias de los Registros de Activación de los procedimientos que
han sido llamados. La pila crece cada vez que se hace una llamada a un procedimiento y decrece
al momento del retorno.
• Heap, que guarda los datos cuyo tamaño varían en tiempo de ejecución.

El área intermedia entre la pila y el heap permite a ambos crecer y usar más espacio del asignado.
Elementos en la definición de subprogramas.
• Encabezamiento (header): es la primera línea de definición de un subprograma e inicia con alguna palabra
especial que indica si es procedimiento o funcion (en Java void e int/float). Aparte de eso, se especifica el
nombre del subprograma, una lista de parámetros (orden, nombre y tipo), valores de retorno (si es función)
• Cuerpo: van las declaraciones y los bloques ejecutables (pueden ir llamadas a otros subprogramas).
• Firma y Protocolo: se define en el encabezamiento. Especifica cómo debe realizarse la comunicación de
parámetros y resultados (tipo y orden de los parámetros y, opcionalmente, valor de retorno)

Pasaje de parámetros.
El acceso a los datos se puede dar de forma implícita, mediante el uso de variables globales, o de forma explícita, a
través del pasaje de parámetros.
Los parámetros formales son los que figuran en el header y los reales son los que figuran en la llama. Los LP deben
establecer cómo será la correspondencia entre parámetros, es decir, la relación entre parámetros reales y formales:
• Por posición: el primer parámetro real se corresponde con el primer formal, y así sucesivamente hasta el ultimo
parámetro. Es efectivo y seguro
• Por nombre: los parámetros pueden aparecer en cualquier orden (flexibilidad), pero se deben conocer
exactamente los nombres de los parámetros formales porque tienen que ser explicitados
• Por defecto: se usan valores por defecto si no se pasa ningún PR a los PF definidos en el subprograma. Los
valores por defecto deben aparecer al final, una vez que se omite un PF, los que siguen toman valor por defecto

Modelos Semánticos de pasaje de parámetros: asociación entre PR y PF


• Semántica In Mode: el flujo de información va desde la unidad llamadora hacia la unidad llamada. Solo permite
ingresar información a la unidad llama.
• Semántica Out Mode: flujo inverso. Solo permite extraer información de la unidad llamada hacia la llamadora
• Semántica In-Out Mode: permite el flujo en ambas direcciones. La mayoría de los LP toman a esta por defecto, si
no se especifica nada, se toma al parámetro como in-out mode.

Modelos de implementación de pasaje de parámetros


Establecen las diferentes maneras que tienen de inicializar los PR a los PF y la forma de relacionarse unos con otros
• Por copia: los PF no comparten el área de almacenamiento con los PR, sino que se tratan como variables locales. Los
parámetros se copian en otra área de memoria. Los PR “copian” su contenido en los PF.
o Por valor: El valor del PR se usa para inicializar el correspondiente PF. Semántica in-mode. La modificación de los
parámetros formales no afecta a los parámetros reales.
o Por resultado: los PF se tratan como variables locales y cuando finaliza la ejecución, los valores de los PF se
copian a los PR. Semántica es out-mode. Cualquier modificación que se haga altera el ambiente de la unidad
llamadora. EJEMPLO, un procedimiento que modifica una variable global.
o Por valor-resultado: se combinan los dos anteriores. Primero se copian los valores de los PR en los PF y al
finalizar, copia los PF en los PR. Semántica inout-mode. Es igual al pasaje por referencia, pero creando una copia
y devolviendo el resultado.
• Por Referencia: La unidad llamadora le pasa a la unidad llamada la dirección (puntero) del PR. Se convierte en una
variable compartida y cualquier modificación afecta al ambiente de la unidad llamadora. Semántica inout-mode.
• Por Nombre: es similar a pase por referencia, pero la diferencia es que la ligadura entre el PR y PF no se establece en
el momento de la llamada sino cada vez que el parámetro formal es referenciado. Cada referencia a los PF puede
quedar ligada a diferentes datos. Semántica inout-mode
Subprogramas como parámetros.
Deben considerarse: chequear el tipo de los parámetros y decidir cuál será el ambiente de referenciamiento para el
subprograma pasado como parámetro:
• Superficial: el ambiente de referenciamiento será el de la instrucción que hace la llamada al subprograma
recibido como parámetro
• Profundo: el ambiente será el de la definición de la unidad pasada como parámetro.
• Ad hoc: el ambiente será el de la sentencia que hace la llamada y que pasa a la función como parámetro real
Algunos lenguajes controlan el tipo de los parámetros, por ejemplo Fortran. En C y C++ no se pueden pasar unidades
como parámetro, pero si punteros a unidades. En este último caso se hace chequeo de tipos.

Subprograma sobrecargado.
Es una unidad que tiene el mismo nombre que otra, en el mismo ambiente de referenciamiento. Cada versión de
unidad debe tener un protocolo único (numero, orden y tipo de parámetros y/o retorno).

Subprograma genérico.
Es una unidad que implementa el mismo algoritmo, pero actúa sobre diferentes tipos de datos en los parámetros. De
esta forma se obtiene la gran ventaja de la reusabilidad del software.
Una unidad genérica se compone de una declaración genérica de los parámetros y un cuerpo (común).
Una vez se tiene escrito el subprograma genérico, se pueden declarar instancias del mismo con diferentes
parámetros reales genéricos, lo que hará que el compilador cree los subprogramas correspondientes.

Constructores de encapsulamiento.
Surgen problemas en cuanto a la complejidad del SW. Estamos hablando de la cantidad de líneas escritas en cada
programa/subprograma y la organización de los mismos (que estén modularizados no siempre significa que están
hechos de forma tal que favorezcan su mantenimiento), también, cuando los programas son muy extensos es muy
costoso volver a recompilarlo después de realizar modificaciones (meter lo de las dll y paquetes)
La solución que se plantea es agrupar los programas relacionados lógicamente, en unidades (encapsulamiento).
Donde cada unidad puede compilarse por separado, sin necesidad de compilar todo el programa. Se usan:
• Clases: permite encapsulamiento, pero no permite ocultar la información. Se compila todas juntas, están
visibles a todos los programadores.
• Módulos: Son fragmentos de programa que contienen procedimientos y funciones y se desarrollan de forma
independiente del resto del programa, lo que permite que se compilen por separado, ahorrando complejidad.
Un cambio en un módulo solo requiere volver a recompilar ese modulo. Son utilizados a modo de librería,
mediante la sentencia import
• Namespace o espacio de nombres: es un medio para encapsular clases, funciones, constantes… dentro de un
entorno, agrupándolas y evitando que se solapen en otras zonas de código independientes. Igual que las
carpetas que contienen archivos dentro del explorador de archivos. Permite que varios usuarios puedan trabajar
en varios componentes de un mismo proyecto sin que se produzcan colisiones durante la integración.

Estructuras de control a nivel subprogramas


• Explicitas: la invocación es establecida por el programador.
o Jerárquica: la unidad llamadora que depende de la unidad llamada. La última no le devuelve el control hasta que
haya terminado con su ejecución. Relación Maestro – Esclavo
o Simétrica: las unidades simétricas son un grupo de unidades que se activan unas a otras explícitamente,
generando una ejecución entrelazada (se ejecutan de forma concurrente, no en simultaneo). Las unidades se
denominan corrutinas. Las corrutinas se crean a través del Master Unit (no es una corrutina), que se encarga de
identificarlas como tales, y es quien hace el primer Resume. Cuando las corrutinas llegan a su final, el control se
devuelve a la Master. Es utilizado en los SO multiproceso. No existe más la relación Maestro – Esclavo.
▪ Las corrutinas se ejecutan un tiempo (no hasta el final de la unidad), y devuelven el control a la unidad que lo
llamo (también pueden invocar otras corrutinas). Tienen varios puntos de entrada que son controlados por
las mismas corrutinas (no tiene sentido que se vuelve a ejecutar la unidad desde el inicio). Se invocan unas a
otras con la palabra Resume. Su ambiente de referenciamiento es sensible a la historia
o Planificada: permite planificar el momento de ejecución de una unidad (la ejecución de una unidad no se
produce directamente después de su invocación). No existe un programa principal, sino un Planificador que
decide que unidades se van a ejecutar, y en qué momento. Ej. que se ejecute antes o después de otro (call subA
after subB), que inicie su ejecución condicionada por una expresión booleana (call subA when var=true), o en
base a una escala de tiempo (call subA at time=50) o de acuerdo a las prioridades (call subA with priority 10).
Usada por LP para simulación (Simula)
o Paralela: se ejecutan dos unidades de manera simultánea. La concurrencia puede ser física (si hay más de un
procesador) o lógica (simulada en un solo procesador). Cuando los procesos se comunican y comparten
información se deben sincronizar para que se ejecuten de manera ordenada y no haya conflictos en el acceso a
los datos compartidos. Los métodos que se utilizan para el control de recursos compartidos son
▪ Semáforos: es una estructura de datos que consiste en una variable entera S (semáforo) que indica si un
recurso está disponible o no, sujeta a dos operaciones: wait y signal, y una cola para almacenar descriptores
de tarea. Estas operaciones se ejecutan de manera indivisible. Cuando un proceso modifica el valor del
semáforo, otros procesos no pueden modificarlo simultáneamente.
▪ Wait decrementa el valor del semáforo. Si el valor se hace negativo es porque se está usando el recurso,
entonces el proceso que ejecuta el Wait se bloquea, y se almacena en una cola.
▪ Cuando el proceso termina, se ejecuta Signal, que incrementa el valor del semáforo.
▪ Monitores: la idea es encapsular los datos compartidos para restringir el acceso. Los datos están ahora
dentro del monitor en lugar de en las unidades clientes. Las llamadas a las unidades del monitor son
implícitamente encoladas si el monitor está ocupado en el momento de realizar la llamada.
▪ Pasaje de mensajes: es utilizado en sistemas distribuidos, donde un proceso envía mensaje a un buzón
compartido, y el otro proceso lo lee. Lo usa Ada.

• Implícitas, que son las manejadas por el LP


o Manejo de excepciones: una excepción es un evento inusual que necesita un manejo especial. Cuando ocurre
una excepción, la ejecución del programa se interrumpe, y se pasa el control al manejador de excepciones
correspondiente según el tipo de excepción. Básicamente es un subprograma que realiza algún procesamiento
que permita luego continuar la ejecución normal del programa. Después de que un manejador completa el
procesamiento de una excepción, existe una duda constante en cuanto a dónde se va a transferir el control
¿Debe volver el control al punto donde se planteó la excepción, o al inicio del subprograma, o terminar la
ejecución de manera normal (como si nada paso)?
o Eventos: son situaciones provocadas por acciones externas producidas por el usuario o entorno. Son alertas al
sistema de que algo pasó (ej. un click del usuario para cerrar una ventana, o abrir un programa). Son objetos que
se crean implícitamente en respuesta a dicha acción. El manejador de eventos es un código que se ejecuta en
respuesta a la aparición de eventos, similar al manejador de excepciones, va a determinar qué acciones tomar.
Implementación de Unidades
Una unidad en ejecución está representada en memoria por un segmento de código y una instancia de registro de
activación (IRA), que es una pila que contiene toda la información necesaria para que la unidad pueda ser ejecutada.
De acuerdo a se diseña la gestión de memoria los LP se clasifican en:

Lenguajes Estáticos: Fortran y Cobol


• La asignación de memoria se calcula estáticamente, en tiempo de compilación, entonces el espacio requerido se
conoce de antemano
• Cada segmento de código queda ligado a una única instancia de RA, por lo que cantidad de RA es fija (uno por cada
segmento de código). Por esto que no permiten recursividad (no permiten ligar un mismo segmento a varios RA)
• Son lenguajes sensibles a la historia (mantienen los datos): dos ejecuciones con las mismas entradas para variables
globales y parámetros pueden producir resultados diferentes. Porque mantienen ocupado el espacio de
almacenamiento, aun cuando las unidades no están activas
• El tiempo de vida de las variables es el tiempo de ejecución de la unidad.
• En este tipo de esquema el RA está formado por la dirección de retorno, los parámetros, las variables locales y el
valor de retorno (o Valor de función) en caso que sea una función.
• Ventajas: Esquema sencillo de implementar. Simple.
• Desventajas: No permite modelar recursividad; mantiene ocupada la memoria con la totalidad de las unidades en
todo momento, aun cuando las unidades no están activas (las descarga una vez que finaliza el programa)

La semántica de la llamada establece: La semántica del retorno establece:


• Guardar el estado de ejecución de la unidad llamadora • Mover los valores de los parámetros formales a los
(Contexto) parámetros reales (si la semántica es out/in out)
• Pasar los parámetros • Si es una función, colocar el retorno en un espacio
• Pasar la dirección de retorno a la unidad llamada accesible por la unidad llamadora
• Transferir el control a la unidad llamada • Restaurar el estado de ejecución (contexto) de la
unidad llamadora
• Transferir el control a la unidad llamadora

Lenguajes basados en pilas: Algol


• La cantidad de memoria requerida no puede calcularse estáticamente. Se calcula cuando alcanza la ejecución de la
unidad. Cuando una unidad se activa, la unidad llamadora crea su instancia de un RA.
• Cuando se termina de ejecutar una unidad, se destruye la instancia del RA y se libera espacio en memoria.
• Si empieza una nueva ejecución de la misma unidad, antes de que termine, se crea otra instancia del RA totalmente
independiente de la anterior, es decir, que SI permite recursividad (en el estático si empieza otra ejecución antes
que termine la primera, se usa el mismo RA) y con un nuevo ambiente de referenciamiento local. NO es sensible a la
historia. Por ejemplo: función factorial
• La cantidad de activaciones que puede tener un subprograma está limitada por la memoria de la maquina
• La estructura del RA es estática, es decir siempre va a estar formada por: dirección de retorno, enlace dinámico
(puntero a la base del RA de la unidad llamadora, sirve para el acceso a variables no locales), parámetros, variables
locales y el valor de función. Las instancias de los RA son dinámicas, es decir, los valores que se van a almacenar en
cada una de las instancias
Lenguajes dinámicos: ADA
• Hacen uso impredecible de la memoria. Ya que al permitir un tamaño dinámico del RA no se puede realizar la
reserva de memoria exacta cuando la unidad se carga en memoria.
• Se utiliza una estructura del tipo heap que es un área de la memoria de tamaño variable, que mantiene estos datos
dinámicamente. El tamaño de los datos almacenados acá puede variar (arboles, listas, cadenas de caracteres de
longitud variable)
• La estructura del RA es la misma que la basada en pila.
• Se reserva, en compilación, espacio para lo que se sabe que se va a usar y que se puede determinar cuánto espacio
ocupa y el resto en ejecución.

¿Cuál es la diferencia entre los esquemas de los lenguajes basados en pila y los dinámicos?
La diferencia más importante es que en los LP basados en pila, el cálculo de memoria necesaria para almacenar las
variables utilizadas se realiza en el momento en que la unidad alcanza la ejecución, mientras que en los LP dinámicos
se realiza a medida que se necesita durante la ejecución. Es decir, no existe una reserva de memoria previa, sino que
se va alocando memoria arbitrariamente. Generalmente esto se realiza utilizando punteros.
Unidad 8: EVALUACION DE LP
Esta unidad habla de los criterios que van a influir en la elección y el uso de los LP. En la catedra se seleccionó:

Legibilidad:
Se refiere al grado de dificultad que se tiene para leer y entender un programa escrito en un LP. El código debe ser
claro y sin ambigüedades. Tiene un fuerte impacto en el mantenimiento, cuanto más legible, más fácil de mantener.
Se dice que un programa legible es autodocumentable, o sea, es entendible sin necesidad de leer la documentación
La legibilidad debe ser considerada en el contexto del dominio del problema: si un programa que resuelve
determinado problema se escribiera en un LP no diseñado para ese uso, la solución resultará difícil de leer.
La legibilidad trae aparejado un conjunto de principios que afectan a la misma para bien o para mal:

• Simplicidad: está ligada a:


o Cantidad de componentes básicos que brinda un LP: un programador puede no conocer todos los componentes
con los que se hizo el código, porque él maneja otros. Un LP con muchos componentes es más difícil de
aprender. ¡Imagínate que el que escribe el código conoce un subconjunto de características del LP y el que se
encarga del mantenimiento conoce otros, cagaron!
o Multiplicidad de expresiones: el LP tiene varias formas para hacer lo mismo. Ej. a=a+1; a++; ++a; a+=1. Uno
puede estar acostumbrado a usar a=a+1 y si encontramos escrito a++ nos costará interpretarlo.
o Sobrecarga de operadores: un mismo operador tiene más de un significado. El abuso de esto disminuye la
legibilidad del código, ni hablar si el LP permite que el usuario cree sus propios op. Sobrecargados
El exceso de simplicidad, reduce su legibilidad. La mayoría de las sentencias de Assembler son simples, y se
requieren utilizar muchas sentencias para escribir un programa equivalente en un LP de Alto nivel.

• Ortogonalidad: Es la capacidad de un LP para combinar un conjunto relativamente chico de estructuras y tipos de


datos, para formar estructuras más complejas
o Un LP altamente ortogonal permite combinar los elementos de manera tal que se transforman en estructuras
muy complejas, por ende, difíciles de leer, de escribir y de entender.
o Un LP con poca ortogonalidad tiene provoca demasiadas excepciones y hace más compleja la legibilidad. Ej. en C
los registros pueden ser devueltos desde una función, pero los arreglos no.
o Un LP con ortogonalidad adecuada, requiere menos excepciones a las reglas, que se traduce en un LP más
regular, que hace al lenguaje más fácil de aprender, leer y entender.
o La simplicidad de un LP es, en parte, el resultado de una combinación de un numero relativamente pequeño
de estructuras primitivas y el uso limitado del concepto de ortogonalidad.
o Ej. Excepción en C: todos los parámetros se pasan por copia de valor, excepto cuando el parámetro es un vector,
en el que se pasa por referencia.

• Tipos de Datos: la posibilidad para definir tipos y estructuras de datos ayudan a la legibilidad Ej. C no usa booleanos
(usa 0 y 1), otros LP permiten el tipo boolean aportando mayor legibilidad. También usar un date para indicar fecha.
Los tipos de datos “registro” proveen un método para representar datos más legibles que usar una colección de
arreglos similares, uno para cada ítem de datos.

• Diseño de Sintaxis: La sintaxis de los elementos afecta la legibilidad de los programas


o Formas de los Identificadores: Identificadores limitados a longitudes muy cortas disminuyen la legibilidad. Ej. en
Basic un identificador podía consistir de una sola letra o seguida de un dígito. Fortran permitía variables de hasta
6 caracteres. No es posible usar nombres connotativos para las variables.
o Palabras Especiales: tienen un tratamiento particular. Algunos LP usan pares de palabras especiales. Ej. Pascal
Begin – End (excepto en el repeat que puede ser omitida, -ejemplo de carencia de ortogonalidad-). C usa { }.
Fortran 90 y Ada usan IF | end IF. Pocas palabras reservadas conducen a la simplicidad (Pascal). Muchas palabras
reservadas aumentan la legibilidad, pero hace más compleja la escritura) (Ada). También tener en cuenta:
▪ Palabras reservadas: son las que el LP se reserva para su uso y no pueden ser usadas como identificadores.
▪ Palabras claves: son palabras especiales, pero pueden ser usadas como identificadores. Estas hacen más
complejo el proceso de compilación debido a que debe considerar el contexto en que se usa la palabra
▪ Palabras pregonadas: palabras opcionales que se agregan para simplificar la comprensión de la estructura.
Ejemplo el IF-THEN-ELSE. El THEN es pregonada.
o Forma y significado: Usar sentencias diseñadas para que su apariencia indique por lo menos parcialmente que
su propósito es una ayuda a la legibilidad. Usar la palabra array para indicar un array, date para indicar un tipo
fecha
Facilidad de escritura:
Especifica la facilidad con que un LP puede usarse para crear programas para un dominio de problema elegido. La
mayoría de las características del LP que afectan la legibilidad también afectan la facilidad de escritura, debido a que
son criterios que en algunos casos se contraponen. Más fácil de escribir y más difícil de leer y viceversa.
Como en el caso de la legibilidad, la facilidad de escritura debe ser considerada en el contexto del objeto de dominio
del problema de un LP. Ej. es difícil programar un reporte financiero en APL, pero muy sencillo en Cobol.
No tiene sentido comparar la facilidad de escriturar de dos LP que pertenecen a dominios de aplicación diferentes.

• Simplicidad y Ortogonalidad: un LP que cuenta con un pequeño set de instrucciones (mucha simplicidad) y de
combinaciones (poca ortogonalidad), requiere un mayor esfuerzo de codificación (menos facilidad de escritura),
pero aumenta la legibilidad (hay pocas variantes para escribir lo mismo). En cambio, si tiene mucha simplicidad y
mucha ortogonalidad, cuenta con muchas estructuras y puede que algunos programadores pueden no estar
familiarizados con todas las construcciones. Se necesita de un equilibrio en la simplicidad y ortogonalidad.
Demasiada ortogonalidad va en detrimento con la facilidad de escritura

• Tipos y Estructuras de Datos: al tener mayor cantidad de tipos y estructuras, es más probable que exista alguno que
represente y resuelva de manera más simple el problema planteado lo que simplificaría la escritura. Ej. tener un
WHILE, un FOR y un REPEAT UNTIL.

• Diseño de sintaxis: elegir los signos y caracteres apropiados, palabras especiales, entre otras, impacta directamente
sobre la facilidad de escritura. Si se permiten identificadores con un número restringido de caracteres y si se permite
el uso o no de caracteres especiales, que tratamiento se le da a las Palabras especiales.

• Soporte para la abstracción: facilidad para definir y usar estructuras complicadas u operaciones de manera que
permitan que muchos de los detalles sean ignorados. Ej. para representar un árbol binario que guarda datos enteros:
o En Fortran 77: tres arreglos enteros paralelos. Disminuye la facilidad de escribir.
o En C++ y Java: abstracción de un nodo del árbol en forma de clase simple, con dos punteros y un entero.
o En Java: la naturalidad de esta última representación hace mucho más fácil de escribir un programa.

• Expresividad: El grado de expresividad de un LP permite que existan operadores muy poderosos que permitan lograr
mucho cálculo con poca codificación. Ej. en C, count ++ es más conveniente y corta que: count = count + 1

Confiabilidad (o Fiabilidad):
Se refiere a la facilidad de escribir programas que funcionen correctamente, cumpliendo sus especificaciones y que
se comporten de forma robusta antes las excepciones.

• Chequeo de tipos: examinar los errores y consistencia de tipos en un momento dado. Se pueden examinar en:
o Compilación: los errores se descubren antes de la ejecución. Facilita las modificaciones requeridas.
o Ejecución: consume más recursos, más tiempo. Hacen las cosas más flexibles
Para que haya fiabilidad debe haber un chequeo de compatibilidad con los tipos esperados y los enviados por el
programa. Además, se revisa si las operaciones que se hacen con esa entidad son adecuadas

• Manejo de excepciones: métodos para interceptar errores y anomalías ocurridos en ejecución. Toman medidas
correctivas y permiten continuar con la ejecución. Hacen que los sistemas sean robustos y confiables debido a que
siempre tienen una respuesta ante cualquier evento desafortunado.

• Restricción de Alias: Significa tener dos o más nombres para referenciar la misma celda de memoria. El alias es una
facilidad peligrosa, porque puede producir errores no deseados, tales como modificaciones de los datos, referencias
erróneas (apuntar a un valor no válido), referencias huérfanas (eliminar el contenido de una celda y uno de los
punteros, pero olvidarse de desreferenciar el otro), punteros dañinos (apuntarlo a un lugar no deseado). Algunos LP
restringen el alias para aumentar su fiabilidad, reduciendo su flexibilidad.

• Legibilidad y Facilidad de Escritura: Si el LP resulta difícil de entender, será difícil de escribir y modificar, por lo
tanto, las características que afectan la legibilidad y la facilidad de escritura, también afectan la confiabilidad.
Costo (Adicional)
• De entrenar programadores: es en función de la simplicidad y ortogonalidad del LP y la experiencia de los
programadores.
• De escritura de programas: en función de la facilidad de escritura y el dominio de aplicación.
• De compilación de programas: es despreciable debido a las enormes capacidades de procesamiento a bajo costo.
Depende la complejidad del LP, y de la fiabilidad en cuanto a chequeos a realizar durante la compilación.
• De ejecución de programas: Esta muy influenciado por el diseño de ese LP, el problema de chequeo de tipos en
tiempo de ejecución. Los compiladores pueden tener optimizadores que disminuyen el tamaño del código objeto y/o
aumentan la velocidad de ejecución.
• Del sistema de implementación del LDP: Es muy poco probable que un sistema que consuma muchos recursos sea
ampliamente utilizado. Un LP cuyo sistema de implementación es caro o sólo corre en hardware caro tendrá pocas
posibilidades de ser ampliamente utilizado.) También se considera la disponibilidad o no de compiladores gratuitos.
• De confiabilidad del LP: En los sistemas críticos, el costo de implementación del sistema se desprecia a cambio de
seguridad y confiabilidad. Las fallas en un sistema crítico pueden ser muy costosas (fallas en el sistema de vuelo de
un avión, o controles de una planta nuclear). Los fracasos de sistemas no críticos también pueden ser muy caros (un
error de SW en un electrodoméstico que provoque una mal función no es grave, pero si ese error de SW ocurrió en
toda la línea del electrodoméstico, no se detectó antes de ser comercializado, y luego los usuarios comienzan a
quejar en forma masiva, eso sí producirá grandes pérdidas a la empresa)
• De mantenimiento de programas: relacionado con la legibilidad y la facilidad de escritura. Incluyen correcciones,
modificaciones y adición de nuevas potencialidades. Este costo puede ser de 2 a 4 veces el costo total de desarrollo

Atributos de un LP en relación con las cualidades del software que produce.


Son las características que debe tener un programa escrito en cierto LP. Estas deben ser:
• Confiable: para que el software sea confiable el LP debe tener:
o Facilidad de escritura: debe permitir expresar el problema de manera que sea natural.
o Legibilidad: debe ser sencillo seguir la lógica del problema y descubrir errores
o Simplicidad: un LP simple es más fácil de dominar y permite expresar los algoritmos más fácilmente.
o Seguridad: no brindar características que permitan escribir programas erróneos. Debe contener chequeo de
tipos, restricción a opciones sensibles, Etc.
o Robustez: que el sistema siga funcionando a pesar de situaciones anómalas que se puedan presentar.
• Mantenible: debe ser un programa legible para favorecer la etapa de mantenimiento. Para que el software sea
mantenible el LP debe tener:
o Factorización: debe permitir modularizar para englobar las características relacionadas en una sola unidad.
(Debe permitir la abstracción de procesos).
o Localidad: el efecto de una característica del LP se restringe (o afecte) a una sola unidad. Por ejemplo, una
variable local a un método “A” debe tener influencia solo en ese módulo. (El efecto de una característica del
lenguaje se restringe a una porción pequeña, local, de todo el programa).
• Eficiente: debe usar los recursos (espacio y velocidad) de manera tal de aprovecharlos al máximo consumiendo la
menor cantidad posible. Optimizaciones de código (en la traducción)
• Portable: tiene que ver con la estandarización. Un LP que está ampliamente disponible y cuya definición es
independiente de las características de una maquina particular sirve para escribir software portable
• Usable: fácil de entender por el usuario, debe permitir diseños ''amigables'' para el usuario. GUI

También podría gustarte