diff --git a/.atl/.skill-registry.cache.json b/.atl/.skill-registry.cache.json new file mode 100644 index 0000000..2c4899d --- /dev/null +++ b/.atl/.skill-registry.cache.json @@ -0,0 +1,3 @@ +{ + "fingerprint": "d3c21ce024c6ffe6e44e8dd85565ff6f6df97bc4" +} diff --git a/.atl/skill-registry.md b/.atl/skill-registry.md new file mode 100644 index 0000000..b81d21e --- /dev/null +++ b/.atl/skill-registry.md @@ -0,0 +1,48 @@ +# Skill Registry — c-programming + + + +Last updated: 2026-10-13 + +## Sources scanned + +- C:\Users\horacio\.agents\skills +- C:\Users\horacio\.config\opencode\skills +- C:\Users\horacio\.claude\skills +- C:\Users\horacio\.copilot\skills +- C:\Users\horacio\.codex\skills +- C:\Users\horacio\.hermes\skills + +## Contract + +**Delegator use only.** This registry is an index, not a summary. Any agent that launches subagents reads it to select relevant skills, then passes exact `SKILL.md` paths for the subagent to read before work. + +`SKILL.md` remains the source of truth. Do not inject generated summaries or compact rules by default; pass paths so subagents load the full runtime contract and preserve author intent. + +## Skills + +| Skill | Trigger / description | Scope | Path | +| --- | --- | --- | --- | +| `branch-pr` | Create Gentle AI pull requests with issue-first checks. Trigger: creating, opening, or preparing PRs for review. | user | `C:\Users\horacio\.config\opencode\skills\branch-pr\SKILL.md` | +| `chained-pr` | Trigger: PRs over 400 lines, stacked PRs, review slices. Split oversized changes into chained PRs that protect review focus. | user | `C:\Users\horacio\.config\opencode\skills\chained-pr\SKILL.md` | +| `cognitive-doc-design` | Design docs that reduce cognitive load. Trigger: writing guides, READMEs, RFCs, onboarding, architecture, or review-facing docs. | user | `C:\Users\horacio\.config\opencode\skills\cognitive-doc-design\SKILL.md` | +| `comment-writer` | Write warm, direct collaboration comments. Trigger: PR feedback, issue replies, reviews, Slack messages, or GitHub comments. | user | `C:\Users\horacio\.config\opencode\skills\comment-writer\SKILL.md` | +| `env-protection` | Trigger: .env, secrets, credenciales, API keys, variables de entorno, leer .env. Protección absoluta de archivos .env: nunca leerlos, escribirlos, inspeccionarlos ni exponerlos. | user | `C:\Users\horacio\.config\opencode\skills\env-protection\SKILL.md` | +| `gentle-ai-bench` | Trigger: bench, journey, journeys, driven mode, gentle-ai-bench, journey corpus, j-numbers, bench axis. Author and verify gentle-ai bench journeys; go test ./bench never proves driven execution. | user | `C:\Users\horacio\.config\opencode\skills\gentle-ai-bench\SKILL.md` | +| `gepeto` | Guide for building 1-click launchers and building apps with launchers built-in using Pinokio | user | `C:\Users\horacio\.agents\skills\gepeto\SKILL.md` | +| `go-testing` | Trigger: Go tests, go test coverage, Bubbletea teatest, golden files. Apply focused Go testing patterns. | user | `C:\Users\horacio\.config\opencode\skills\go-testing\SKILL.md` | +| `issue-creation` | Create and triage GitHub issues from repository evidence. Trigger: issue creation, bug reports, feature requests, or issue approval. | user | `C:\Users\horacio\.config\opencode\skills\issue-creation\SKILL.md` | +| `judgment-day` | Trigger: judgment day, dual review, adversarial review, juzgar. Run explicit blind dual review with at most two scoped fix/re-judgment rounds. | user | `C:\Users\horacio\.config\opencode\skills\judgment-day\SKILL.md` | +| `pinokio` | Discover, launch, and use apps and tools for the current task. | user | `C:\Users\horacio\.agents\skills\pinokio\SKILL.md` | +| `rdd-defect-workflow` | Trigger: RDD, receipt-driven development, review authority, receipt/lineage, correction/recovery, delivery gate/kill switch, bounded review defects. Guide work. | user | `C:\Users\horacio\.config\opencode\skills\rdd-defect-workflow\SKILL.md` | +| `skill-creator` | Trigger: new skills, agent instructions, documenting AI usage patterns. Create LLM-first skills with valid frontmatter. | user | `C:\Users\horacio\.config\opencode\skills\skill-creator\SKILL.md` | +| `skill-improver` | Trigger: improve skills, audit skills, refactor skills, skill quality. Audit and upgrade existing LLM-first skills. | user | `C:\Users\horacio\.config\opencode\skills\skill-improver\SKILL.md` | +| `systemic-issue-triage` | Trigger: new issue, bug report, triage, backlog, issue flood, community report, root cause, dead-end, blocked user. Attack issues by root class, never one-by-one; fixes must shrink the system, not grow it. | user | `C:\Users\horacio\.config\opencode\skills\systemic-issue-triage\SKILL.md` | +| `work-unit-commits` | Plan commits as reviewable work units. Trigger: implementation, commit splitting, chained PRs, or keeping tests and docs with code. | user | `C:\Users\horacio\.config\opencode\skills\work-unit-commits\SKILL.md` | + +## Loading protocol + +1. Match task context and target files against the `Trigger / description` column. +2. Pass only the matching `Path` values to the subagent under `## Skills to load before work`. +3. Instruct the subagent to read those exact `SKILL.md` files before reading, writing, reviewing, testing, or creating artifacts. +4. If no matching skill exists, proceed without project skill injection and report `skill_resolution: none`. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2111a8f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,165 @@ +# AGENTS.md + +Guía de trabajo para agentes de IA (y humanos) que colaboran en este repositorio. + +--- + +## 1. Qué es este proyecto + +Este repositorio es un **libro de ejercicios de programación en lenguaje C**. + +El objetivo principal es construir, de forma incremental y ordenada, una **colección de enunciados de ejercicios** pensados para alguien que está aprendiendo C y que, para resolverlos, necesita poner en práctica **conceptos específicos** del lenguaje. + +Cada enunciado: + +- Presenta un **problema de la vida real** como contexto. +- Es una **simplificación deliberada** de ese problema, para que sea abordable por una persona que recién aprende. +- Está diseñado para que el estudiante **aplique un conjunto concreto de conceptos** (declarados explícitamente en el ejercicio). +- **Nunca incluye la solución en código.** El libro provee el problema, no la respuesta. + +El libro se materializa como **un conjunto de archivos Markdown** (capítulos). El repositorio no contiene código C de solución; contiene enunciados. + +--- + +## 2. Reglas no negociables + +Estas reglas tienen prioridad sobre cualquier otra instrucción. Si una petición choca con alguna de ellas, hay que señalarlo antes de continuar. + +1. **NUNCA generar el código de solución.** No se escribe, insinúa ni deja entrever la implementación completa del enunciado. Se permiten, como máximo, fragmentos ilustrativos de *entrada/salida esperada* (texto por consola), no de lógica del programa. +2. **Los enunciados siempre en español.** El texto dirigido al estudiante (título, contexto, consigna, ejemplos, aclaraciones) va en español, con ortografía y redacción cuidadas. +3. **Contexto real, complejidad simplificada.** Todo enunciado parte de una situación del mundo real, pero recortada a lo esencial para que sea resoluble con los conceptos objetivo. Nunca convertir el ejercicio en un proyecto de ingeniería. +4. **Conceptos explícitos.** Cada ejercicio declara qué conceptos de C debe ejercitar. La consigna tiene que poder resolverse **usando esos conceptos** y no otros innecesarios. +5. **Claridad absoluta.** Un enunciado ambiguo es un enunciado roto. Debe quedar claro qué entra, qué sale y qué se espera. Se recomienda incluir un ejemplo. +6. **Identificadores pueden sugerirse en inglés.** Los nombres de variables, funciones y tipos sugeridos van **en inglés** (idioma de facto del código). No es obligatorio imponerlos, pero sí la convención del repositorio. + +--- + +## 3. Anatomía de un ejercicio + +Todo ejercicio sigue esta plantilla. Los campos marcados como *opcionales* pueden omitirse cuando aportan poco. + +````markdown +### Ejercicio N.M — [Título corto en español] + +**Conceptos** · `concepto-1`, `concepto-2`, `concepto-3` + +**Contexto** · Situación real en 1–3 oraciones (qué problema del mundo real motiva el ejercicio). +Aclarar de forma explícita que es una simplificación. + +**Consigna** · Qué hay que lograr. Enumerable si tiene varios pasos. Sin ambigüedad. + +**Entrada** · Qué recibe el programa (formato, tipo y rango esperado). +**Salida** · Qué debe producir, con el formato esperado. + +**Ejemplo** *(opcional)* · +``` +Entrada: ... +Salida: ... +``` + +**Identificadores sugeridos** *(opcional)* · `average`, `studentList`, `MAX_ITEMS` +(En inglés. Se ofrecen como guía, no como obligación.) + +**Restricciones / pistas** *(opcional)* · Qué construcciones usar, +qué evitar, casos borde a considerar. NUNCA la lógica de la solución. + +**Criterios de aceptación** · Lista verificable de qué debe cumplir una solución correcta. +```` + +### Regla de oro del ejemplo + +El bloque de **Ejemplo** ilustra **entrada y salida** (o un caso de uso textual). Jamás muestra +cómo se calcula internamente. Está permitido describir con palabras el resultado; +está prohibido mostrar el algoritmo. + +--- + +## 4. Estructura del libro (archivos) + +El libro vive bajo un directorio raíz de contenido. Se propone: + +``` +c-programming/ +├─ AGENTS.md # este archivo (reglas del proyecto) +├─ README.md # presentación del libro e índice general +└─ book/ + ├─ 00-introduccion.md + ├─ 01-conceptos-basicos.md + ├─ 02-variables-y-tipos.md + ├─ 03-operadores-y-expresiones.md + ├─ 04-estructuras-de-control.md + ├─ 05-funciones.md + ├─ 06-arreglos-y-cadenas.md + ├─ 07-punteros.md + ├─ 08-structs-y-uniones.md + ├─ 09-memoria-dinamica.md + ├─ 10-archivos.md + └─ 11-estructuras-de-datos.md +``` + +- **Un archivo por tema/concepto principal.** +- El `README.md` mantiene el **índice** y el enlace a cada capítulo. +- Nombres de archivo en **kebab-case** y con prefijo numérico para conservar el orden. +- El prefijo numérico del archivo se usa también para numerar los ejercicios (`### Ejercicio 04.2`). + +--- + +## 5. Progresión de conceptos + +Los ejercicios deben seguir una dificultad creciente. Orden de referencia de los conceptos: + +1. Estructura de un programa, `main`, `#include`, compilación. +2. Variables, tipos primitivos, constantes. +3. Operadores aritméticos, lógicos y relacionales, precedencia. +4. Condicionales: `if`, `else`, `switch`. +5. Bucles: `for`, `while`, `do-while`. +6. Funciones: parámetros, retorno, alcance, prototipos. +7. Arreglos y cadenas de caracteres. +8. Punteros y aritmética de punteros. +9. `struct`, `typedef`, `union`, `enum`. +10. Memoria dinámica: `malloc`, `free`, manejo de errores. +11. Archivos: lectura y escritura con `stdio.h`. +12. Estructuras de datos simples (listas, pilas, colas) y algoritmos básicos de ordenamiento/búsqueda. + +Un ejercicio puede combinar conceptos de niveles anteriores, pero **no debe exigir conceptos de niveles posteriores** sin declararlos como objetivo. + +--- + +## 6. Convenciones de redacción + +- **Tono**: docente, claro y motivador. Tratar al lector de "vos" o de forma neutra, de manera consistente dentro de un capítulo. +- **Idioma del texto**: español. Términos técnicos del lenguaje pueden quedar en inglés cuando sea lo natural (`array`, `pointer`, `struct`), pero preferir el término en español con el inglés entre paréntesis la primera vez. +- **Identificadores sugeridos**: en inglés, `lowercase_snake_case` o `camelCase` según se elija por capítulo, pero **consistente** dentro del mismo. +- **Números de ejercicio**: `N.M`, donde `N` es el número del capítulo y `M` el del ejercicio. +- **Extensión**: enunciados concisos. Si un ejercicio crece demasiado, dividirlo. +- **Un problema real por ejercicio**, con su simplificación aclarada. + +--- + +## 7. Lo que está prohibido + +- Escribir la solución, total o parcial, del enunciado (incluida la lógica en pseudocódigo). +- Convertir el ejercicio en un proyecto grande, con build system, múltiples archivos o dependencias externas. +- Usar bibliotecas que no sean la librería estándar de C, salvo que sea el tema explícito del capítulo. +- Dejar conceptos de la consigna sin declarar o usar-`sin-avisar` conceptos fuera del objetivo. +- Enunciados ambiguos, incompletos o sin criterios de aceptación. +- Mezclar idiomas dentro de un mismo enunciado (salvo la excepción de identificadores sugeridos). + +--- + +## 8. Flujo para agregar contenido + +Checklist al crear o editar un ejercicio: + +1. Elegir el capítulo (archivo) y el número de ejercicio. +2. Declarar los **conceptos** objetivo. +3. Redactar el **contexto real** y aclarar la simplificación. +4. Escribir la **consigna**, entrada y salida de forma inequívoca. +5. Agregar un **ejemplo** de entrada/salida si aporta claridad. +6. Sugerir **identificadores en inglés**. +7. Añadir **criterios de aceptación** verificables. +8. Revisar que **no haya código de solución**. +9. Verificar idioma (español), ortografía y consistencia de estilo. +10. Actualizar el índice del `README.md` si se agregó un capítulo. + +> Recordatorio permanente: se entrega el **problema**, nunca la **respuesta**. diff --git a/book/12-arenas.md b/book/12-arenas.md new file mode 100644 index 0000000..3cc3b2d --- /dev/null +++ b/book/12-arenas.md @@ -0,0 +1,803 @@ +# Capítulo 12 — Arenas (asignadores de región) + +> Capítulo avanzado. Se apoya en lo aprendido sobre punteros (`08`), `struct` (`09`) +> y memoria dinámica (`09-memoria-dinamica`). Leelo después de tener claros `malloc`, `free` +> y las operaciones con punteros. + +--- + +## 12.1 El problema: `malloc` para todo, `free` para todo + +Cuando recién empezás, la memoria dinámica parece simple: + +```c +Node *n = malloc(sizeof(Node)); // reservo +/* ... uso ... */ +free(n); // libero +``` + +Ese par `malloc`/`free` funciona bien para **un** objeto. El problema aparece cuando tu programa +necesita reservar **muchísimas cosas pequeñas**: nodos de un árbol, tokens de un parser, cadenas +temporales, estructuras de un request HTTP, filas de una tabla, etc. + +Ahí empezás a ver estos dolores: + +- **Seguís el rastro de cada `free`.** Si te olvidás de uno, tenés una *fuga de memoria* (memory leak). +- **Liberás dos veces** lo mismo y el programa explota (*double free*). +- **Usás memoria después de liberarla** (*use-after-free*): el peor bug, porque a veces "funciona". +- **Reservar mil veces** un pedacito tiene costo: cada `malloc` lleva *overhead* interno. +- **Manejar un error a mitad de camino** (por ejemplo, falló el `malloc` número 400 de 1000) + te obliga a liberar todo lo anterior. Código feo y frágil. + +La pregunta clave es: **¿y si en lugar de reservar y liberar objeto por objeto, reservo un bloque +grande y me olvido de liberar cada cosa individualmente?** + +--- + +## 12.2 ¿Qué es una arena? + +Una **arena** (también llamada *arena allocator*, *region allocator* o *bump allocator*) es una +estrategia de manejo de memoria donde: + +1. Reservás **un único bloque grande** de memoria de una sola vez (por ejemplo, 1 MiB). +2. Repartís ese bloque **de a poco**, avanzando un *cursor* cada vez que alguien pide memoria. +3. **No liberás objetos individuales.** Liberás **todo junto** al final, de una sola vez. +4. Para "empezar de cero" sin devolverle la memoria al sistema, **rebobinás el cursor** al inicio. + +La analogía más simple: pensá en una **hoja de papel rayada**. + +- La hoja entera es el bloque que reservaste. +- El renglón donde vas escribiendo es el **cursor/offset** (`offset`). +- Cada vez que escribís algo, bajás al renglón siguiente: **nunca reusás un renglón ya escrito**. +- Cuando querés empezar de nuevo, **raspás toda la hoja** (reiniciás el cursor a 0). No vas borrando + palabra por palabra. +- Al final, **tirás la hoja a la basura** (un solo `free`). + +Esa es toda la idea. Un cursor que solo avanza. Por eso se le dice *bump allocator*: "bump" = golpear +el cursor hacia adelante. + +### Ventajas + +- **Rapidísimo**: asignar es sumar al offset. Sin listas internas, sin buscar huecos. +- **Imposible olvidarse de un `free` individual**: hay uno solo, el de la arena completa. +- **Sin fragmentación visible**: al liberar todo junto, no quedan "agujeros" sueltos. +- **Manejo de errores simple**: si algo falla a mitad de camino, tirás la arena entera. + +### Desventajas / cuándo NO usarla + +- **No podés liberar un objeto puntual.** Si necesitás liberar *una cosa* y no todo, la arena no es + la herramienta (o necesitás una arena con "checkpoints", ver 12.5). +- **La memoria crece hasta el pico de uso.** El offset solo sube; no se recupera hasta el reset. +- **No sirve para estructuras de vida larga y tamaño impredecible** que necesitan liberarse por + partes (por ejemplo, una lista donde vas borrando el medio continuamente). + +### El caso de uso ideal + +Cuando tenés **muchos objetos pequeños con la misma vida útil** y los liberás **todos juntos**: + +- Parsear un documento: todos los tokens viven hasta que terminás de procesarlo. +- Procesar un request: toda la memoria temporal muere cuando respondés. +- Compilar / analizar texto: reservás muchísimo y tirás todo al final. +- Un juego: cada "nivel" o "frame" reserva y libera su propia arena. + +--- + +## 12.3 Un primer ejemplo mínimo (comentado) + +> Este es **material didáctico para entender el concepto**. Los ejercicios del 12.7 te van a pedir +> que lo **extiendas** con features que acá no están. No es la solución de ningún ejercicio. + +La estructura central tiene solo **tres campos**: + +```c +#include // size_t + +typedef struct { + unsigned char *buffer; // el bloque grande (donde vive toda la memoria) + size_t capacity; // cuántos bytes entran en total + size_t offset; // cuántos bytes ya se usaron (el cursor) +} Arena; +``` + +- `buffer` es el `malloc` grande. Es `unsigned char *` porque la arena es "bytes crudos": + ella no sabe ni le importa qué tipo vas a guardar. +- `capacity` es el tamaño total. Sirve para saber cuándo **no hay más lugar**. +- `offset` es el cursor. Empieza en 0. Solo avanza. + +### Crear y destruir la arena + +```c +#include // malloc, free + +// Reserva el bloque grande. Devuelve una Arena lista para usar. +Arena arena_create(size_t capacity) { + Arena a; + a.buffer = (unsigned char *)malloc(capacity); + a.capacity = capacity; + a.offset = 0; // cursor al principio + return a; +} + +// Libera TODO. Un solo free para toda la arena. +void arena_destroy(Arena *a) { + free(a->buffer); // chau bloque entero + a->buffer = NULL; + a->capacity = 0; + a->offset = 0; +} +``` + +Fijate algo importante: `arena_create` devuelve una `Arena` **por valor**. La estructura es chica +(3 campos), copiarla es barato, y así evitás que el llamador maneje memoria para la propia arena. + +### Repartir memoria: la única operación de asignación + +```c +// Pide `size` bytes a la arena. Devuelve un puntero a memoria SIN inicializar, +// o NULL si no queda espacio. NUNCA necesita saber cuándo liberar. +void *arena_alloc(Arena *a, size_t size) { + // 1) Si la arena no queda con lugar suficiente, no hay nada que hacer. + if (a->offset + size > a->capacity) { + return NULL; // "arena llena" + } + + // 2) El punto de reparto es el cursor actual. + void *ptr = a->buffer + a->offset; + + // 3) El cursor avanza. Esto es TODO lo que cuesta asignar: una suma. + a->offset += size; + + return ptr; +} +``` + +Esa suma es el corazón de la arena. `return a->buffer + a->offset;` es literalmente "te doy el +renglón donde está el cursor" y `a->offset += size;` es "bajo al renglón siguiente". + +Mirá que **nunca hay un `free` por objeto**. No existe `arena_free(ptr)` porque no hace falta: +cuando el llamador termine, llama a `arena_reset` o a `arena_destroy`. + +### Reiniciar sin liberar: reaprovechar el bloque + +```c +// Vuelve el cursor a cero. La memoria NO se devuelve al sistema, +// pero podés volver a repartirla como si estuviera vacía. +void arena_reset(Arena *a) { + a->offset = 0; +} +``` + +Esto es oro puro para bucles donde, en cada iteración, reservás un montón y al final ya no te +importa: en vez de reservar y liberar 1000 veces, reseteás la arena y listo. + +### Cómo se usa todo junto (ejemplo de uso comentado) + +```c +#include +#include + +typedef struct { int x, y; } Point; + +int main(void) { + // Una sola reserva grande para todo el programa. + Arena arena = arena_create(1024 * 1024); // 1 MiB + + // Reservo un Point (nótese: uso sizeof del TIPO, no cuántos bytes "a mano"). + Point *p = (Point *)arena_alloc(&arena, sizeof(Point)); + p->x = 3; + p->y = 4; + + // Reservo un arreglo de 100 enteros. + int *numbers = (int *)arena_alloc(&arena, 100 * sizeof(int)); + for (int i = 0; i < 100; i++) { + numbers[i] = i * i; + } + + printf("p = (%d, %d), numbers[42] = %d\n", p->x, p->y, numbers[42]); + + // NADA de free(p), free(numbers): se liberan TODOS con la arena. + arena_destroy(&arena); + + // Truco típico: reusar el mismo bloque en un bucle. + // for (int frame = 0; frame < 60; frame++) { + // ... reservar todo lo del frame ... + // render(); + // arena_reset(&arena); // listo para el próximo frame, sin malloc/free + // } + return 0; +} +``` + +Ese es el mínimo viable. Con esto ya podés resolver cosas reales. Lo que sigue son los detalles +que lo vuelven "de producción". + +--- + +## 12.4 Dos detalles que sí importan + +### 12.4.1 Alineación de memoria + +Los procesadores no leen cualquier dirección igual de rápido, y algunos **exigen** que ciertos tipos +empiecen en direcciones múltiplo de su tamaño (`int` a múltiplo de 4, `double` a múltiplo de 8, etc.). +Si repartís tamaños impares (por ejemplo 3 bytes), la próxima asignación puede quedar **desalineada** +y provocar comportamientos raros o lentitud. + +La solución es **redondear el tamaño hacia arriba** hasta un múltiplo de la alineación: + +```c +#define ARENA_ALIGN 8 // alineamos a 8 bytes + +// Redondea n hacia arriba al múltiplo de ARENA_ALIGN más cercano. +#define ARENA_ALIGN_UP(n) (((n) + (ARENA_ALIGN - 1)) & ~(size_t)(ARENA_ALIGN - 1)) + +void *arena_alloc(Arena *a, size_t size) { + size_t aligned = ARENA_ALIGN_UP(size); // reservamos un poco más, para alinear + if (a->offset + aligned > a->capacity) { + return NULL; + } + void *ptr = a->buffer + a->offset; + a->offset += aligned; + return ptr; +} +``` + +La parte `& ~(ARENA_ALIGN - 1)` es un truco de bits: como 8 es potencia de 2, "redondear hacia +arriba a múltiplo de 8" se hace con una máscara. Es válido **solo porque la alineación es potencia +de 2**. + +### 12.4.2 ¿Y si no entra lo que pedís? + +En este ejemplo simple, `arena_alloc` devuelve `NULL` si la arena está llena. Un diseño más +cómodo es que la arena sea una **cadena de regiones** (*chunks*): cuando una se llena, agrega otra +y sigue. Eso permite "crecer" sin límite práctico, a costa de más código. Los ejercicios te van a +pedir ambas variantes. + +--- + +## 12.5 Conceptos que vas a necesitar después + +- **Checkpoint / restore (arenas temporales o *scoped*).** Aunque la arena no libera objetos, + podés **guardar el offset actual** (`checkpoint`), reservar cosas temporales, y luego **volver** + a ese offset (`restore`) para descartar todo lo reservado desde el checkpoint. Es la forma de + tener "sub-arenas" que se liberan parcialmente, sin un solo `free`. + +- **Helpers tipados.** Escribir `arena_alloc(&a, sizeof(Point))` es tedioso y propenso a error de + tipo. Se acostumbra envolverlo en una función o macro que reciba el tipo: por ejemplo + `arena_alloc_type(&a, Point)`. Cuidado con las macros: el preprocesador juega sucio. + +- **Arena sobre memoria estática (stack/global).** Si sabés que nunca necesitás más de X bytes, + podés crear la arena sobre un `unsigned char buffer[X];` local, sin `malloc` **en absoluto**. + Rapidísimo y sin fugas. Limitación: no podés devolver punteros a esa memoria más allá de la + función. + +- **Arena para cadenas.** Como las cadenas son bytes, una operación muy útil es + `arena_strdup`: copiar un `const char *` a la arena y devolver tu propia copia. Perfecto para + tokens de un parser. + +- **Benchmark.** Con frecuencia una arena es **varias veces más rápida** que miles de `malloc`. + Medirlo es parte de entender el por qué. + +--- + +## 12.6 Errores comunes (leelos, te van a pasar) + +- **Guardar punteros después de un `reset`/`destroy` y usarlos.** Eso es *use-after-free* con otra + cara. La arena te da velocidad, no inmunidad. +- **Asumir que `arena_alloc` devuelve memoria en cero.** `malloc` no limpia; la arena tampoco. + Si necesitás ceros, acordate de inicializar (`memset`). +- **Olvidar la alineación** y sufrir *undefined behavior* con `double`/structs. +- **Creer que podés liberar un objeto del medio.** No se puede. Si lo necesitás, la estructura + equivocada es la arena. +- **Reservar la arena *dentro* de la función que la usa y devolverle un puntero a alguien.** El + bloque muere al salir de la función (si la creaste sobre el stack) o cuando la destruís. +- **`offset` desbordado.** Si no chequearas `offset + size > capacity`, escribirías fuera del + bloque: *buffer overflow* clásico. + +--- + +## 12.7 Ejercicios + +> Dificultad incremental, de **noob** a **pro**. Cada ejercicio declara sus conceptos objetivo. +> Resolvelos **usando esos conceptos**. Sin código de solución: el libro te da el problema, no la respuesta. +> +> Cada ejercicio trae un **escenario de prueba**: una secuencia concreta de operaciones y el +> **resultado esperado**. Usalo para **probar lo que vas construyendo** (imprimí el resultado real +> por consola y comparalo). El escenario describe *entrada/salida esperada*, nunca el algoritmo. + +### Ejercicio 12.1 — La arena mínima + +**Conceptos** · `struct`, `malloc`, `free`, punteros, `size_t` + +**Contexto** · Estás escribiendo la base de un asignador para un editor de texto que reserva +muchos objetos pequeños de corta vida. Antes de optimizar nada, necesitás la estructura mínima de +una arena. Simplificación: ignoramos alineación por ahora. + +**Consigna** · Implementá una estructura `Arena` y las funciones `arena_create`, +`arena_alloc` y `arena_destroy`. `arena_alloc` reparte el cursor y devuelve `NULL` si no hay +lugar. + +**Entrada** · Una capacidad en bytes y pedidos de tamaño variable. +**Salida** · Punteros válidos dentro del bloque, o `NULL` cuando la arena se llena. + +**Escenario de prueba** · +``` +capacity = 16 + +1. arena_create(16) -> arena con 16 bytes disponibles +2. arena_alloc(4) -> OK (base + 0) +3. arena_alloc(4) -> OK (base + 4) +4. arena_alloc(8) -> OK (base + 8) -> la arena queda llena +5. arena_alloc(1) -> NULL ("no hay espacio") +6. arena_destroy() -> el bloque completo se libera de una sola vez +``` +(El reuso sin `destroy` se ve en el 12.2.) + +**Identificadores sugeridos** · `Arena`, `arena_create`, `arena_alloc`, `arena_destroy`, `capacity`, `offset` + +**Criterios de aceptación** · +- `arena_alloc` devuelve direcciones **contiguas** y crecientes. +- No se escribe **nunca** fuera del bloque. +- Un único `free` en `arena_destroy` libera todo. +- `arena_alloc` devuelve `NULL` exactamente cuando no hay lugar. + +--- + +### Ejercicio 12.2 — Reset y reutilización + +**Conceptos** · punteros, ciclo de vida de memoria, `for` + +**Contexto** · Un motor de juego reserva, en cada frame, un montón de datos temporales. Reservar y +liberar 60 veces por segundo es un desperdicio. Simplificación: no hay gráficos, solo el ciclo de +reservas. + +**Consigna** · Agregá `arena_reset` que vuelva el cursor a 0 sin liberar el bloque. Simulá N +"frames": en cada uno reservá varios objetos, usalos, y reseteá la arena. La dirección **base** del +bloque no debe cambiar entre frames. + +**Entrada** · Cantidad de frames y cuántos bytes reserva cada frame. +**Salida** · Para cada frame, la dirección del primer objeto reservado (debe repetirse) y el uso total. + +**Escenario de prueba** · +``` +capacity = 16 ; frames = 3 ; cada frame reserva 4 + 4 + 4 = 12 + +frame 0: + alloc(4) -> base + 0 + alloc(4) -> base + 4 + alloc(4) -> base + 8 + reset -> offset vuelve a 0 +frame 1: + alloc(4) -> base + 0 (misma dirección que en el frame 0) + ... +frame 2: + alloc(4) -> base + 0 (otra vez la misma dirección) +``` +Esperado: las tres primeras direcciones son idénticas y el programa hace **un solo `malloc`** en total. + +**Identificadores sugeridos** · `arena_reset`, `frame`, `frame_count` + +**Restricciones / pistas** · Después del reset, `offset` vuelve a 0 pero `buffer` es el mismo. +Pensá qué pasa si guardás un puntero de un frame y lo usás en el siguiente. + +**Criterios de aceptación** · +- El bloque se reserva **una sola vez**. +- La primera dirección de cada frame es idéntica. +- No hay ningún `malloc` dentro del bucle de frames. + +--- + +### Ejercicio 12.3 — Alineación correcta + +**Conceptos** · aritmética de bits, alineación, punteros + +**Contexto** · Tu arena guarda mezclados `char`, `int` y `double`. Sin alineación, algunos accesos +fallan en ciertas arquitecturas. Simplificación: solo verificamos direcciones múltiplo de la +alineación. + +**Consigna** · Modificá `arena_alloc` para que **cada** puntero devuelto quede alineado a un +múltiplo de 8. Agregá una función `arena_is_aligned` que diga si una dirección dada es múltiplo de +8. + +**Entrada** · Una secuencia de pedidos de tamaños impares (3, 5, 1, 9...). +**Salida** · Las direcciones devueltas y un booleano de alineación para cada una. + +**Escenario de prueba** · +``` +capacity = 32 ; alineación = 8 + +1. arena_alloc(3) -> base + 0 (offset 0 -> 8) +2. arena_alloc(1) -> base + 8 (offset 8 -> 16) +3. arena_alloc(5) -> base + 16 (offset 16 -> 24) +4. arena_alloc(9) -> NULL (9 se redondea a 16 y solo quedan 8 bytes) +5. arena_alloc(8) -> base + 24 (offset 24 -> 32; arena llena) +6. arena_alloc(1) -> NULL +``` +Además: `arena_is_aligned(base + 0)`, `+ 8`, `+ 16`, `+ 24` -> todos `true` (múltiplo de 8). + +**Identificadores sugeridos** · `ARENA_ALIGN`, `arena_is_aligned`, `aligned` + +**Restricciones / pistas** · Redondeá el tamaño hacia arriba, no la dirección. Usá máscara de bits +(asumiendo alineación potencia de 2). + +**Criterios de aceptación** · +- Toda dirección devuelta es múltiplo de 8. +- Reservar 1 byte consume 8 de capacidad. +- No se pierde memoria fuera de lo necesario (no redondeás de más). + +--- + +### Ejercicio 12.4 — Helper tipado + +**Conceptos** · macros o funciones, `sizeof`, punteros genéricos (`void *`) + +**Contexto** · Escribir `arena_alloc(&a, sizeof(Point))` mil veces invita a errores de tipo. +Querés una forma cómoda y segura de pedir "un objeto de tal tipo". Simplificación: solo tipos de +tamaño fijo. + +**Consigna** · Ofrecé una abstracción `ARENA_ALLOC_TYPE(arena, Type)` (o una función equivalente) +que reserve exactamente `sizeof(Type)` bytes alineados y devuelva un puntero al tipo pedido, **sin +cast explícito** en el sitio de uso. + +**Entrada** · Distintos tipos (`int`, `double`, `Point`). +**Salida** · Punteros usables directamente como el tipo pedido. + +**Escenario de prueba** · +``` +capacity = 64 ; alineación = 8 ; Point = { int x; int y; } (8 bytes) + +1. int *n = ARENA_ALLOC_TYPE(&a, int); -> OK (usa bytes 0..7) +2. double *d = ARENA_ALLOC_TYPE(&a, double); -> OK (usa bytes 8..15) +3. Point *p = ARENA_ALLOC_TYPE(&a, Point); -> OK (usa bytes 16..23) +4. p->x = 3; p->y = 4; leer p->x / p->y -> 3 y 4, sin `cast` en el llamador +``` +Esperado: no aparece ningún `(Point *)` ni `sizeof(Point)` en el código que usa la macro. + +**Identificadores sugeridos** · `ARENA_ALLOC_TYPE`, `arena_alloc_type` + +**Restricciones / pistas** · Si usás macro, cuidá los paréntesis y evaluá el argumento una sola +vez. Si usás `void *` + cast, explicá por qué el cast es seguro. + +**Criterios de aceptación** · +- El resultado se usa sin `cast` en el llamador. +- El tamaño reservado es exactamente `sizeof(Type)` (más alineación). +- Funciona con structs, no solo con tipos primitivos. + +--- + +### Ejercicio 12.5 — Arena para cadenas: `arena_strdup` + +**Conceptos** · cadenas, `strlen`, `memcpy`, punteros, terminador `'\0'` + +**Contexto** · Un tokenizador lee muchísimos `const char *` de un buffer grande y necesita copias +propias que sobrevivan al buffer original. Simplificación: no implementás el tokenizador, solo la +copia. + +**Consigna** · Implementá `arena_strdup(Arena *a, const char *src)` que copie la cadena `src` +(incluido el `'\0'`) a la arena y devuelva el puntero a la copia. Si no hay lugar, `NULL`. + +**Entrada** · Una o más cadenas de origen. +**Salida** · Copias independientes cuyo contenido es igual al original. + +**Escenario de prueba** · +``` +capacity = 16 + +1. arena_strdup(&a, "arena") -> OK (6 bytes con terminador -> consumen 8) +2. arena_strdup(&a, "hola") -> OK (5 bytes con terminador -> consumen 8) +3. arena_strdup(&a, "x") -> NULL (la arena está llena) +4. se modifica el original "arena" (por ejemplo, su primer byte) + -> la copia sigue diciendo "arena"; strcmp(copia, "arena") == 0 +``` + +**Identificadores sugeridos** · `arena_strdup`, `src`, `dst` + +**Restricciones / pistas** · No olvides el byte del terminador: reservá `strlen(src) + 1`. No uses +`strdup` de la librería: la gracia es hacerlo vos. + +**Criterios de aceptación** · +- La copia es válida aun si el `src` original se modifica o deja de existir. +- Se copia el terminador. +- Devuelve `NULL` si la arena no tiene lugar. + +--- + +### Ejercicio 12.6 — Estadísticas de uso + +**Conceptos** · `struct`, punteros, aritmética, operadores relacionales + +**Contexto** · En desarrollo querés detectar si tu arena de 1 MiB está siempre al borde del +desborde, para dimensionarla bien. Simplificación: solo métricas, sin redimensionado. + +**Consigna** · Agregá funciones que reporten: bytes usados, bytes libres, y el **pico histórico** +de uso (el `offset` máximo alcanzado, aunque luego haya habido `reset`). + +**Entrada** · Una secuencia de reservas y resets. +**Salida** · `used`, `remaining`, `peak` correctos en cada momento. + +**Escenario de prueba** · +``` +capacity = 64 +operación used remaining peak +----------------- ----- --------- ---- +arena_create(64) 0 64 0 +arena_alloc(8) 8 56 8 +arena_alloc(16) 24 40 24 +arena_reset() 0 64 24 <- peak NO baja +arena_alloc(4) 8 56 24 +arena_alloc(40) 48 16 48 +arena_reset() 0 64 48 +``` +Esperado: en toda fila `used + remaining == 64`, y `peak` solo puede subir. + +**Identificadores sugeridos** · `arena_used`, `arena_remaining`, `peak_usage` + +**Restricciones / pistas** · `peak` **no** puede bajar con `reset`: es un máximo histórico. +Distinguí "usado ahora" de "usado alguna vez". + +**Criterios de aceptación** · +- `used + remaining == capacity` en todo momento. +- Tras varios resets con usos distintos, `peak` refleja el mayor uso jamás alcanzado. +- Ninguna función estadística reserva ni libera memoria. + +--- + +### Ejercicio 12.7 — Checkpoints (arenas temporales) + +**Conceptos** · punteros, copia de estado, `size_t`, anidamiento lógico + +**Contexto** · Dentro del procesamiento de un documento, cada sección necesita memoria temporal +que podés tirar al terminar la sección, **sin** tirar lo acumulado antes. Simplificación: secciones +anidadas de profundidad acotada. + +**Consigna** · Implementá `arena_checkpoint(Arena *a)` que devuelva el offset actual, y +`arena_restore(Arena *a, size_t checkpoint)` que vuelva el cursor a ese punto (descartando lo +reservado después). Usalos para procesar secciones temporales anidadas. + +**Entrada** · Una estructura de secciones que reservan y destruyen memoria temporal. +**Salida** · Al restaurar, las reservas posteriores al checkpoint dejan de estar "vivas". + +**Escenario de prueba** · +``` +capacity = 32 + +1. cp0 = arena_checkpoint(&a) -> 0 +2. arena_alloc(8) -> base + 0 (offset 8) +3. arena_alloc(8) -> base + 8 (offset 16) +4. cp1 = arena_checkpoint(&a) -> 16 +5. arena_alloc(8) -> base + 16 (offset 24) +6. arena_restore(&a, cp1) -> offset 16 (se descartan los 8 del paso 5) +7. arena_alloc(16) -> base + 16 (offset 32) -> reutiliza el hueco +8. arena_restore(&a, cp0) -> offset 0 (se descarta todo) +9. arena_alloc(32) -> base + 0 (offset 32) -> ahora sí entra +``` +Ojo: el puntero devuelto en el paso 5 queda inválido después del paso 6. + +**Identificadores sugeridos** · `arena_checkpoint`, `arena_restore`, `checkpoint` + +**Restricciones / pistas** · Este es tu primer "liberar parcialmente" sin `free`. Cuidado: los +punteros reservados después del checkpoint quedan inválidos al restaurar. + +**Criterios de aceptación** · +- Restaurar a un checkpoint rehabilita exactamente la capacidad liberada. +- Checkpoints anidados funcionan (restaurar al más interno, luego al externo). +- Restaurar es O(1): solo escribe el offset. + +--- + +### Ejercicio 12.8 — Arena que crece (cadena de regiones) + +**Conceptos** · `malloc`, `free`, structs enlazados, manejo de errores + +**Contexto** · No sabés de antemano cuánta memoria necesita un request: a veces 4 KiB, a veces +50 MiB. Una arena de tamaño fijo te obliga a elegir mal. Simplificación: crecimiento por duplicación. + +**Consigna** · Rediseñá la arena como una **cadena de regiones** (`chunk`): cuando una región se +llena, se reserva otra (por ejemplo del doble de tamaño) y se sigue repartiendo ahí. +`arena_alloc` nunca devuelve `NULL` salvo que la reserva del sistema falle. + +**Entrada** · Una larga secuencia de reservas que superan con creces la capacidad inicial. +**Salida** · Todas las reservas exitosas; los punteros viven hasta el `arena_destroy`. + +**Escenario de prueba** · +``` +región inicial = 16 bytes ; crecimiento = x2 (la nueva región nunca es menor a lo pedido) + +1. arena_alloc(12) -> OK (entra en la región 1; quedan pocos bytes) +2. arena_alloc(12) -> OK (no entra en la región 1 -> se crea la región 2, de 32) +3. arena_alloc(100) -> OK (no entra en ninguna -> se crea la región 3, >= 100) +4. se leen los datos de los pasos 1 y 2 -> siguen intactos +5. arena_destroy() -> se liberan las 3 regiones (cada una exactamente una vez) +``` + +**Identificadores sugeridos** · `Chunk`, `arena_grow`, `next`, `chunk_size` + +**Restricciones / pistas** · Elegí una política de crecimiento y **justificala**. `arena_destroy` +debe liberar **todas** las regiones, no solo la actual. Pensá el manejo de error si falla `malloc`. + +**Criterios de aceptación** · +- Pedir mucho más que la capacidad inicial no falla. +- `arena_destroy` libera cada región exactamente una vez. +- Los punteros ya entregados siguen válidos mientras la arena viva. +- El crecimiento no corrompe datos previos. + +--- + +### Ejercicio 12.9 — Arena como almacén de una colección + +**Conceptos** · structs, arreglos dinámicos, punteros, `arena_alloc` + +**Contexto** · Un sistema de contactos lee N registros de un archivo (nombre + teléfono) y los +guarda en memoria temporal para procesarlos. Simplificación: lectura simulada, sin archivos reales +todavía. + +**Consigna** · Usando una arena (no un `malloc` por contacto), cargá una lista de contactos y +luego calculá: cuántos hay, la longitud total de los nombres, y buscá un nombre. Liberá **todo** +con un solo `arena_destroy`. + +**Entrada** · N contactos con nombre (string) y teléfono. +**Salida** · Cantidad, suma de longitudes, resultado de la búsqueda, y confirmación de que la +memoria se libera de una sola vez. + +**Escenario de prueba** · +``` +capacity = 4096 +entrada: + { "Ana", "111" } + { "Bruno", "222" } + { "Carla", "333" } + +salida esperada: + cantidad = 3 + suma de letras de nombres = 3 + 5 + 5 = 13 + buscar "Bruno" -> encontrado, teléfono 222 + buscar "Zoe" -> no encontrado + arena_destroy() -> TODA la memoria se libera de una sola vez +``` + +**Identificadores sugeridos** · `Contact`, `ContactList`, `contacts`, `load_contacts` + +**Restricciones / pistas** · Los nombres también van en la arena (`arena_strdup`). Si el arreglo de +contactos puede crecer, decidí entre pedir capacidad fija o usar la arena que crece (12.8). + +**Criterios de aceptación** · +- **Cero** `malloc`/`free` por contacto: toda la memoria sale de la arena. +- Un único `arena_destroy` limpia todo. +- La búsqueda recorre correctamente los datos guardados en la arena. + +--- + +### Ejercicio 12.10 — Arena sin `malloc` (buffer estático) + +**Conceptos** · arreglos locales, duración de almacenamiento, punteros, límites + +**Contexto** · En un sistema embebido no querés tocar el heap: la fragmentación puede +colgar el equipo. Reservás un buffer fijo en memoria y vivís con él. Simplificación: no hay +embebido real, solo un buffer estático local. + +**Consigna** · Creá una arena sobre un `unsigned char buffer[4096];` **local** (sin `malloc`). +Documentá con claridad por qué **no** podés devolver punteros a esa memoria desde la función que +la crea, y demostralo con un caso que **debe fallar** (explicado en comentarios, sin ejecutarlo). + +**Entrada** · Reservas de distinto tamaño dentro de la función. +**Salida** · Direcciones dentro del rango `[buffer, buffer + 4096)`. + +**Escenario de prueba** · +``` +unsigned char buffer[4096]; (variable local) ; alineación = 8 + +1. arena_init_on(&a, buffer, 4096) -> arena sobre memoria prestada +2. arena_alloc(1000) -> OK, dentro de [buffer, buffer + 4096) +3. arena_alloc(3000) -> OK (offset 4000) +4. arena_alloc(96) -> OK (llenó los 4096) +5. arena_alloc(1) -> NULL +6. arena_destroy() -> NO libera `buffer` (no es propia) + +caso que DEBE fallar (explicado en comentarios, no ejecutado): + una función crea la arena local y devuelve un puntero a un objeto de `buffer`; + al volver, `buffer` ya no existe -> puntero colgante (dangling pointer). +``` + +**Identificadores sugeridos** · `buffer`, `ARENA_STACK_SIZE`, `arena_init_on` + +**Restricciones / pistas** · La arena apunta al buffer, no lo posee: `arena_destroy` **no** debe +hacer `free`. Refactorizá para que la arena funcione tanto con memoria propia como prestada. + +**Criterios de aceptación** · +- No se usa `malloc` en ningún lado. +- `arena_destroy` distingue "memoria propia" de "memoria prestada" y no libera la prestada. +- Se explica y se evidencia el peligro de devolver punteros fuera de alcance. + +--- + +### Ejercicio 12.11 — Arena para un árbol sintáctico (pro) + +**Conceptos** · structs con punteros, recursión, arena, cadenas, `enum` + +**Contexto** · Escribir un mini-analizador de expresiones aritméticas (`3 + 4 * (2 - 1)`) donde +cada nodo del árbol se reserva en una arena y **todo** se libera de golpe al terminar. Un caso +clásico de "muchos nodos pequeños con la misma vida útil". + +**Consigna** · Definí nodos (`number`, `binop`) e implementá el almacenamiento de nodos y de +etiquetas de operador usando la arena. Recorré el árbol (recursivo) para imprimir la expresión en +notación prefija y calculá el total de nodos y de bytes usados. Liberá con un solo `arena_destroy`. + +**Entrada** · Tokens de una expresión (o la expresión ya tokenizada). +**Salida** · La expresión en prefijo, el total de nodos y los bytes de arena consumidos. + +**Escenario de prueba** · +``` +entrada (tokens): 3 + ( 4 - 1 ) + +salida esperada: + prefijo: (+ 3 (- 4 1)) + nodos: 5 + bytes: la suma de los sizeof reservados + (por ejemplo, si sizeof(Node) == 16 -> 5 * 16 = 80) +``` +Sin `malloc` por nodo; un único `arena_destroy` al terminar. + +**Identificadores sugeridos** · `Node`, `NodeKind`, `make_number`, `make_binop`, `print_prefix` + +**Restricciones / pistas** · Cada nodo y cada etiqueta salen de la arena, sin `malloc`. Pensá la +vida útil: todos los nodos viven hasta terminar el análisis. + +**Criterios de aceptación** · +- Ningún nodo se reserva con `malloc` directo. +- El recorrido prefijo es correcto para expresiones anidadas. +- Un solo `arena_destroy` libera todo el árbol. +- Los conteos de nodos y bytes son exactos. + +--- + +### Ejercicio 12.12 — Arena vs `malloc`: medí y decidí (pro) + +**Conceptos** · arena, `malloc`/`free`, medición de tiempo (`clock`), análisis de complejidad + +**Contexto** · Tu equipo duda de si vale la pena migrar a arenas. Nada convence como un número. +Simplificación: una medición simple en un solo proceso, sin herramientas externas. + +**Consigna** · Implementá dos versiones del **mismo** workload (por ejemplo, crear y liberar +500.000 objetos pequeños): una con `malloc`/`free` por objeto y otra con una arena. Medí el tiempo +de cada una con `clock()` y reportá ambas más la mejora destructiva (cuántas veces más rápida). +Además, escribí un **análisis** (en comentarios o en el informe) de **cuándo** conviene cada +enfoque, incluyendo un contraejemplo donde la arena **pierde**. + +**Entrada** · Número de objetos y tamaño de cada uno. +**Salida** · Tiempos de la versión con `malloc` y con arena, y la relación entre ellos. + +**Escenario de prueba** · +``` +entrada: N = 500.000 objetos de 16 bytes cada uno + +salida esperada (los tiempos son orientativos): + malloc: 0.180 s + arena: 0.021 s + la arena es ~8.5x más rápida + +contraejemplo (también forma parte del ejercicio): + reservar 500.000 objetos y luego liberar la mitad "del medio" y seguir trabajando + -> con malloc es natural; con una arena NO se puede -> la arena es la decisión equivocada +``` + +**Identificadores sugeridos** · `bench_malloc`, `bench_arena`, `clock` + +**Restricciones / pistas** · El workload debe ser **idéntico** en ambas versiones para que la +comparación valga. Cuidá que el compilador no optimice de más (los "objetos" deben tener efecto +observable). El contraejemplo es obligatorio: medir no es lo mismo que entender. + +**Criterios de aceptación** · +- Ambas versiones hacen el **mismo** trabajo. +- La medición se repite y se reporta de forma honesta (sin descartar resultados incómodos). +- El análisis explica el resultado, no solo lo muestra. +- Se identifica al menos un escenario donde la arena es la decisión **equivocada**. + +--- + +## 12.8 Cierre + +Una arena no es magia: es **disciplina sobre la duración de los datos**. Si entendés que un +conjunto de objetos **nace, vive y muere junto**, ganás velocidad y simplicidad enormes. Si te +olvidás de esa condición, la arena te muerde. + +Regla mental para llevarte: **una arena por "unidad de trabajo"** (un request, un frame, un +documento, un análisis). Cuando la unidad termina, la arena se resetea o se destruye. Nada más.