diff --git a/PROGRESO.md b/PROGRESO.md
index 3c692a8..d7ffc6e 100644
--- a/PROGRESO.md
+++ b/PROGRESO.md
@@ -4,6 +4,11 @@
- **Fase 2**: buffer dinámico. La Fase 1 está cerrada en Fedora y en Windows
(2026-10-05).
+- 2026-10-06 (Fedora): antes del 2.3b, repaso de punteros. Le costaba qué
+ viaja a una función, `*` frente a `->`, la pila frente al montón y restar en
+ hex. Se creó `docs/02-pointers-visual.html` (interactivo). Pendiente de que
+ conteste: A) por qué `&buffer_a - &buffer` da 4; B) cuántos bytes copia su
+ `append` actual con `data_b`.
- Paso actual: 2.3b. `hello.c` ya usa bien `goto cleanup` (punteros a `NULL`
uno por línea, `result`, un solo `return`), sin avisos con gcc ni clang, y
Valgrind limpio. Falta `escpos_buffer_append`: el parámetro con la cantidad,
@@ -109,6 +114,14 @@
que las tres valían `NULL` (solo la última). Lo detectó clang con
`-Wsometimes-uninitialized`. Ahora declara una variable por línea.
- Variables locales sin inicializar: creía que valían `NULL` como en Go.
+- `sizeof`: el 2026-10-06 dijo que `sizeof buffer` (un puntero) da 1 byte; da
+ 8 en 64 bits. Y lo veía como algo que "va a la casa" mientras se ejecuta,
+ cuando se calcula al compilar a partir del tipo.
+- Hexadecimal: no sabe restar direcciones (`0xd210 - 0xd1f0`). Se le enseñó
+ a hacerlo con `p` en gdb y con las potencias de 16. Pide ayuda con
+ "ni idea": darle herramientas para medir, no la cuenta hecha.
+- Se salta la predicción: el 2026-10-06 miró gdb sin predecir tres veces
+ seguidas. Insistir en que la escriba antes de ejecutar.
- Impresoras físicas: sin registrar marca y modelo.
- Confunde puntero colgante ("se pierde") con fuga. Tampoco tenía claro por qué
el slice viejo es seguro en Go tras `append` (el GC no libera el array viejo,
diff --git a/docs/02-pointers-and-strings.md b/docs/02-pointers-and-strings.md
index 4ceb2bc..5481fec 100644
--- a/docs/02-pointers-and-strings.md
+++ b/docs/02-pointers-and-strings.md
@@ -196,6 +196,70 @@ uint8_t init[] = { 0x1B, 0x40 }; /* ESC @ */
**En Go** `[2]byte{0x1B, 0x40}` es un array con su longitud dentro del tipo.
Se pasa por copia, y para pasarlo sin copiar se usa un slice: `init[:]`.
+## Aritmética de punteros: `[]` también lleva un `*` escondido
+
+Con dibujos interactivos y preguntas: [02-pointers-visual.html](02-pointers-visual.html).
+
+Sumar un entero a un puntero da **otra dirección**: `p + i` avanza `i`
+**elementos**, es decir, `i * sizeof *p` bytes. No va a la casa. Solo calcula
+qué casa es.
+
+`p[i]` es una abreviatura de `*(p + i)`: calcula la dirección y va a ella.
+Igual que `->`, los corchetes llevan el `*` dentro.
+
+La cadena completa con el buffer:
+
+| Escribes | Equivale a | Tipo | Qué es |
+|---|---|---|---|
+| `buffer` | | `escpos_buffer *` | El papel (8 bytes) |
+| `*buffer` | | `struct escpos_buffer` | La casa (24 bytes) |
+| `buffer->data` | `(*buffer).data` | `uint8_t *` | Un papel guardado dentro de la casa, con la dirección del bloque de bytes |
+| `buffer->data + i` | `&buffer->data[i]` | `uint8_t *` | La dirección de la casilla `i` |
+| `buffer->data[i]` | `*(buffer->data + i)` | `uint8_t` | El byte de la casilla `i` |
+
+Para saber si una expresión lleva `*`, cuenta: `*`, `->` y `[]` desreferencian
+una vez cada uno, y `&` quita una desreferencia.
+
+### Qué viaja cuando llamas a una función
+
+En C, a una función le llega siempre **una copia** del valor que le pasas.
+
+- `f(buffer)`: viaja una copia del papel, 8 bytes. La función llega a la misma
+ casa, así que lo que cambie en ella lo ves tú.
+- `f(*buffer)`: viaja una copia de la casa, 24 bytes. Lo que cambie la función
+ se queda en su copia.
+- `f(data)`, con `data` un array de 300 bytes: el array decae a la dirección
+ de su primera casilla. Viaja un papel de 8 bytes, no los 300 bytes. Dentro de
+ la función, `sizeof *data` es 1, el tamaño de una casilla.
+
+### Restar direcciones
+
+Las direcciones se escriben en hexadecimal, en base 16: cada posición vale 16
+veces la de su derecha, y `a`–`f` valen 10–15.
+
+| Hex | Decimal |
+|---|---|
+| `0x08` | 8 |
+| `0x10` | 16 |
+| `0x18` | 24 |
+| `0x20` | 32 |
+| `0x40` | 64 |
+| `0x80` | 128 |
+| `0x100` | 256 |
+
+`0x20` es 2 × 16 + 0 = 32. Para no hacer la cuenta a mano:
+
+- En gdb: `p 0xd210 - 0xd1f0` da `32`, y `p/x 32` da `0x20`.
+- En bash: `echo $((0xd210 - 0xd1f0))`.
+
+Si restas dos **punteros** del mismo tipo (`p &b - &a`), el resultado no son
+bytes: son **elementos**, igual que en la suma. Dos `escpos_buffer *` separados
+32 bytes dan `4`, porque cada uno ocupa 8.
+
+**En Go** pasa lo mismo, todo se copia: un `*Buffer` copia el puntero y un
+slice copia la cabecera (puntero, len y cap). La diferencia es que en el slice
+viaja la longitud, y en C tienes que pasarla tú.
+
## `printf` y el formato
El primer argumento de `printf` es el **formato**. Nunca pases ahí una cadena
diff --git a/docs/02-pointers-visual.html b/docs/02-pointers-visual.html
new file mode 100644
index 0000000..68231c8
--- /dev/null
+++ b/docs/02-pointers-visual.html
@@ -0,0 +1,1016 @@
+
+
+
+
+
+Papel y casa
+
+
+
+
+
+
+
+
+
+
Papel y casa
+
Punteros en C dibujados con las direcciones reales que sacaste con gdb de build/hello. En cada pantalla, primero predices y luego miras.
+
+ papel: un puntero, 8 bytes con una dirección
+ pila: variables locales
+ montón: lo que sale de malloc/realloc
+
+
+
+
+
+
+
+
+
Dónde vive cada cosa
+
El momento: el punto de parada en la línea 42 de hello.c. buffer tiene 100 bytes 'A' y buffer_a tiene un 0x1B. Contesta las preguntas y el mapa se irá rellenando.
+
+
+
+
+
+
Pila · [stack]0x7fff…
+
+
+
buffer
0x4052b0
en 0x7fffffffd520 · 8 B
+
+
0x20 = 32 bytes
+
+
buffer_a
0x405360
en 0x7fffffffd540 · 8 B
+
+
+
+
entre las dos zonas hay unos 128 TB de direcciones sin usar
+
+
Montón · [heap]0x40…
+
+
+
*buffer0x4052b0 · 24 B
+
+
data · +00x4052d0
+
len · +8100
+
cap · +16128
+
+
+
+
bloque de buffer0x4052d0 · 128 B
+
+
100 usados · 28 libres
+
+
+
+
+
*buffer_a0x405360 · 24 B
+
+
data · +00x405380
+
len · +81
+
cap · +1664
+
+
+
+
bloque de buffer_a0x405380 · 64 B
+
+
1 usado · 63 libres
+
+
+
+
Las cajas con ? aparecen cuando contestes.
+
+
+
+
En Go
b := &Buffer{} también puede dejar el papel en la pila y la casa en el montón. La diferencia es que lo decide el compilador (escape analysis) y tú no lo ves. En C lo decides tú: si lo pides con malloc, va al montón.
+
+
+
+
+
+
Expresiones: ¿papel o lo que hay dentro?
+
Elige una expresión, predice qué te da y cuánto ocupa, y comprueba. El dibujo marca el resultado. Regla: *, -> y [] van a la casa (un salto cada uno), y & deshace un salto.
+
+
+
+
+
+
Pilamarco de main
+
+
+
buffer
0x4052b0
en 0x7fffffffd520 · 8 B
+
+
+
+
+
Montón
+
+
+
struct en 0x4052b024 B
+
+
data · 0x4052b00x4052d0
+
len · 0x4052b8100
+
cap · 0x4052c0128
+
+
+
+
bloque en 0x4052d0128 B
+
+
+
+
+
+
+
+
En Go
Go esconde casi todos los saltos: b.len desreferencia solo aunque b sea un *Buffer, y s[1] no deja ver que hay un puntero dentro del slice. En C cada salto se escribe: ->, * o [].
+
+
+
+
+
+
Aritmética de punteros
+
p + i no va a ningún sitio: calcula otra dirección, ielementos más allá. p[i] calcula esa dirección y va a ella. Cuánto es un elemento depende del tipo del puntero.
No hay aritmética de punteros (salvo con unsafe). s[i] comprueba el límite y hace panic si te pasas. En C, p[i] fuera del bloque compila sin avisar y es comportamiento indefinido: lees o escribes lo que haya al lado.
+
+
+
+
+
+
Qué viaja al llamar a una función
+
Paso a paso: escpos_buffer_append(buffer_b, data_b), con uint8_t data_b[300] declarado en main. Direcciones de ejemplo.
marco de escpos_buffer_appenddirecciones más bajas
+
+
+
buffer
0x4053e0
copia · 8 B
+
+
+
byte
0x7fffffffd3d0
copia · 8 B
+
+
+
+
+
+
Montón
+
+
+
*buffer_b0x4053e0 · 24 B
+
+
dataNULL
+
len0
+
cap0
+
+
+
+
+
+
+
+
+
+
C · append(escpos_buffer *buffer, uint8_t byte[])
+
buffer · 8 Bbyte · 8 B
+
Viajan 16 bytes. La longitud no viaja: si la función la necesita, tiene que llegarle como otro argumento.
+
+
+
Go · Append(b *Buffer, data []byte)
+
b · 8 Bdata.ptr · 8 Bdata.len = 300data.cap = 300
+
Viajan 32 bytes. El slice es una cabecera de tres campos que se copia entera, y por eso len(data) da 300 dentro de la función.
+
+
+
+
+
+
+
+
Leer y restar hexadecimal
+
Base 16: los dígitos van de 0 a f (a=10 … f=15), y cada posición vale 16 veces la de su derecha.
+
+
+
+
+
+
Hex
Decimal
Dónde lo has visto
+
+
0x08
8
sizeof de un puntero o de size_t
+
0x10
16
de data a cap dentro del struct
+
0x18
24
sizeof *buffer
+
0x20
32
de &buffer a &buffer_a; del struct a su data
+
0x40
64
cap inicial del buffer
+
0x80
128
cap tras el primer crecimiento
+
0x100
256
16 × 16
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
En Go
Los literales son iguales: 0x20 == 32, y fmt.Printf("%x", 32) imprime 20. En C, printf("%zx", n) para un size_t y %p para un puntero.
+
+
+
+
+
+
+
+
diff --git a/docs/07-dynamic-memory.md b/docs/07-dynamic-memory.md
index 577c55d..4519976 100644
--- a/docs/07-dynamic-memory.md
+++ b/docs/07-dynamic-memory.md
@@ -52,6 +52,30 @@ propios gestores sobre `mmap` y pueden no tener `[heap]` en absoluto.
Comprobado con el proceso principal de VS Code: tiene `[stack]` pero no
`[heap]`.
+### Ejemplo: dónde vive cada parte del buffer
+
+Medido con gdb en `hello` (Fedora, 2026-10-06), en el punto de parada de
+`main`:
+
+| Qué | Expresión en gdb | Dirección | Zona |
+|---|---|---|---|
+| El papel: la variable local `buffer` de `main` | `p &buffer` | `0x7fffffffd520` | `[stack]` |
+| La casa: el struct de 24 bytes | `p buffer` | `0x4052b0` | `[heap]` |
+| Los bytes: el bloque de 128 | `p buffer->data` | `0x4052d0` | `[heap]` |
+
+- Son **tres sitios**: el puntero está en la pila, y los dos bloques de
+ `malloc`/`realloc` están en el montón, separados entre sí.
+- Los campos del struct sí van juntos y en orden: `&buffer->len` es
+ `0x4052b8`, 8 bytes después de `data`.
+- El struct y los datos están a 32 bytes y no a 24: `malloc` redondea el tamaño
+ y guarda delante de cada bloque una cabecera con información para `free`.
+- `info proc mappings` en gdb (o `/proc//maps`) dice a qué zona pertenece
+ cada dirección.
+
+**En Go** la variable `b := &Buffer{}` también puede vivir en la pila y el
+struct en el montón, pero lo decide el compilador (*escape analysis*) y no lo
+ves.
+
### Memoria virtual frente a memoria real
`/proc//status` muestra dos cifras:
@@ -243,6 +267,39 @@ Comparado con el `copy(dst, src)` de Go:
- Go copia `min(len(dst), len(src))` y devuelve cuántos copió: nunca se sale.
`memcpy` no puede comprobar nada porque no conoce los tamaños.
+### `restrict`: la promesa de que no se solapan
+
+Desde C99 la firma real lleva `restrict`:
+
+```c
+void *memcpy(void *restrict dst, const void *restrict src, size_t n);
+```
+
+`restrict` en un puntero es una **promesa del programador al compilador**:
+mientras dura la función, a la memoria que se toca por ese puntero no se accede
+por ningún otro. Aquí quiere decir que `dst` y `src` no se solapan.
+
+- **Para qué sirve:** sabiendo que no se solapan, el compilador puede copiar en
+ bloques de 16 o 32 bytes, reordenar y no volver a leer `src` después de cada
+ escritura en `dst`. Sin la promesa, tendría que suponer que escribir en `dst`
+ puede cambiar `src`.
+- **Quién lo comprueba:** nadie. Si rompes la promesa (con bloques que se
+ solapan), es comportamiento indefinido. Para bloques solapados está `memmove`,
+ que no lleva `restrict`.
+- **Para quien llama no cambia nada:** se llama igual que en C89.
+
+`man 3 memcpy` en Fedora lo escribe como `void dest[restrict .n]`. Es una
+notación de la página de manual para decir que el bloque mide `n`. No es C
+válido.
+
+Versión del estándar: el `Makefile` compila con `-std=c17`, así que
+`restrict` existe. Para comprobar qué estándar usa el compilador:
+`echo | gcc -std=c17 -dM -E - | grep __STDC_VERSION__` da `201710L` (C17). Sin
+`-std`, gcc 15 usa C23 (`202311L`).
+
+**En Go** no existe. El compilador tiene que suponer lo peor cuando dos slices
+pueden compartir memoria, y `copy` funciona aunque se solapen, como `memmove`.
+
**Por qué usarlo en vez de un bucle:** glibc implementa `memcpy` en ensamblador
optimizado, con instrucciones que copian 16 o 32 bytes de una vez (SIMD). A
veces gcc incluso convierte un bucle de copia en una llamada a `memcpy`. Y
diff --git a/docs/README.md b/docs/README.md
index 14dd11a..6ee6e97 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -9,9 +9,11 @@ Ordenados por tema, en el orden en que se ven en el proyecto.
flags, qué ve quien recibe los binarios, `nm` y errores del enlazador.
2. [Punteros y cadenas](02-pointers-and-strings.md): cadenas terminadas en
`'\0'`, `const char *`, los dos significados de `*`, dónde va el `*`,
- nombres de parámetros, paso por valor frente a puntero, un puntero no sabe
+ nombres de parámetros, paso por valor frente a puntero, aritmética de
+ punteros (`p + i`, `p[i]`) y qué viaja al llamar a una función, un puntero no sabe
cuántos elementos hay, `const` en parámetros, arrays y el formato de
- `printf`.
+ `printf`. Versión interactiva con dibujos:
+ [02-pointers-visual.html](02-pointers-visual.html) (ábrela en el navegador).
3. [La memoria de un proceso](03-process-memory.md): memoria virtual, fallos
de página, modo usuario y modo núcleo, llamadas al sistema, cómo se lee la
memoria de otro proceso, y por qué el peligro real está en la tuya.
@@ -26,7 +28,7 @@ Ordenados por tema, en el orden en que se ven en el proyecto.
detección de Windows/Linux.
7. [Memoria dinámica](07-dynamic-memory.md): pila y montón, *escape
analysis* de Go, `malloc`/`free`/`calloc`, `sizeof *p`, `realloc` y
- estrategia de crecimiento, `memcpy` y desbordamiento al calcular tamaños, fugas, liberar en caminos
+ estrategia de crecimiento, `memcpy` y `restrict`, desbordamiento al calcular tamaños, fugas, liberar en caminos
de error sin `defer` (`goto cleanup`), propiedad de
los punteros y Valgrind.
8. [Structs](08-structs.md): definir frente a declarar, tipos incompletos y
diff --git a/docs/cheatsheet.md b/docs/cheatsheet.md
index ec7fe56..906378f 100644
--- a/docs/cheatsheet.md
+++ b/docs/cheatsheet.md
@@ -31,6 +31,7 @@ Si juntas dos flags de parada (`-E -S`), gana la que para antes.
| `xxd fichero.bin` | Volcado hexadecimal (para revisar los bytes ESC/POS). |
| `tail -c 5 fichero \| xxd` | Muestra los últimos bytes: sirve para ver si el fichero acaba en `0a` (`\n`). |
| `man 3 memcpy` | Documentación de una función de la librería de C: cabecera, firma y comportamiento. `man 2` para llamadas al sistema. |
+| `echo \| gcc -std=c17 -dM -E - \| grep __STDC_VERSION__` | Qué versión del estándar de C usa el compilador: `201710L` es C17, `202311L` es C23. |
| `echo $?` | Código de salida del último comando: `0` es éxito. |
## Memoria
@@ -57,6 +58,8 @@ Si juntas dos flags de parada (`-E -S`), gana la que para antes.
| `run` / `bt` / `print x` | Dentro de gdb: ejecutar, ver la pila de llamadas, ver una variable. |
| `Ctrl+C` (dentro de gdb, con el programa en marcha) | Detiene el programa donde esté. Para encontrar bucles infinitos. |
| `break f.c:42` / `next` / `step` / `continue` | Dentro de gdb: punto de parada, siguiente línea, entrar en la función, seguir. |
+| `p &x` / `p *p` / `p/x *p@n` | Dentro de gdb: dirección de `x`, lo que hay en la dirección de `p`, y `n` elementos seguidos desde `p` en hexadecimal. |
+| `info proc mappings` | Dentro de gdb: mapa de memoria del proceso (`[heap]`, `[stack]`, librerías). Dice en qué zona cae una dirección. |
### VS Code
diff --git a/examples/hello.c b/examples/hello.c
index 4f3099f..676db87 100644
--- a/examples/hello.c
+++ b/examples/hello.c
@@ -25,7 +25,7 @@ int main(void)
uint8_t data[] = {0x1B, 0x40};
int err = 0;
- err = escpos_buffer_append(buffer_a, data);
+ err = escpos_buffer_append(buffer_a, data, sizeof data);
if (err == -1) { goto cleanup; }
buffer_b = escpos_buffer_new();
@@ -36,7 +36,7 @@ int main(void)
data_b[i] = 'A';
}
- err = escpos_buffer_append(buffer_b, data_b);
+ err = escpos_buffer_append(buffer_b, data_b, sizeof data_b);
if (err == -1) { goto cleanup; }
result = 0;
diff --git a/include/escpos.h b/include/escpos.h
index 07b797b..d63dca1 100644
--- a/include/escpos.h
+++ b/include/escpos.h
@@ -1,12 +1,13 @@
#ifndef ESCPOS_H
#define ESCPOS_H
#include
-
+ #include
+
const char *escpos_version(void);
typedef struct escpos_buffer escpos_buffer;
- /*
+ /*
* Crea un buffer vacío. Devuelve un puntero al buffer, o NULL si no hay
* memoria.
* El que llama es el dueño y debe liberarlo con escpos_buffer_free.
@@ -15,7 +16,7 @@
escpos_buffer *escpos_buffer_new(void);
/*
- * Libera el buffer y toda su memoria. Si recibe NULL no hace nada.
+ * Libera el buffer y toda su memoria. Si recibe NULL no hace nada.
* Propiedad: después de llamarla, el puntero ya no es válido.
*/
void escpos_buffer_free(escpos_buffer *buffer);
@@ -31,7 +32,7 @@
/*
* Añade los bytes al final del buffer
*/
- int escpos_buffer_append(escpos_buffer *buffer, uint8_t byte[]);
-
-
+ int escpos_buffer_append(escpos_buffer *buffer, uint8_t byte[], size_t size);
+
+
#endif
diff --git a/src/buffer.c b/src/buffer.c
index ff3cc07..3a23dbf 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -2,6 +2,7 @@
#include
#include
#include
+#include
struct escpos_buffer
@@ -88,19 +89,15 @@ int escpos_buffer_append_byte(escpos_buffer *buffer, uint8_t byte)
return 0;
}
-int escpos_buffer_append(escpos_buffer *buffer, uint8_t byte[])
+int escpos_buffer_append(escpos_buffer *buffer, uint8_t byte[], size_t size)
{
- int err = buffer_reserve(buffer, buffer->len + sizeof *byte);
+ int err = buffer_reserve(buffer, buffer->len + size);
if (err == -1)
{
return -1;
}
- for (size_t i = 0; i < sizeof *byte; i++)
- {
- buffer->data[buffer->len] = byte[i];
- buffer->len++;
- }
+ memcpy(buffer->data, byte, size);
return 0;
}