c749ba4116
- Adjust page numbers in the PDF to reflect content changes. - Correct exercise references and improve explanations for arena concepts. - Enhance code examples for clarity and consistency. - Add detailed descriptions for memory alignment and checkpoint functionalities. - Include additional exercises focusing on heterogeneous records and performance comparisons.
944 lines
41 KiB
Markdown
944 lines
41 KiB
Markdown
# 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.8 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.
|
|
|
|
> **¿Una arena guarda un solo tipo o varios?** Una arena **no tiene tipo**: guarda *bytes*. El tipo
|
|
> no vive en la arena, sino en **cómo vos interpretás** el puntero que te devolvió `arena_alloc`.
|
|
> Por eso una **misma** arena puede contener, al mismo tiempo, tipos distintos —`int`, `double`,
|
|
> structs, cadenas—, cada uno en su propio hueco, siempre que respetes la **alineación** (ver 12.4.1).
|
|
> El ejemplo de la `main` en 12.3 ya lo hace: pide un `Point` y, del **mismo** bloque, un arreglo de
|
|
> 100 `int`. Lo único que la arena **no** hace es acordarse por vos a qué tipo corresponde cada
|
|
> puntero: eso queda de tu lado.
|
|
|
|
### 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`.
|
|
|
|
> **Reservar es solo el primer paso.** El puntero que devuelve `arena_alloc` es una dirección
|
|
> común y corriente: guardás y recuperás datos por él (`*p = ...`, `x = *p`), igual que con
|
|
> cualquier otro puntero. La arena no es una caja fuerte ni un tipo especial: es memoria normal
|
|
> que se libera en bloque. Por eso, en los ejercicios no alcanza con pedir bytes — hay que
|
|
> **escribir** en ese espacio y **leerlo de vuelta** para comprobar que sirve.
|
|
|
|
### 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 Dato de color: arenas en programas que probablemente uses
|
|
|
|
No es una curiosidad académica: este truco está metido en software masivo que usás todos los días.
|
|
Cada caso nombra **qué parte** del programa la usa y **para qué**, como simple información (no hace
|
|
falta entenderlos a fondo):
|
|
|
|
- **Google Chrome, Microsoft Edge, Node.js** (los tres corren el motor V8): V8 reserva los nodos del
|
|
*parser* y del compilador de JavaScript en "zonas". La documentación lo dice tal cual: los bloques
|
|
"no se pueden liberar por separado... se liberan todos juntos en una sola operación", y las zonas
|
|
se usan "en todo el parser y los compiladores de V8". Terminado el script, la zona entera se tira
|
|
de una sola vez.
|
|
- **Mozilla Firefox** (motor SpiderMonkey): usa un asignador llamado `LifoAlloc`, que su propia doc
|
|
define como un *bump allocator*: "asignación rápida, pero incapaz de liberar asignaciones
|
|
individuales". Se usa mientras se parsea y se compila (JIT) el JavaScript, y trae `mark`/`release`
|
|
— el mismo *checkpoint* / *restore* del ejercicio 12.8.
|
|
- **Juegos hechos con Unity** (miles, y gran parte del mercado móvil): Unity documenta
|
|
`Allocator.Temp` como un *stack allocator* del que "cada frame, el hilo principal crea un
|
|
allocator Temp y lo libera por completo al final del frame". Es exactamente el `arena_reset` por
|
|
frame del ejercicio 12.2, pero corriendo 60 veces por segundo.
|
|
- **Python** (CPython): el asignador de objetos chicos usa bloques llamados, literalmente, "arenas"
|
|
(de 256 KiB). El archivo se llama, sin vueltas, `Objects/obmalloc.c`.
|
|
- **Java y Android** (y todo lo que corre encima, de apps móviles a Minecraft): la máquina virtual
|
|
asigna objetos nuevos casi "empujando" un puntero en la generación joven (el famoso TLAB), sin
|
|
buscar huecos. El aseo de la basura, otra vez, se hace en bloque.
|
|
|
|
De los que mencionaste —Word, Excel, Photoshop, Notepad, Paint— no tengo una fuente que afirme
|
|
"acá usan una arena" y no te lo voy a inventar. Pero el concepto es el mismo: la próxima vez que
|
|
leas "pool", "zone", "arena" o "region allocator" en el código de un programa grande, ya sabés de
|
|
qué hablan.
|
|
|
|
---
|
|
|
|
## 12.8 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. Después de reservar, **guardá un valor por el puntero** (por ejemplo, un `int`) y **leelo de
|
|
vuelta**: el espacio sirve solo si podés escribir y recuperar datos en él.
|
|
|
|
**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; y el valor guardado se recupera igual.
|
|
|
|
**Escenario de prueba** ·
|
|
```
|
|
capacity = 16
|
|
|
|
1. arena_create(16) -> arena con 16 bytes disponibles
|
|
2. int *p = arena_alloc(4) -> OK (base + 0) ; *p = 7 ; leer *p -> 7
|
|
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.
|
|
- Se guarda un valor por el puntero y se recupera intacto (el espacio es **usable**, no solo reservado).
|
|
- 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, **escribí valores en ellos y leelos de vuelta**, y
|
|
recién después 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:
|
|
int *a = alloc(4); *a = 10; leer *a -> 10 (offset 0)
|
|
int *b = alloc(4); *b = 20; leer *b -> 20 (offset 4)
|
|
reset -> offset vuelve a 0
|
|
frame 1:
|
|
int *a = alloc(4) -> base + 0 (misma dirección que en el frame 0)
|
|
*a = 30; leer *a -> 30 (el valor viejo ya no está: hueco reutilizado)
|
|
frame 2:
|
|
int *a = 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.
|
|
- Cada frame guarda y recupera sus valores correctamente del espacio reutilizado.
|
|
- 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)
|
|
escribir 'A' en base + 0 y leer -> 'A'
|
|
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.
|
|
- Se escribe y se lee un valor desde un hueco alineado.
|
|
- 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 — Una arena, muchos tipos
|
|
|
|
**Conceptos** · `void *`, `cast`, `sizeof`, alineación, punteros
|
|
|
|
**Contexto** · Un equipo de telemetría junta lecturas de sensores de distinto tipo —temperatura
|
|
(`double`), cantidad de eventos (`int`), estado (`char`)— y, por velocidad, las guarda **juntas** en
|
|
un único bloque temporal en lugar de reservar cada una por separado. Simplificación: no hay
|
|
sensores reales, solo los valores mezclados.
|
|
|
|
**Consigna** · Usando **una sola** arena, reservá y guardá tres valores de **tipos distintos** (un
|
|
`double`, un `int`, un `char`). Escribí cada valor, leelos de vuelta y comprobá que coinciden.
|
|
Verificá además que cada puntero quede alineado a su tipo.
|
|
|
|
**Entrada** · Tres valores de tipos distintos.
|
|
**Salida** · Los tres valores leídos correctamente desde la **misma** arena, y la comprobación de alineación.
|
|
|
|
**Escenario de prueba** ·
|
|
```
|
|
capacity = 64 ; alineación = 8
|
|
|
|
1. double *t = arena_alloc(&a, sizeof(double)); -> base + 0 (offset 0 -> 8)
|
|
2. int *c = arena_alloc(&a, sizeof(int)); -> base + 8 (offset 8 -> 16)
|
|
3. char *s = arena_alloc(&a, sizeof(char)); -> base + 16 (offset 16 -> 24)
|
|
4. *t = 36.5; *c = 42; *s = 'A';
|
|
leer de vuelta -> 36.5, 42, 'A'
|
|
```
|
|
Los tres punteros apuntan a huecos distintos del **mismo** bloque, y cada uno es múltiplo de 8.
|
|
|
|
**Identificadores sugeridos** · `temperature`, `eventCount`, `status`, `arena_alloc`
|
|
|
|
**Restricciones / pistas** · Un `arena_alloc` por valor, todos de la misma arena. No los envuelvas
|
|
en un struct: la gracia es ver tipos sueltos conviviendo. Cuidá el `cast` al tipo correcto al leer.
|
|
|
|
**Criterios de aceptación** ·
|
|
- Los tres valores viven en **una sola** arena.
|
|
- Se leen de vuelta con el tipo correcto y coinciden con lo escrito.
|
|
- Cada puntero queda alineado a 8 (y por lo tanto a su tipo).
|
|
- Un único `arena_destroy` libera todo.
|
|
|
|
---
|
|
|
|
### Ejercicio 12.6 — 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.7 — 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.8 — 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. int *x = arena_alloc(8); *x = 100; leer *x -> 100 (base + 0, offset 8)
|
|
3. arena_alloc(8) -> base + 8 (offset 16)
|
|
4. cp1 = arena_checkpoint(&a) -> 16
|
|
5. int *tmp = arena_alloc(8); *tmp = 999 (base + 16, offset 24)
|
|
6. arena_restore(&a, cp1) -> offset 16 (se descarta `tmp`)
|
|
7. leer *x -> 100 (sobrevive al restore); usar `tmp` ahora es inválido
|
|
8. arena_alloc(16) -> base + 16 (offset 32) -> reutiliza el hueco
|
|
9. arena_restore(&a, cp0) -> offset 0 (se descarta todo)
|
|
10. 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.
|
|
- Lo reservado **antes** del checkpoint conserva su valor tras restaurar.
|
|
- Checkpoints anidados funcionan (restaurar al más interno, luego al externo).
|
|
- Restaurar es O(1): solo escribe el offset.
|
|
|
|
---
|
|
|
|
### Ejercicio 12.9 — 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.10 — 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.9).
|
|
|
|
**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.11 — 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. char *s = arena_alloc(1000); copiar "hola arena" y leer -> "hola arena"
|
|
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 guarda y se lee una cadena desde el buffer prestado.
|
|
- Se explica y se evidencia el peligro de devolver punteros fuera de alcance.
|
|
|
|
---
|
|
|
|
### Ejercicio 12.12 — 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.13 — 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**.
|
|
|
|
---
|
|
|
|
### Ejercicio 12.14 — Registros heterogéneos con unión etiquetada (pro)
|
|
|
|
**Conceptos** · `enum`, `union`, punteros, arena, arreglo dinámico, `switch`
|
|
|
|
**Contexto** · Un sistema de auditoría registra eventos de distinto tipo —inicio de sesión (con
|
|
usuario), compra (con monto) y error (con código)— en un mismo flujo temporal. Cada evento tiene
|
|
una **forma distinta**, pero todos deben vivir juntos y liberarse de una sola vez. Simplificación:
|
|
los eventos se generan a mano, sin persistencia.
|
|
|
|
**Consigna** · Definí un tipo `Event` con un `enum kind` y una `union` de cargas útiles (usuario
|
|
como cadena, monto como `double`, código como `int`). Creá varios eventos de **distinto tipo** en
|
|
una **misma** arena (las cadenas también salen de la arena), recorrelos y procesalos según su
|
|
etiqueta (`switch`), e imprimí un resumen. Liberá todo con un solo `arena_destroy`.
|
|
|
|
**Entrada** · Una lista de eventos de tipos variados.
|
|
**Salida** · Cada evento procesado según su tipo, un total de eventos y un conteo por tipo.
|
|
|
|
**Ejemplo** ·
|
|
```
|
|
entrada:
|
|
LOGIN usuario = "ana"
|
|
PURCHASE monto = 19.99
|
|
ERROR código = 404
|
|
|
|
salida esperada:
|
|
LOGIN ana
|
|
PURCHASE 19.99
|
|
ERROR 404
|
|
total eventos = 3
|
|
login = 1, purchase = 1, error = 1
|
|
```
|
|
|
|
**Identificadores sugeridos** · `Event`, `EventKind`, `USER_MAX`, `make_login`, `make_purchase`, `make_error`, `process_events`
|
|
|
|
**Restricciones / pistas** · La `union` ahorra memoria: todos los payloads comparten el mismo
|
|
espacio y el `enum` te dice cuál es válido en cada momento. Reservá el evento **y** su cadena en la
|
|
arena. Nunca leas el campo de la `union` que no corresponde al `kind`. Sin `malloc` por evento.
|
|
|
|
**Criterios de aceptación** ·
|
|
- Un solo `arena_destroy` libera los eventos y sus cadenas.
|
|
- El procesamiento respeta la etiqueta (`switch` sobre `kind`), sin leer campos inválidos.
|
|
- El conteo por tipo y el total de eventos son exactos.
|
|
- No hay `malloc`/`free` por evento.
|
|
|
|
---
|
|
|
|
## 12.9 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.
|