Add AGENTS.md for project guidelines and introduce Chapter 12 on Arena Allocators
- Created AGENTS.md to outline rules and structure for contributors. - Added Chapter 12 on Arena Allocators, covering memory management strategies, implementation details, and exercises for practical understanding.
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"fingerprint": "d3c21ce024c6ffe6e44e8dd85565ff6f6df97bc4"
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Skill Registry — c-programming
|
||||||
|
|
||||||
|
<!-- Auto-generated by gentle-ai skill-registry refresh. Run `gentle-ai skill-registry refresh --force` to regenerate. -->
|
||||||
|
|
||||||
|
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`.
|
||||||
@@ -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**.
|
||||||
@@ -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 <stddef.h> // 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 <stdlib.h> // 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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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.
|
||||||
Reference in New Issue
Block a user