progress
This commit is contained in:
+13
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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/<pid>/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/<pid>/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
|
||||
|
||||
+5
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
#ifndef ESCPOS_H
|
||||
#define ESCPOS_H
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
const char *escpos_version(void);
|
||||
|
||||
@@ -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
|
||||
|
||||
+4
-7
@@ -2,6 +2,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user