|
|
|
@@ -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.
|