B3-T2: LENGUAJES DE PROGRAMACIÓN — CONCEPTOS, PARADIGMAS Y COMPILACIÓN

Tema teórico importante. Preguntan mucho sobre las fases del compilador, la jerarquía de Chomsky (tipos 0-3 de gramáticas), la diferencia entre compilación e interpretación, y los paradigmas de programación. También caen preguntas sobre gestión de memoria y recolección de basura (garbage collection).

CLASIFICACIÓN DE LENGUAJES

Por nivel de abstracción

NivelDescripciónEjemplos
Lenguaje máquinaInstrucciones binarias directamente ejecutables por la CPU. Específico de cada arquitectura (ISA)Código binario (0s y 1s)
Ensamblador (bajo nivel)Mnemónicos que representan instrucciones máquina. Correspondencia 1:1 con instrucciones de la CPUx86 ASM, ARM ASM, MIPS ASM
Alto nivelAbstrae el hardware. Sintaxis cercana al lenguaje humano. Una sentencia equivale a muchas instrucciones máquinaC, Java, Python, JavaScript
Muy alto nivel / DSLOrientados a un dominio específico. Máxima abstracciónSQL, MATLAB, R, HTML/CSS

Por modo de ejecución

TipoProcesoVentajaDesventajaEjemplos
CompiladoCódigo fuente → compilador → código máquina nativoMáxima velocidad de ejecuciónNo portable (binario específico de plataforma), compilación lentaC, C++, Rust, Go, Fortran
InterpretadoCódigo fuente → intérprete ejecuta línea a líneaPortable, desarrollo rápido, dinámicoMenor velocidad de ejecuciónPython, Ruby, PHP, Perl, Bash
Bytecode + VMCódigo fuente → compilador → bytecode → máquina virtual (JIT)Portable ("write once, run anywhere") + buena velocidadOverhead de la VM, mayor uso de memoriaJava (JVM), C# (CLR), Kotlin
TranspiladoCódigo fuente de un lenguaje → código fuente de otro lenguajeNuevas funcionalidades sobre lenguaje establecidoDebugging más complejo (source maps)TypeScript→JS, Sass→CSS, CoffeeScript→JS
Examen: Java NO es ni puramente compilado ni interpretado — se compila a bytecode (.class) que ejecuta la JVM. La JVM usa JIT (Just-In-Time) para compilar bytecode a código nativo en tiempo de ejecución. Python se compila a bytecode (.pyc) pero se ejecuta en su intérprete (CPython).

Por paradigma

ParadigmaConcepto centralCaracterísticasLenguajes
Imperativo / proceduralSecuencia de instrucciones que modifican el estadoVariables, asignación, bucles, funciones/procedimientosC, Pascal, Fortran, COBOL
Orientado a objetos (OO)Objetos que encapsulan datos y comportamientoClases, herencia, polimorfismo, encapsulación, abstracciónJava, C#, C++, Python, Ruby
FuncionalFunciones puras sin efectos secundariosInmutabilidad, funciones de orden superior, recursión, evaluación perezosaHaskell, Erlang, Clojure, F#, Lisp, Scala
LógicoHechos y reglas; el motor infiere conclusionesUnificación, backtracking, base de conocimientoProlog, Datalog, Mercury
DeclarativoSe describe QUÉ se quiere, no CÓMOSin flujo de control explícitoSQL, HTML, CSS, XSLT
MultiparadigmaCombina varios paradigmasFlexibilidadPython, JavaScript, Scala, Kotlin, C++

Por sistema de tipos

ClasificaciónDescripciónEjemplos
Tipado estáticoLos tipos se comprueban en compilación. Cada variable tiene un tipo fijoJava, C, C++, C#, Go, Rust, TypeScript
Tipado dinámicoLos tipos se comprueban en ejecución. Las variables pueden cambiar de tipoPython, JavaScript, Ruby, PHP, Perl
Tipado fuerteNo permite conversiones implícitas entre tipos incompatiblesPython, Java, Haskell, Rust
Tipado débilPermite conversiones implícitas (coerción) entre tiposJavaScript, C, PHP, Perl
Examen: Python es dinámico y fuerte (no puedes sumar string + int sin convertir). JavaScript es dinámico y débil ("5" + 3 = "53", coerción implícita). Java es estático y fuerte. C es estático pero más débil (permite casts peligrosos).

COMPILACIÓN: FASES DEL COMPILADOR

Estructura general del compilador

Un compilador traduce código fuente a código objeto (típicamente código máquina o bytecode). Se divide en dos grandes fases:

FaseSubfasesFunción
Front-end (análisis)Análisis léxico (scanner)Convierte el flujo de caracteres en tokens (lexemas)
Análisis sintáctico (parser)Construye el árbol de sintaxis abstracta (AST) según la gramática
Análisis semánticoComprueba tipos, declaraciones, ámbito de variables; tabla de símbolos
Middle (intermedio)Generación de código intermedioGenera representación intermedia (IR) independiente de la máquina (ej. código de tres direcciones)
Back-end (síntesis)Optimización de códigoMejora el código intermedio (eliminación de código muerto, propagación de constantes, desenrollado de bucles)
Generación de código objetoProduce código máquina para la arquitectura destino. Asignación de registros

Detalle de cada fase

FaseEntradaSalidaHerramienta/Concepto clave
Análisis léxicoFlujo de caracteresFlujo de tokensAutómatas finitos, expresiones regulares. Herramientas: lex, flex
Análisis sintácticoFlujo de tokensÁrbol de sintaxis (AST / parse tree)Gramáticas libres de contexto (tipo 2 Chomsky). Herramientas: yacc, bison
Análisis semánticoASTAST anotado (con tipos, ámbitos)Tabla de símbolos, comprobación de tipos, resolución de ámbitos
Generación IRAST anotadoCódigo intermedio (3-address code, SSA)Representación independiente de máquina
OptimizaciónCódigo intermedioCódigo intermedio optimizadoAnálisis de flujo de datos, grafos de control de flujo
Generación códigoIR optimizadoCódigo máquina / bytecodeSelección de instrucciones, asignación de registros, scheduling
Examen: El análisis léxico usa autómatas finitos (y expresiones regulares). El análisis sintáctico usa gramáticas libres de contexto. Los parsers pueden ser descendentes (top-down: LL, recursive descent) o ascendentes (bottom-up: LR, LALR, SLR). yacc/bison generan parsers LALR(1).

Tabla de símbolos

La tabla de símbolos es una estructura de datos (típicamente hash table) que mantiene información sobre cada identificador del programa: nombre, tipo, ámbito, dirección de memoria, etc. Se construye durante el análisis léxico/semántico y se consulta durante todo el proceso de compilación.


GRAMÁTICAS FORMALES — JERARQUÍA DE CHOMSKY

Noam Chomsky (1956) clasificó las gramáticas formales en 4 tipos según las restricciones de sus reglas de producción:

TipoNombreRestricción de produccionesAutómata reconocedorUso en compiladores
Tipo 0Sin restricciones (recursivamente enumerable)α → β (sin restricciones)Máquina de TuringModelo teórico general
Tipo 1Sensibles al contextoαAβ → αγβ (|lado izq| ≤ |lado der|)Autómata linealmente acotadoSemántica contextual
Tipo 2Libres de contexto (CFG)A → γ (un solo no-terminal a la izquierda)Autómata de pila (PDA)Análisis sintáctico
Tipo 3RegularesA → aB o A → a (lineal por la derecha/izquierda)Autómata finito (DFA/NFA)Análisis léxico
Examen: Pregunta clásica — qué tipo de gramática usa cada fase del compilador. Léxico → Tipo 3 (regular), reconocido por autómata finito. Sintáctico → Tipo 2 (libre de contexto), reconocido por autómata de pila. La jerarquía es inclusiva: Tipo 3 ⊂ Tipo 2 ⊂ Tipo 1 ⊂ Tipo 0.

BNF y EBNF

NotaciónDescripciónEjemplo
BNF (Backus-Naur Form)Notación formal para definir gramáticas libres de contexto<expr> ::= <term> "+" <expr> | <term>
EBNF (Extended BNF)BNF extendida con repetición (*), opcionalidad (?), agrupación ()expr = term { "+" term }

GESTIÓN DE MEMORIA EN PROGRAMAS

Regiones de memoria

RegiónContenidoGestiónCiclo de vida
Código (text)Instrucciones del programa compiladoFija, solo lecturaTodo el programa
Datos estáticos / globalVariables globales y estáticasFija, asignada al inicioTodo el programa
Stack (pila)Variables locales, parámetros, direcciones de retorno, frames de funciónAutomática (LIFO)Duración de la llamada a función
Heap (montículo)Objetos y datos asignados dinámicamenteManual (malloc/free, new/delete) o GCHasta que se libere explícitamente o GC lo recoja
Examen: El stack es rápido pero limitado (tamaño fijo, stack overflow si se excede). El heap es más lento pero flexible (tamaño dinámico). En Java/Python/C# todo objeto va al heap; en C/C++ el programador decide. El stack crece hacia abajo y el heap hacia arriba (en la mayoría de arquitecturas).

Garbage Collection (recolección de basura)

AlgoritmoDescripciónVentajaDesventaja
Conteo de referenciasCada objeto lleva un contador de referencias. Se libera cuando llega a 0Simple, liberación inmediataNo detecta ciclos (A→B→A)
Mark and SweepFase 1: marca objetos alcanzables desde raíces (mark). Fase 2: libera los no marcados (sweep)Detecta ciclosPausas (stop-the-world)
Mark and CompactMark + compacta la memoria (mueve objetos vivos juntos)Elimina fragmentaciónMás lento (mover objetos)
GeneracionalDivide heap en generaciones (young/old). Los objetos nuevos se recogen con más frecuenciaEficiente (mayoría de objetos mueren jóvenes — hipótesis generacional)Complejidad
Copying GCDivide memoria en dos mitades. Copia objetos vivos de una mitad a otra y limpia la fuenteRápido, sin fragmentaciónSolo usa 50% de la memoria
Examen: Java (JVM) usa GC generacional: Young Generation (Eden + Survivor spaces) + Old Generation (Tenured). Minor GC recoge Young Gen (frecuente, rápido). Major/Full GC recoge Old Gen (infrecuente, más lento). G1GC es el colector por defecto desde Java 9.

TIPOS DE DATOS Y ESTRUCTURAS

Tipos de datos primitivos

TipoDescripciónTamaño típico (Java)
byteEntero con signo8 bits (-128 a 127)
shortEntero con signo16 bits (-32.768 a 32.767)
intEntero con signo32 bits (-2³¹ a 2³¹-1)
longEntero con signo64 bits (-2⁶³ a 2⁶³-1)
floatComa flotante (IEEE 754)32 bits (7 dígitos significativos)
doubleComa flotante doble precisión64 bits (15-17 dígitos significativos)
charCarácter Unicode16 bits (UTF-16) en Java, 8 bits en C
booleanVerdadero/falso1 bit lógico (implementación varía)

Paso de parámetros

MétodoDescripciónLenguajes
Por valorSe copia el valor del argumento. El original no se modificaC (primitivos), Java (primitivos)
Por referenciaSe pasa la dirección de memoria. La función puede modificar el originalC++ (con &), C# (ref/out), Fortran
Por valor de referenciaSe copia la referencia al objeto (no el objeto). El objeto puede modificarse, pero reasignar la referencia no afecta al originalJava (objetos), Python, JavaScript
Por nombreSe sustituye la expresión textual (como una macro). Evaluación perezosaHaskell (evaluación perezosa), Algol 60
Examen: Java siempre pasa por valor. Para primitivos, se copia el valor. Para objetos, se copia la referencia (por valor de referencia). Esto significa que puedes modificar el objeto pero no puedes reasignar la referencia original.

Ámbito (scope) y enlace (binding)

ConceptoDescripción
Ámbito estático (léxico)El ámbito se determina por la estructura del código fuente (en tiempo de compilación). Usado por C, Java, Python
Ámbito dinámicoEl ámbito depende de la pila de llamadas en ejecución. Usado por Bash, Emacs Lisp (antiguo)
Binding estáticoLa asociación variable-tipo/valor se resuelve en compilación
Binding dinámicoLa asociación se resuelve en tiempo de ejecución (polimorfismo, duck typing)

PARADIGMA FUNCIONAL — CONCEPTOS CLAVE

ConceptoDescripción
Función puraSin efectos secundarios. El resultado solo depende de los argumentos (misma entrada → misma salida)
InmutabilidadLos datos no se modifican. Se crean copias con los cambios aplicados
Función de orden superiorFunción que recibe otra función como argumento o devuelve una función
Clausura (closure)Función que captura variables del ámbito en que fue definida
Evaluación perezosa (lazy)Las expresiones no se evalúan hasta que su valor es necesario
Pattern matchingDesestructuración y bifurcación basada en la forma de los datos
Map / Filter / ReduceOperaciones fundamentales sobre colecciones sin bucles explícitos
CurryingTransformar una función de múltiples argumentos en una cadena de funciones de un argumento
Transparencia referencialUna expresión puede reemplazarse por su valor sin cambiar el comportamiento del programa

PARADIGMA LÓGICO — PROLOG

ConceptoDescripción
HechosAfirmaciones sobre el mundo: padre(pedro, juan).
ReglasInferencias: abuelo(X,Z) :- padre(X,Y), padre(Y,Z).
ConsultasPreguntas al motor: ?- abuelo(pedro, Quien).
UnificaciónProceso de encontrar valores para las variables que hagan coincidir los términos
BacktrackingSi una rama falla, Prolog retrocede y prueba alternativas
Negación por fallo\+ en Prolog: algo es falso si no se puede demostrar verdadero

HERRAMIENTAS Y ENTORNOS

HerramientaFunción
CompiladorTraduce código fuente a código máquina (gcc, javac, rustc)
IntérpreteEjecuta código fuente directamente línea a línea (python, ruby, node)
Linker (enlazador)Combina archivos objeto (.o) y librerías en un ejecutable final
Loader (cargador)Carga el ejecutable en memoria para su ejecución
PreprocesadorProcesa directivas antes de compilar (#include, #define en C/C++)
Depurador (debugger)Permite ejecutar paso a paso, inspeccionar variables, poner breakpoints (gdb, lldb)
ProfilerAnaliza el rendimiento: qué funciones consumen más CPU/memoria
LinterAnaliza código estáticamente buscando errores, estilo y problemas potenciales


FUENTES PÚBLICAS

Este resumen ha sido elaborado íntegramente a partir de fuentes de dominio público. No se ha utilizado material con copyright de terceros ni material de preparadores.
FuenteTipoReferencia
Chomsky, N. (1956) — Jerarquía de gramáticasPublicación académicaDominio público
Aho et al. — Conceptos de compiladoresConocimiento académicoDominio público
Documentación Java, Python, CDocumentación oficialdocs.oracle.com, docs.python.org
ISO/IEC 9899 — Lenguaje CEstándarISO

¿Quieres practicar este tema con tests?

MIMIR tiene más de 5.000 preguntas verificadas, simulacros con penalización real y chat IA que resuelve tus dudas sobre este tema.

Abrir MIMIR gratis →