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
|
- **Fase 2**: buffer dinámico. La Fase 1 está cerrada en Fedora y en Windows
|
||||||
(2026-10-05).
|
(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`
|
- 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
|
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,
|
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
|
que las tres valían `NULL` (solo la última). Lo detectó clang con
|
||||||
`-Wsometimes-uninitialized`. Ahora declara una variable por línea.
|
`-Wsometimes-uninitialized`. Ahora declara una variable por línea.
|
||||||
- Variables locales sin inicializar: creía que valían `NULL` como en Go.
|
- 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.
|
- Impresoras físicas: sin registrar marca y modelo.
|
||||||
- Confunde puntero colgante ("se pierde") con fuga. Tampoco tenía claro por qué
|
- 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,
|
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.
|
**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[:]`.
|
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
|
## `printf` y el formato
|
||||||
|
|
||||||
El primer argumento de `printf` es el **formato**. Nunca pases ahí una cadena
|
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
|
Comprobado con el proceso principal de VS Code: tiene `[stack]` pero no
|
||||||
`[heap]`.
|
`[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
|
### Memoria virtual frente a memoria real
|
||||||
|
|
||||||
`/proc/<pid>/status` muestra dos cifras:
|
`/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.
|
- 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.
|
`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
|
**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
|
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
|
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.
|
flags, qué ve quien recibe los binarios, `nm` y errores del enlazador.
|
||||||
2. [Punteros y cadenas](02-pointers-and-strings.md): cadenas terminadas en
|
2. [Punteros y cadenas](02-pointers-and-strings.md): cadenas terminadas en
|
||||||
`'\0'`, `const char *`, los dos significados de `*`, dónde va el `*`,
|
`'\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
|
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
|
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
|
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.
|
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.
|
detección de Windows/Linux.
|
||||||
7. [Memoria dinámica](07-dynamic-memory.md): pila y montón, *escape
|
7. [Memoria dinámica](07-dynamic-memory.md): pila y montón, *escape
|
||||||
analysis* de Go, `malloc`/`free`/`calloc`, `sizeof *p`, `realloc` y
|
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
|
de error sin `defer` (`goto cleanup`), propiedad de
|
||||||
los punteros y Valgrind.
|
los punteros y Valgrind.
|
||||||
8. [Structs](08-structs.md): definir frente a declarar, tipos incompletos y
|
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). |
|
| `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`). |
|
| `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. |
|
| `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. |
|
| `echo $?` | Código de salida del último comando: `0` es éxito. |
|
||||||
|
|
||||||
## Memoria
|
## 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. |
|
| `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. |
|
| `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. |
|
| `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
|
### VS Code
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -25,7 +25,7 @@ int main(void)
|
|||||||
uint8_t data[] = {0x1B, 0x40};
|
uint8_t data[] = {0x1B, 0x40};
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
err = escpos_buffer_append(buffer_a, data);
|
err = escpos_buffer_append(buffer_a, data, sizeof data);
|
||||||
if (err == -1) { goto cleanup; }
|
if (err == -1) { goto cleanup; }
|
||||||
|
|
||||||
buffer_b = escpos_buffer_new();
|
buffer_b = escpos_buffer_new();
|
||||||
@@ -36,7 +36,7 @@ int main(void)
|
|||||||
data_b[i] = 'A';
|
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; }
|
if (err == -1) { goto cleanup; }
|
||||||
|
|
||||||
result = 0;
|
result = 0;
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
#ifndef ESCPOS_H
|
#ifndef ESCPOS_H
|
||||||
#define ESCPOS_H
|
#define ESCPOS_H
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
const char *escpos_version(void);
|
const char *escpos_version(void);
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@
|
|||||||
/*
|
/*
|
||||||
* Añade los bytes al final del buffer
|
* 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
|
#endif
|
||||||
|
|||||||
+4
-7
@@ -2,6 +2,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
struct escpos_buffer
|
struct escpos_buffer
|
||||||
@@ -88,19 +89,15 @@ int escpos_buffer_append_byte(escpos_buffer *buffer, uint8_t byte)
|
|||||||
return 0;
|
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)
|
if (err == -1)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < sizeof *byte; i++)
|
memcpy(buffer->data, byte, size);
|
||||||
{
|
|
||||||
buffer->data[buffer->len] = byte[i];
|
|
||||||
buffer->len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user