Documentos de Académico
Documentos de Profesional
Documentos de Cultura
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).
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)
• 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
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.
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
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.
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.
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.
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.
• 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.
• 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.
¿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.
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.
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 “+”
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.
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.
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
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;
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.
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.
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…)
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.
• 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;
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).
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.
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.
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).
• 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.
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)
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.
• 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.
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++
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.
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
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.
¿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:
• 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.
• 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