diff --git a/.atl/.skill-registry.cache.json b/.atl/.skill-registry.cache.json index 2c4899d..22c19c4 100644 --- a/.atl/.skill-registry.cache.json +++ b/.atl/.skill-registry.cache.json @@ -1,3 +1,3 @@ { - "fingerprint": "d3c21ce024c6ffe6e44e8dd85565ff6f6df97bc4" + "fingerprint": "60c5eae54c7e739d5e839304b313804bc547b807" } diff --git a/.atl/skill-registry.md b/.atl/skill-registry.md index b81d21e..36ae5a4 100644 --- a/.atl/skill-registry.md +++ b/.atl/skill-registry.md @@ -6,6 +6,7 @@ Last updated: 2026-10-13 ## Sources scanned +- .opencode\skills - C:\Users\horacio\.agents\skills - C:\Users\horacio\.config\opencode\skills - C:\Users\horacio\.claude\skills @@ -23,6 +24,7 @@ Last updated: 2026-10-13 | Skill | Trigger / description | Scope | Path | | --- | --- | --- | --- | +| `book-pdf` | Generar o regenerar los PDF de los capítulos del libro de C en dist/, con diseño consistente (resaltado de sintaxis, fuente mono para código, notas con fondo de color). Trigger - pdf, generar pdf, exportar capítulo, dist, md-to-pdf, print.css, "convertir a pdf", "rehacer pdf". | project | `C:\Users\horacio\Desarrollo\c-programming\.opencode\skills\book-pdf\SKILL.md` | | `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` | diff --git a/book/12-arenas.md b/book/12-arenas.md index 3cc3b2d..81185f0 100644 --- a/book/12-arenas.md +++ b/book/12-arenas.md @@ -84,7 +84,7 @@ Cuando tenés **muchos objetos pequeños con la misma vida útil** y los liberá ## 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 +> 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**: @@ -104,6 +104,14 @@ typedef struct { - `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 @@ -157,6 +165,12 @@ renglón donde está el cursor" y `a->offset += size;` es "bajo al renglón sigu 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 @@ -295,7 +309,39 @@ pedir ambas variantes. --- -## 12.7 Ejercicios +## 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. @@ -314,17 +360,18 @@ 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. +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. +**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. arena_alloc(4) -> OK (base + 0) +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") @@ -336,6 +383,7 @@ capacity = 16 **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. @@ -351,8 +399,8 @@ liberar 60 veces por segundo es un desperdicio. Simplificación: no hay gráfico 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. +"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. @@ -362,15 +410,14 @@ bloque no debe cambiar entre frames. 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 + 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: - alloc(4) -> base + 0 (misma dirección que en el frame 0) - ... + 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: - alloc(4) -> base + 0 (otra vez la misma dirección) + 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. @@ -382,6 +429,7 @@ 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. --- @@ -406,6 +454,7 @@ múltiplo de 8. Agregá una función `arena_is_aligned` que diga si una direcci 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) @@ -422,6 +471,7 @@ Además: `arena_is_aligned(base + 0)`, `+ 8`, `+ 16`, `+ 24` -> todos `true` (m **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). --- @@ -464,7 +514,48 @@ vez. Si usás `void *` + cast, explicá por qué el cast es seguro. --- -### Ejercicio 12.5 — Arena para cadenas: `arena_strdup` +### 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'` @@ -501,7 +592,7 @@ capacity = 16 --- -### Ejercicio 12.6 — Estadísticas de uso +### Ejercicio 12.7 — Estadísticas de uso **Conceptos** · `struct`, punteros, aritmética, operadores relacionales @@ -541,7 +632,7 @@ Distinguí "usado ahora" de "usado alguna vez". --- -### Ejercicio 12.7 — Checkpoints (arenas temporales) +### Ejercicio 12.8 — Checkpoints (arenas temporales) **Conceptos** · punteros, copia de estado, `size_t`, anidamiento lógico @@ -561,14 +652,15 @@ reservado después). Usalos para procesar secciones temporales anidadas. capacity = 32 1. cp0 = arena_checkpoint(&a) -> 0 -2. arena_alloc(8) -> base + 0 (offset 8) +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. 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 +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. @@ -579,12 +671,13 @@ 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.8 — Arena que crece (cadena de regiones) +### Ejercicio 12.9 — Arena que crece (cadena de regiones) **Conceptos** · `malloc`, `free`, structs enlazados, manejo de errores @@ -622,7 +715,7 @@ debe liberar **todas** las regiones, no solo la actual. Pensá el manejo de erro --- -### Ejercicio 12.9 — Arena como almacén de una colección +### Ejercicio 12.10 — Arena como almacén de una colección **Conceptos** · structs, arreglos dinámicos, punteros, `arena_alloc` @@ -657,7 +750,7 @@ salida esperada: **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). +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. @@ -666,7 +759,7 @@ contactos puede crecer, decidí entre pedir capacidad fija o usar la arena que c --- -### Ejercicio 12.10 — Arena sin `malloc` (buffer estático) +### Ejercicio 12.11 — Arena sin `malloc` (buffer estático) **Conceptos** · arreglos locales, duración de almacenamiento, punteros, límites @@ -686,7 +779,7 @@ la crea, y demostralo con un caso que **debe fallar** (explicado en comentarios, 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) +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 @@ -705,11 +798,12 @@ hacer `free`. Refactorizá para que la arena funcione tanto con memoria propia c **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.11 — Arena para un árbol sintáctico (pro) +### Ejercicio 12.12 — Arena para un árbol sintáctico (pro) **Conceptos** · structs con punteros, recursión, arena, cadenas, `enum` @@ -749,7 +843,7 @@ vida útil: todos los nodos viven hasta terminar el análisis. --- -### Ejercicio 12.12 — Arena vs `malloc`: medí y decidí (pro) +### Ejercicio 12.13 — Arena vs `malloc`: medí y decidí (pro) **Conceptos** · arena, `malloc`/`free`, medición de tiempo (`clock`), análisis de complejidad @@ -793,7 +887,53 @@ observable). El contraejemplo es obligatorio: medir no es lo mismo que entender. --- -## 12.8 Cierre +### 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 diff --git a/dist/12-arenas.pdf b/dist/12-arenas.pdf index d7c9841..2429152 100644 Binary files a/dist/12-arenas.pdf and b/dist/12-arenas.pdf differ