Update arena chapter in the programming exercises book:
- 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.
This commit is contained in:
+171
-31
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user