memcpy
This commit is contained in:
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Linux",
|
||||||
|
"includePath": ["${workspaceFolder}/include"],
|
||||||
|
"compilerPath": "/usr/bin/gcc",
|
||||||
|
"compilerArgs": ["-std=c17", "-Wall", "-Wextra", "-Wpedantic"],
|
||||||
|
"cStandard": "c17",
|
||||||
|
"intelliSenseMode": "linux-gcc-x64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Win32",
|
||||||
|
"includePath": ["${workspaceFolder}/include"],
|
||||||
|
"compilerPath": "C:/msys64/ucrt64/bin/gcc.exe",
|
||||||
|
"compilerArgs": ["-std=c17", "-Wall", "-Wextra", "-Wpedantic"],
|
||||||
|
"cStandard": "c17",
|
||||||
|
"intelliSenseMode": "windows-gcc-x64"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+34
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Depurar hello (gdb)",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/build/hello",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"externalConsole": false,
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"miDebuggerPath": "/usr/bin/gdb",
|
||||||
|
"preLaunchTask": "make",
|
||||||
|
"setupCommands": [
|
||||||
|
{
|
||||||
|
"description": "Formato legible para structs y arrays",
|
||||||
|
"text": "-enable-pretty-printing",
|
||||||
|
"ignoreFailures": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "No descargar símbolos de depuración del sistema",
|
||||||
|
"text": "set debuginfod enabled off",
|
||||||
|
"ignoreFailures": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"windows": {
|
||||||
|
"program": "${workspaceFolder}/build/hello.exe",
|
||||||
|
"miDebuggerPath": "C:/msys64/ucrt64/bin/gdb.exe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"files.insertFinalNewline": true,
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"files.associations": {
|
||||||
|
"*.h": "c"
|
||||||
|
},
|
||||||
|
"[c]": {
|
||||||
|
"editor.tabSize": 4,
|
||||||
|
"editor.insertSpaces": true,
|
||||||
|
"editor.formatOnSave": false
|
||||||
|
},
|
||||||
|
"[makefile]": {
|
||||||
|
"editor.insertSpaces": false,
|
||||||
|
"editor.detectIndentation": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+31
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "make",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "make",
|
||||||
|
"group": { "kind": "build", "isDefault": true },
|
||||||
|
"problemMatcher": ["$gcc"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "make clean",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "make clean",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "make (clang)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "make clean && make CC=clang",
|
||||||
|
"problemMatcher": ["$gcc"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "valgrind",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "valgrind --leak-check=full ./build/hello",
|
||||||
|
"dependsOn": "make",
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
CC = gcc
|
CC = gcc
|
||||||
CFLAGS = -std=c17 -Wall -Wextra -Wpedantic -g
|
CFLAGS = -std=c17 -Werror -Wall -Wextra -Wpedantic -g
|
||||||
CPPFLAGS = -Iinclude
|
CPPFLAGS = -Iinclude
|
||||||
|
|
||||||
all: build/hello
|
all: build/hello
|
||||||
@@ -19,5 +19,11 @@ build/hello: build/hello.o build/escpos.o build/buffer.o
|
|||||||
clean:
|
clean:
|
||||||
rm -rf build
|
rm -rf build
|
||||||
|
|
||||||
|
valgrind: build/hello
|
||||||
|
valgrind --leak-check=full build/hello
|
||||||
|
|
||||||
|
run: build/hello
|
||||||
|
build/hello
|
||||||
|
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|||||||
+61
-13
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
## Estado actual
|
## Estado actual
|
||||||
|
|
||||||
- **Fase 2** — Buffer dinámico. (Fase 1 cerrada en Fedora; falta probar en Windows.)
|
- **Fase 2**: buffer dinámico. La Fase 1 está cerrada en Fedora, pero falta
|
||||||
- Paso actual: 2.2, escpos_buffer_append_byte con realloc y duplicado de capacidad.
|
probarla en Windows.
|
||||||
|
- Paso actual: 2.3b, `escpos_buffer_append` (varios bytes: puntero `const` +
|
||||||
|
cantidad + `memcpy`). El 2.3a ya está hecho: `buffer_reserve`, `static`, `t` en `nm`.
|
||||||
- Entorno activo: Fedora 42 nativo (gcc 15.2, clang 20, make 4.4, gdb 17,
|
- Entorno activo: Fedora 42 nativo (gcc 15.2, clang 20, make 4.4, gdb 17,
|
||||||
valgrind 3.26).
|
valgrind 3.26).
|
||||||
|
|
||||||
@@ -13,27 +15,73 @@
|
|||||||
la usa. Compila y enlaza a mano en Windows y en Fedora.
|
la usa. Compila y enlaza a mano en Windows y en Fedora.
|
||||||
- `samples/hello_world.bin`: ESC @, texto, LF, ESC d 5, GS V 0.
|
- `samples/hello_world.bin`: ESC @, texto, LF, ESC d 5, GS V 0.
|
||||||
- Emulador ESC/POS en Go (`emulator/`) para probar sin gastar papel.
|
- Emulador ESC/POS en Go (`emulator/`) para probar sin gastar papel.
|
||||||
|
- Makefile con regla patrón, `clean` y `.PHONY`. Compila desde un clon limpio y
|
||||||
|
sin avisos con gcc y con clang.
|
||||||
|
- 2.1: `escpos_buffer_new` / `escpos_buffer_free` con tipo opaco. Valgrind
|
||||||
|
limpio, y fuga provocada a propósito para leer el informe.
|
||||||
|
- 2.2: `escpos_buffer_append_byte` con `realloc`: 64 bytes al principio, luego
|
||||||
|
el doble. Comprobado con 100 bytes (crece de 64 a 128). Valgrind limpio.
|
||||||
|
Sin el `free`, Valgrind da 24 `definitely` + 128 `indirectly`.
|
||||||
|
- 2.3a: crecimiento extraído a `static int buffer_reserve(buffer, min_cap)`:
|
||||||
|
arranca en 64 o en `cap` y dobla hasta que cabe; un único `realloc`.
|
||||||
|
|
||||||
## Conceptos de C aprendidos
|
## Conceptos de C aprendidos
|
||||||
|
|
||||||
- Etapas de compilación, unidades de traducción, cabeceras, *include guards*,
|
- Etapas de compilación, unidades de traducción, cabeceras, *include guards*,
|
||||||
`static` para encapsular, `nm` y errores del enlazador.
|
`static` para encapsular, `nm` y errores del enlazador.
|
||||||
- Cadenas terminadas en `'\0'`, `const char *`.
|
- Cadenas terminadas en `'\0'`, `const char *`, `'A'` frente a `"A"`.
|
||||||
- Memoria virtual de un proceso, modo usuario y modo núcleo.
|
- Memoria virtual de un proceso, modo usuario y modo núcleo. `VmSize` frente a
|
||||||
- Make: reglas, dependencias por fechas, variables, regla patrón con $@ $< $^, .PHONY.
|
`VmRSS`, *overcommit* y OOM killer.
|
||||||
- Pila y montón, malloc/free, NULL, propiedad de punteros, tipo opaco, typedef, `->`, uint8_t/size_t, Valgrind.
|
- Make: reglas, dependencias por fechas, variables, regla patrón con
|
||||||
|
`$@ $< $^`, `.PHONY`.
|
||||||
|
- Pila y montón, `malloc`/`free`/`realloc`, `NULL`, propiedad de punteros,
|
||||||
|
tipo opaco, `typedef`, `->`, `uint8_t`/`size_t`, qué tipo usar.
|
||||||
|
- Depuración: *segfault* (139), `free(): invalid pointer` (134), gdb básico,
|
||||||
|
y leer errores e informes de fugas en Valgrind.
|
||||||
|
|
||||||
|
## Forma de trabajar
|
||||||
|
|
||||||
|
- Desde el 2026-10-02: sin *vibecoding*. Enunciado y pruebas, predicción antes
|
||||||
|
de compilar, y pistas por niveles solo si las pide. Ante un error, primero su
|
||||||
|
diagnóstico.
|
||||||
|
|
||||||
## Decisiones de diseño
|
## Decisiones de diseño
|
||||||
|
|
||||||
- Prefijo `escpos_` para toda la API pública.
|
- Prefijo `escpos_` para toda la API pública.
|
||||||
- `escpos_buffer` es un tipo opaco: declarado en `escpos.h`, definido en `src/buffer.c`. Campos `uint8_t *data`, `size_t len`, `size_t cap`.
|
- `escpos_buffer` es un tipo opaco: declarado en `escpos.h` y definido en
|
||||||
- Estilo: nombres en `snake_case` y llave de apertura en línea aparte (estilo Allman) en funciones, structs y bloques. Aplicarlo a todo el proyecto, también a `escpos.c`.
|
`src/buffer.c`. Campos `uint8_t *data`, `size_t len` y `size_t cap`.
|
||||||
- Lo generado va en `build/`, que se crea en las recetas con `mkdir -p` y no se versiona.
|
- Crecimiento del buffer: 64 bytes al principio y luego el doble.
|
||||||
|
- Por ahora, las funciones que pueden fallar devuelven `int`: `0` si va bien y
|
||||||
|
`-1` si no hay memoria. Pendiente de cambiar a códigos de error propios.
|
||||||
|
- Estilo: nombres en `snake_case` y llave de apertura en línea aparte (estilo
|
||||||
|
Allman) en funciones, structs y bloques.
|
||||||
|
- Lo generado va en `build/`, que se crea en las recetas con `mkdir -p` y no se
|
||||||
|
versiona.
|
||||||
|
|
||||||
## Pendiente / puntos débiles
|
## Pendiente / puntos débiles
|
||||||
|
|
||||||
- `.gitignore` creado. Siguen versionados `emulator/escpos-emu.exe` y `goruntime/goruntime.exe`.
|
- Fase 1: probar el Makefile en Windows (MSYS2), con el sufijo `.exe`.
|
||||||
- Ficheros sin salto de línea final: corregido; editor configurado.
|
- `.gitignore` creado. Siguen versionados `emulator/escpos-emu.exe` y
|
||||||
- Make: costó entender que va hacia atrás desde el objetivo, y que una regla patrón es una plantilla. Repasar con la sección de docs/06-make.md.
|
`goruntime/goruntime.exe`.
|
||||||
- Punteros: le cuesta saber cuándo hace falta `*` y cuándo no (también le pasaba en Go). Reforzar con ejemplos de su propio código.
|
- Make: le costó entender que va hacia atrás desde el objetivo y que una regla
|
||||||
|
patrón es una plantilla. Repasar con `docs/06-make.md`.
|
||||||
|
- Punteros: le cuesta saber cuándo hace falta `*` y cuándo no, y distinguir la
|
||||||
|
dirección del bloque (`data`) de una casilla (`data[i]`). Le funciona la
|
||||||
|
analogía de papel y casa. Reforzar con ejemplos de su propio código.
|
||||||
|
- Tipos: usó `uint8_t` para un código de retorno. Repasar la tabla "Qué tipo
|
||||||
|
usar" de `docs/08-structs.md`.
|
||||||
|
- Valorar añadir `-Wconversion` a `CFLAGS`: detecta conversiones con pérdida
|
||||||
|
que gcc no avisa por defecto.
|
||||||
|
- Bucles: en `buffer_reserve` calculaba cada vuelta a partir de un valor que no
|
||||||
|
cambiaba (`buffer->cap`) o que empezaba en 0, y eso daba bucles infinitos.
|
||||||
|
Lo resolvió siguiendo los valores vuelta a vuelta. Seguir pidiéndole trazas
|
||||||
|
a mano.
|
||||||
- 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é
|
||||||
|
el slice viejo es seguro en Go tras `append` (el GC no libera el array viejo,
|
||||||
|
no es por la copia). Repasar con la tabla de docs/07-dynamic-memory.md.
|
||||||
|
|
||||||
|
## Otros
|
||||||
|
|
||||||
|
- `goescpos/`: equivalente en Go idiomático (`bytes.Buffer`) de lo hecho hasta
|
||||||
|
ahora, para comparar.
|
||||||
|
|||||||
@@ -139,6 +139,63 @@ entero**. Eso tiene dos problemas:
|
|||||||
Por eso las funciones de un tipo opaco reciben siempre un **puntero**. Es lo
|
Por eso las funciones de un tipo opaco reciben siempre un **puntero**. Es lo
|
||||||
mismo que los métodos con receptor puntero de Go (`func (b *Buffer) Free()`).
|
mismo que los métodos con receptor puntero de Go (`func (b *Buffer) Free()`).
|
||||||
|
|
||||||
|
## Un puntero no sabe cuántos elementos hay
|
||||||
|
|
||||||
|
Un slice de Go lleva dentro tres cosas: puntero, `len` y `cap`. Un puntero de
|
||||||
|
C lleva **solo la dirección del primer elemento**. No hay forma de preguntarle
|
||||||
|
cuántos hay detrás.
|
||||||
|
|
||||||
|
Por eso las funciones de C que reciben un bloque de datos piden siempre **dos
|
||||||
|
parámetros**: el puntero y la cantidad.
|
||||||
|
|
||||||
|
```c
|
||||||
|
int escpos_buffer_append(escpos_buffer *buffer, const uint8_t *bytes, size_t count);
|
||||||
|
```
|
||||||
|
|
||||||
|
En Go sería un solo parámetro: `func (b *Buffer) Append(bytes []byte)`.
|
||||||
|
|
||||||
|
Si el que llama pasa un `count` mayor que lo que hay de verdad, la función lee
|
||||||
|
fuera del bloque. El compilador no puede detectarlo. Es la fuente de los
|
||||||
|
desbordamientos de memoria más famosos.
|
||||||
|
|
||||||
|
Las cadenas son la excepción: su longitud se deduce porque acaban en `'\0'`.
|
||||||
|
Pero los datos binarios (un comando ESC/POS, una imagen) pueden contener
|
||||||
|
bytes `0x00` en medio, así que necesitan su cantidad aparte.
|
||||||
|
|
||||||
|
## `const` en parámetros puntero: una promesa
|
||||||
|
|
||||||
|
```c
|
||||||
|
const uint8_t *bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
Se lee "puntero a `uint8_t` que **no voy a modificar**". Es un contrato con el
|
||||||
|
que llama: le garantizas que sus datos salen intactos. Si dentro de la función
|
||||||
|
intentas escribir `bytes[0] = 0;`, el compilador da error.
|
||||||
|
|
||||||
|
- Pasar un `uint8_t *` normal donde se pide `const uint8_t *` está permitido:
|
||||||
|
solo añades una restricción.
|
||||||
|
- Al revés da aviso: estarías quitando una promesa.
|
||||||
|
|
||||||
|
**En Go** no existe. Un slice que recibes lo puedes modificar, y el que llama no
|
||||||
|
tiene ninguna garantía.
|
||||||
|
|
||||||
|
## Arrays
|
||||||
|
|
||||||
|
```c
|
||||||
|
uint8_t init[] = { 0x1B, 0x40 }; /* ESC @ */
|
||||||
|
```
|
||||||
|
|
||||||
|
- Un array de tamaño fijo. Si no pones el tamaño entre corchetes, el
|
||||||
|
compilador lo cuenta a partir de los valores.
|
||||||
|
- `sizeof init` da el tamaño **en bytes** del array completo (aquí 2). Solo
|
||||||
|
funciona con el array en su ámbito, no con un puntero.
|
||||||
|
- Al pasar un array a una función, se convierte en un **puntero a su primer
|
||||||
|
elemento** (*decay*). La función recibe la dirección, no una copia, y pierde
|
||||||
|
el tamaño. Por eso hay que pasar `sizeof init` aparte.
|
||||||
|
|
||||||
|
**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[:]`.
|
||||||
|
|
||||||
## `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
|
||||||
|
|||||||
@@ -130,6 +130,19 @@ Make. Por eso allí todo va en `.PHONY` y la comparación de fechas no se usa
|
|||||||
nunca. En C es al revés: Make **es** el sistema de compilación, y solo las
|
nunca. En C es al revés: Make **es** el sistema de compilación, y solo las
|
||||||
acciones (`all`, `clean`) son phony.
|
acciones (`all`, `clean`) son phony.
|
||||||
|
|
||||||
|
### Los avisos solo salen al compilar
|
||||||
|
|
||||||
|
Un *warning* aparece **solo cuando ese `.c` se compila**. Como el `.o` sí se
|
||||||
|
genera, el siguiente `make` lo ve al día, no recompila, y el aviso no vuelve a
|
||||||
|
salir. Es fácil no verlo nunca. Hay tres formas de evitarlo:
|
||||||
|
|
||||||
|
- `make clean && make` para verlo todo otra vez.
|
||||||
|
- `touch src/buffer.c && make` para recompilar solo ese fichero.
|
||||||
|
- Añadir `-Werror` a `CFLAGS`: convierte los avisos en errores. Así no se
|
||||||
|
genera el `.o` y el aviso sale en cada `make` hasta que lo arreglas.
|
||||||
|
|
||||||
|
**En Go** no pasa: lo que en C son avisos importantes, en Go son errores.
|
||||||
|
|
||||||
### Variables desde la línea de comandos
|
### Variables desde la línea de comandos
|
||||||
|
|
||||||
`make CC=clang` sobrescribe la variable `CC` del Makefile solo para esa
|
`make CC=clang` sobrescribe la variable `CC` del Makefile solo para esa
|
||||||
|
|||||||
@@ -171,6 +171,37 @@ solo entonces asignarlo.
|
|||||||
escribir `s = append(s, x)`: si el array subyacente se movió, el slice viejo
|
escribir `s = append(s, x)`: si el array subyacente se movió, el slice viejo
|
||||||
apunta al sitio antiguo.
|
apunta al sitio antiguo.
|
||||||
|
|
||||||
|
**Colgante no es lo mismo que perdido.** Tras un `realloc` que mueve el bloque,
|
||||||
|
el puntero viejo no cambia: sigue con la dirección antigua, pero esa memoria ya
|
||||||
|
está liberada. Es un puntero colgante, y usarlo es comportamiento indefinido.
|
||||||
|
"Perdido" es lo contrario, una fuga: memoria reservada sin ningún puntero.
|
||||||
|
|
||||||
|
| | C (`realloc`) | Go (`append`) |
|
||||||
|
|---|---|---|
|
||||||
|
| ¿Copia al bloque nuevo? | Sí | Sí |
|
||||||
|
| ¿Libera el bloque viejo? | Sí, en el acto | No; el GC lo mantiene vivo mientras algo lo referencie |
|
||||||
|
| Puntero/slice viejo | Colgante: comportamiento indefinido | Válido pero desconectado: ya no ve los cambios |
|
||||||
|
| Síntoma del fallo | Cuelgue o corrupción del montón | Datos incorrectos, sin ningún error |
|
||||||
|
|
||||||
|
```go
|
||||||
|
a := make([]byte, 3) // len 3, cap 3: lleno
|
||||||
|
b := a // b comparte el array con a
|
||||||
|
a = append(a, 'X') // no cabe: nuevo array
|
||||||
|
a[0] = 'Z'
|
||||||
|
fmt.Println(b[0]) // 0: b sigue en el array viejo
|
||||||
|
```
|
||||||
|
|
||||||
|
Si `a` hubiera tenido capacidad libre, `append` no movería nada y `b[0]` valdría
|
||||||
|
`'Z'`. El resultado depende de `cap`.
|
||||||
|
|
||||||
|
**Qué hace `bytes.Buffer` por debajo.** Es el mismo algoritmo que
|
||||||
|
`escpos_buffer_append_byte`: 64 bytes iniciales (`smallBufferSize`) y duplicar
|
||||||
|
al llenarse. Diferencias: nunca crece en el sitio (siempre reserva y copia),
|
||||||
|
pone la memoria a cero (`malloc` no), redondea la capacidad al tamaño de clase
|
||||||
|
del asignador de Go, comprueba los índices y, si falla, hace `panic` en vez de
|
||||||
|
devolver un error. El struct `bytes.Buffer` puede vivir en la pila si no
|
||||||
|
escapa; solo los datos van al montón.
|
||||||
|
|
||||||
### Estrategia de crecimiento
|
### Estrategia de crecimiento
|
||||||
|
|
||||||
Si el buffer creciera de byte en byte, cada byte nuevo podría provocar una
|
Si el buffer creciera de byte en byte, cada byte nuevo podría provocar una
|
||||||
@@ -182,6 +213,63 @@ O(1)*).
|
|||||||
`append` en Go duplica hasta 256 elementos y a partir de ahí crece más despacio
|
`append` en Go duplica hasta 256 elementos y a partir de ahí crece más despacio
|
||||||
(unos 1,25×).
|
(unos 1,25×).
|
||||||
|
|
||||||
|
## `memcpy`: copiar bloques de bytes
|
||||||
|
|
||||||
|
`memcpy(dst, src, n)` (en `<string.h>`) copia `n` bytes desde la dirección
|
||||||
|
`src` a la dirección `dst`. Su firma:
|
||||||
|
|
||||||
|
```c
|
||||||
|
void *memcpy(void *dst, const void *src, size_t n);
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parte | Qué significa |
|
||||||
|
|---|---|
|
||||||
|
| `void *dst` | Dirección de destino. `void *` es "puntero a cualquier cosa": acepta un `uint8_t *`, un `int *` o un `struct x *` sin *cast* |
|
||||||
|
| `const void *src` | Dirección de origen. El `const` es la promesa: `memcpy` no modifica tus datos |
|
||||||
|
| `size_t n` | Cuántos **bytes** copiar. Se pasa aparte porque un puntero no sabe cuántos hay detrás |
|
||||||
|
| devuelve `void *` | El mismo `dst`. Casi nunca se usa |
|
||||||
|
|
||||||
|
Fíjate en que su firma resuelve el mismo problema que cualquier función que
|
||||||
|
recibe un bloque de datos: puntero, cantidad y `const` en lo que solo se lee.
|
||||||
|
|
||||||
|
`n` va en **bytes**, no en elementos. Con `uint8_t` coincide. Para copiar 10
|
||||||
|
`int`, `n` es `10 * sizeof(int)`, o sea, 40.
|
||||||
|
|
||||||
|
Comparado con el `copy(dst, src)` de Go:
|
||||||
|
|
||||||
|
- No sabe nada de tamaños: copia exactamente `n` bytes, quepan o no. Si en
|
||||||
|
`dst` no hay `n` bytes reservados, escribe fuera del bloque.
|
||||||
|
- Los dos bloques **no pueden solaparse**. Si se solapan, se usa `memmove`.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
**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
|
||||||
|
además dice lo que haces en una sola línea.
|
||||||
|
|
||||||
|
La documentación de cualquier función de la librería de C se consulta con
|
||||||
|
`man 3 memcpy`. El 3 es la sección de funciones de librería; la 2 es la de
|
||||||
|
llamadas al sistema.
|
||||||
|
|
||||||
|
Para copiar al final del buffer hace falta la dirección de la casilla `len`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
&buffer->data[buffer->len] /* "la dirección de la casilla len" */
|
||||||
|
buffer->data + buffer->len /* lo mismo: aritmética de punteros */
|
||||||
|
```
|
||||||
|
|
||||||
|
Sumar un número a un puntero avanza ese número de **elementos** (no de bytes;
|
||||||
|
aquí coincide porque cada elemento es un `uint8_t`).
|
||||||
|
|
||||||
|
### Desbordamiento al calcular tamaños
|
||||||
|
|
||||||
|
`len + count` o `cap * 2` pueden pasar del máximo de `size_t` y dar la vuelta
|
||||||
|
a un número pequeño. Entonces `realloc` reserva poco y `memcpy` escribe fuera.
|
||||||
|
Con datos que vienen de fuera (por ejemplo, desde Go mediante cgo) hay que
|
||||||
|
comprobarlo antes de sumar: `if (count > SIZE_MAX - len)`. `SIZE_MAX` está en
|
||||||
|
`<stdint.h>`.
|
||||||
|
|
||||||
## Fugas de memoria
|
## Fugas de memoria
|
||||||
|
|
||||||
Si pierdes el último puntero a un bloque sin hacer `free`, ese bloque queda
|
Si pierdes el último puntero a un bloque sin hacer `free`, ese bloque queda
|
||||||
@@ -265,6 +353,57 @@ dice qué hacer con cada una.
|
|||||||
El código Go que llame a `escpos_buffer_new` tendrá que hacer
|
El código Go que llame a `escpos_buffer_new` tendrá que hacer
|
||||||
`defer C.escpos_buffer_free(b)`, igual que con un fichero.
|
`defer C.escpos_buffer_free(b)`, igual que con un fichero.
|
||||||
|
|
||||||
|
## Liberar en los caminos de error: C no tiene `defer`
|
||||||
|
|
||||||
|
En Go, `defer b.Close()` se ejecuta salga la función por donde salga. En C no
|
||||||
|
existe nada así: cada `return` tiene que dejar liberado todo lo que se creó
|
||||||
|
antes. Con varios recursos, repetir los `free` delante de cada `return` es
|
||||||
|
largo y es fácil olvidarse de alguno.
|
||||||
|
|
||||||
|
El patrón clásico de C es **un único punto de limpieza al final**, al que se
|
||||||
|
salta con `goto`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int run(void)
|
||||||
|
{
|
||||||
|
int result = 1; /* se supone fallo hasta llegar al final */
|
||||||
|
thing *a = NULL; /* todo a NULL desde el principio */
|
||||||
|
thing *b = NULL;
|
||||||
|
|
||||||
|
a = thing_new();
|
||||||
|
if (a == NULL) { goto cleanup; }
|
||||||
|
|
||||||
|
b = thing_new();
|
||||||
|
if (b == NULL) { goto cleanup; }
|
||||||
|
|
||||||
|
/* ... trabajo ... */
|
||||||
|
|
||||||
|
result = 0; /* todo ha ido bien */
|
||||||
|
|
||||||
|
cleanup: /* etiqueta: destino del goto */
|
||||||
|
thing_free(b); /* seguro aunque sea NULL */
|
||||||
|
thing_free(a);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Por qué funciona:
|
||||||
|
|
||||||
|
- Todos los punteros empiezan en `NULL`, y la función de liberar acepta `NULL`
|
||||||
|
sin hacer nada. Por eso `escpos_buffer_free` se diseñó así. Da igual en qué
|
||||||
|
punto falle: la limpieza libera lo que exista e ignora lo demás.
|
||||||
|
- Hay un solo `return`. Al añadir un recurso nuevo, se añade su `free` en un
|
||||||
|
solo sitio.
|
||||||
|
- Se libera en orden inverso al de creación, como hace `defer` (LIFO).
|
||||||
|
|
||||||
|
`goto` tiene mala fama por el código "espagueti", pero este uso, saltar
|
||||||
|
**hacia delante** a la limpieza, es idiomático. El núcleo de Linux lo usa por
|
||||||
|
todas partes.
|
||||||
|
|
||||||
|
gcc y clang tienen una extensión, `__attribute__((cleanup(f)))`, que se
|
||||||
|
parece a `defer`. No es C estándar y no funciona en MSVC, así que en este
|
||||||
|
proyecto no se usa.
|
||||||
|
|
||||||
## Valgrind
|
## Valgrind
|
||||||
|
|
||||||
`valgrind` ejecuta tu programa en una CPU simulada y vigila cada acceso a
|
`valgrind` ejecuta tu programa en una CPU simulada y vigila cada acceso a
|
||||||
|
|||||||
@@ -79,7 +79,53 @@ gdb ./build/hello
|
|||||||
| `continue` (`c`) | Sigue hasta el siguiente punto de parada |
|
| `continue` (`c`) | Sigue hasta el siguiente punto de parada |
|
||||||
| `quit` (`q`) | Sale |
|
| `quit` (`q`) | Sale |
|
||||||
|
|
||||||
|
### Programa que no termina (bucle infinito)
|
||||||
|
|
||||||
|
1. `gdb ./build/hello` y `run`.
|
||||||
|
2. Cuando lleve un rato colgado, pulsa **Ctrl+C**: gdb detiene el programa
|
||||||
|
donde esté y te muestra la línea.
|
||||||
|
3. `bt` para ver en qué función está, y `print` de las variables del bucle
|
||||||
|
(`print new_cap`). Si al repetir `next` varias veces la variable no cambia,
|
||||||
|
has encontrado el problema.
|
||||||
|
|
||||||
|
Ejemplo real: un bucle `while (new_cap < min_cap) { new_cap *= 2; }` con
|
||||||
|
`new_cap` empezando en 0. Cero por dos es cero, así que el bucle no termina
|
||||||
|
nunca. `print new_cap` da `0` en cada vuelta.
|
||||||
|
|
||||||
Al arrancar, Fedora pregunta si quieres descargar símbolos de depuración del
|
Al arrancar, Fedora pregunta si quieres descargar símbolos de depuración del
|
||||||
sistema (*debuginfod*). Para nuestro código no hacen falta: responde `n`.
|
sistema (*debuginfod*). Para nuestro código no hacen falta: responde `n`.
|
||||||
|
|
||||||
**En Go** el equivalente es Delve (`dlv debug`), con comandos casi iguales.
|
**En Go** el equivalente es Delve (`dlv debug`), con comandos casi iguales.
|
||||||
|
|
||||||
|
## Depurar en VS Code
|
||||||
|
|
||||||
|
La extensión C/C++ (`ms-vscode.cpptools`) lanza **el mismo gdb** y le envía
|
||||||
|
órdenes por su protocolo de texto (gdb/MI). Cada clic equivale a un comando:
|
||||||
|
|
||||||
|
| VS Code | gdb |
|
||||||
|
|---|---|
|
||||||
|
| Clic a la izquierda del número de línea (punto rojo) | `break fichero.c:línea` |
|
||||||
|
| F5 | `run` / `continue` |
|
||||||
|
| F10 | `next` |
|
||||||
|
| F11 | `step` |
|
||||||
|
| Shift+F11 | `finish` (salir de la función actual) |
|
||||||
|
| Panel *Variables*, pasar el ratón por encima o panel *Watch* | `print` |
|
||||||
|
| Panel *Call Stack* | `bt` |
|
||||||
|
| Botón de pausa | Ctrl+C |
|
||||||
|
|
||||||
|
Configuración del proyecto, en `.vscode/`:
|
||||||
|
|
||||||
|
- `tasks.json`: tareas `make`, `make clean`, `make (clang)` y `valgrind`.
|
||||||
|
Se lanzan con *Terminal → Run Task*, y Ctrl+Shift+B ejecuta `make`.
|
||||||
|
- `launch.json`: depura `build/hello` con gdb y ejecuta `make` antes. Tiene ya
|
||||||
|
los valores para Windows (MSYS2 UCRT64).
|
||||||
|
- `c_cpp_properties.json`: dice a IntelliSense dónde están las cabeceras
|
||||||
|
(`include/`) y que el estándar es C17, para que el editor avise de lo mismo
|
||||||
|
que gcc.
|
||||||
|
- `settings.json`: salto de línea final, sin espacios al final de línea, y
|
||||||
|
tabuladores reales en el Makefile.
|
||||||
|
|
||||||
|
Para ver un puntero como array en el panel *Watch*: `*buffer->data@10` (los 10
|
||||||
|
primeros bytes de `data`). Es sintaxis de gdb y funciona igual en su consola.
|
||||||
|
|
||||||
|
**En Go**, la extensión de Go hace lo mismo con Delve.
|
||||||
|
|||||||
+6
-3
@@ -9,7 +9,8 @@ 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 y el formato de
|
nombres de parámetros, paso por valor frente a puntero, un puntero no sabe
|
||||||
|
cuántos elementos hay, `const` en parámetros, arrays y el formato de
|
||||||
`printf`.
|
`printf`.
|
||||||
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
|
||||||
@@ -25,14 +26,16 @@ 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, fugas, propiedad de
|
estrategia de crecimiento, `memcpy` y desbordamiento al calcular tamaños, fugas, liberar en caminos
|
||||||
|
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
|
||||||
tipo opaco, struct público frente a opaco, `typedef`, `uint8_t` y
|
tipo opaco, struct público frente a opaco, `typedef`, `uint8_t` y
|
||||||
`size_t`, qué tipo usar en cada caso, `.` frente a `->` e inicializadores designados.
|
`size_t`, qué tipo usar en cada caso, `.` frente a `->` e inicializadores designados.
|
||||||
9. [Depurar](09-debugging.md): qué es un *segfault*, código de salida 139,
|
9. [Depurar](09-debugging.md): qué es un *segfault*, código de salida 139,
|
||||||
`NULL` y la página 0, `free(): invalid pointer` (código 134), método para
|
`NULL` y la página 0, `free(): invalid pointer` (código 134), método para
|
||||||
depurar con Valgrind y los comandos mínimos de gdb.
|
depurar con Valgrind, los comandos mínimos de gdb y la depuración visual en
|
||||||
|
VS Code.
|
||||||
|
|
||||||
[Chuleta de comandos](cheatsheet.md): todos los comandos usados, agrupados por
|
[Chuleta de comandos](cheatsheet.md): todos los comandos usados, agrupados por
|
||||||
tarea.
|
tarea.
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ Si juntas dos flags de parada (`-E -S`), gana la que para antes.
|
|||||||
| `objdump --dwarf=info fichero.o` | Muestra la información de depuración que añade `-g`. |
|
| `objdump --dwarf=info fichero.o` | Muestra la información de depuración que añade `-g`. |
|
||||||
| `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. |
|
||||||
| `echo $?` | Código de salida del último comando: `0` es éxito. |
|
| `echo $?` | Código de salida del último comando: `0` es éxito. |
|
||||||
|
|
||||||
## Memoria
|
## Memoria
|
||||||
@@ -54,8 +55,20 @@ Si juntas dos flags de parada (`-E -S`), gana la que para antes.
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `gdb ./build/hello` | Abre el programa en el depurador (hace falta `-g`). |
|
| `gdb ./build/hello` | Abre el programa en el depurador (hace falta `-g`). |
|
||||||
| `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. |
|
||||||
| `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. |
|
||||||
|
|
||||||
|
### VS Code
|
||||||
|
|
||||||
|
| Atajo | Qué hace |
|
||||||
|
|---|---|
|
||||||
|
| F5 | Compila con `make` y depura `build/hello` |
|
||||||
|
| F9 o clic junto al número de línea | Punto de parada |
|
||||||
|
| F10 / F11 / Shift+F11 | Siguiente línea / entrar en la función / salir de la función |
|
||||||
|
| Ctrl+Shift+B | `make` |
|
||||||
|
| *Terminal → Run Task* | `make clean`, `make (clang)`, `valgrind` |
|
||||||
|
| `*buffer->data@10` en *Watch* | Ver los 10 primeros bytes de un puntero |
|
||||||
|
|
||||||
## Solo en Windows (MSYS2)
|
## Solo en Windows (MSYS2)
|
||||||
|
|
||||||
| Comando | Qué hace |
|
| Comando | Qué hace |
|
||||||
|
|||||||
+31
-13
@@ -1,28 +1,46 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "escpos.h"
|
#include "escpos.h"
|
||||||
|
|
||||||
int main(void) {
|
int main(void)
|
||||||
|
{
|
||||||
|
|
||||||
const char *version = escpos_version();
|
const char *version = escpos_version();
|
||||||
|
|
||||||
printf("%s\n", version);
|
printf("%s\n", version);
|
||||||
|
|
||||||
escpos_buffer *buffer = escpos_buffer_new();
|
escpos_buffer *buffer = escpos_buffer_new();
|
||||||
if (buffer == NULL)
|
if (buffer == NULL) { goto cleanup; }
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < 100; i++)
|
for (int i = 0; i < 100; i++)
|
||||||
{
|
{
|
||||||
int err = escpos_buffer_append_byte(buffer, 'A');
|
int err = escpos_buffer_append_byte(buffer, 'A');
|
||||||
if (err == -1)
|
if (err == -1) { goto cleanup; }
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
escpos_buffer_free(buffer);
|
escpos_buffer *buffer_a = escpos_buffer_new();
|
||||||
|
if (buffer_a == NULL) { goto cleanup; }
|
||||||
|
uint8_t data[] = {0x1B, 0x40};
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
err = escpos_buffer_append(buffer_a, data);
|
||||||
|
if (err == -1) { goto cleanup; }
|
||||||
|
|
||||||
|
escpos_buffer *buffer_b = escpos_buffer_new();
|
||||||
|
if (buffer_b == NULL) { goto cleanup; }
|
||||||
|
uint8_t data_b[300];
|
||||||
|
for (int i = 0; i < 300; i++)
|
||||||
|
{
|
||||||
|
data_b[i] = 'A';
|
||||||
|
}
|
||||||
|
|
||||||
|
err = escpos_buffer_append(buffer_b, data_b);
|
||||||
|
if (err == -1) { goto cleanup; }
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
escpos_buffer_free(buffer);
|
||||||
|
escpos_buffer_free(buffer_a);
|
||||||
|
escpos_buffer_free(buffer_b);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Equivalente a examples/hello.c.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"goescpos/escpos"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println(escpos.Version)
|
||||||
|
|
||||||
|
// bytes.Buffer sustituye a todo src/buffer.c: crece solo y lo libera el GC.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for range 100 {
|
||||||
|
buf.WriteByte('A')
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("len=%d cap=%d\n", buf.Len(), buf.Cap())
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// Package escpos es el equivalente en Go de include/escpos.h + src/escpos.c.
|
||||||
|
package escpos
|
||||||
|
|
||||||
|
// Version equivale a escpos_version().
|
||||||
|
const Version = "1.0.0"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module goescpos
|
||||||
|
|
||||||
|
go 1.26
|
||||||
Executable
BIN
Binary file not shown.
+9
-2
@@ -21,10 +21,17 @@
|
|||||||
void escpos_buffer_free(escpos_buffer *buffer);
|
void escpos_buffer_free(escpos_buffer *buffer);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Añade un byte al final. Devuelve 0 o -1 si no hay memoria; en ese caso
|
* Añade un byte al final del buffer.
|
||||||
* el buffer queda intacto.
|
* Devuelve 0 si va bien, o -1 si no hay memoria; en ese caso el buffer
|
||||||
|
* queda intacto.
|
||||||
*/
|
*/
|
||||||
int escpos_buffer_append_byte(escpos_buffer *buffer, uint8_t byte);
|
int escpos_buffer_append_byte(escpos_buffer *buffer, uint8_t byte);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Añade los bytes al final del buffer
|
||||||
|
*/
|
||||||
|
int escpos_buffer_append(escpos_buffer *buffer, uint8_t byte[]);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+66
-28
@@ -4,31 +4,67 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
|
||||||
struct escpos_buffer
|
struct escpos_buffer
|
||||||
{
|
{
|
||||||
uint8_t *data;
|
uint8_t *data;
|
||||||
size_t len;
|
size_t len;
|
||||||
size_t cap;
|
size_t cap;
|
||||||
};
|
};
|
||||||
|
|
||||||
escpos_buffer *escpos_buffer_new(void)
|
static int buffer_reserve(escpos_buffer *buffer, size_t min_cap)
|
||||||
|
{
|
||||||
|
if (min_cap <= buffer->cap)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t new_cap;
|
||||||
|
|
||||||
|
if (buffer->cap == 0)
|
||||||
|
{
|
||||||
|
new_cap = 64;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
new_cap = buffer->cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
while (new_cap < min_cap)
|
||||||
|
{
|
||||||
|
new_cap *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *tmp = realloc(buffer->data, new_cap);
|
||||||
|
if (tmp == NULL)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer->data = tmp;
|
||||||
|
buffer->cap = new_cap;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
escpos_buffer *escpos_buffer_new(void)
|
||||||
{
|
{
|
||||||
escpos_buffer *buffer = malloc(sizeof *buffer);
|
escpos_buffer *buffer = malloc(sizeof *buffer);
|
||||||
if (buffer == NULL)
|
if (buffer == NULL)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer->data = NULL;
|
buffer->data = NULL;
|
||||||
buffer->len = 0;
|
buffer->len = 0;
|
||||||
buffer->cap = 0;
|
buffer->cap = 0;
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void escpos_buffer_free(escpos_buffer *buffer)
|
void escpos_buffer_free(escpos_buffer *buffer)
|
||||||
{
|
{
|
||||||
if (buffer == NULL)
|
if (buffer == NULL)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -39,30 +75,32 @@ void escpos_buffer_free(escpos_buffer *buffer)
|
|||||||
|
|
||||||
int escpos_buffer_append_byte(escpos_buffer *buffer, uint8_t byte)
|
int escpos_buffer_append_byte(escpos_buffer *buffer, uint8_t byte)
|
||||||
{
|
{
|
||||||
if (buffer->len == buffer->cap)
|
|
||||||
|
int err = buffer_reserve(buffer, buffer->len + 1);
|
||||||
|
if (err == -1)
|
||||||
{
|
{
|
||||||
size_t new_cap = 0;
|
return -1;
|
||||||
if (buffer->cap == 0)
|
|
||||||
{
|
|
||||||
new_cap = 64;
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
new_cap = buffer->cap * 2;
|
|
||||||
};
|
|
||||||
|
|
||||||
uint8_t *tmp = realloc(buffer->data, new_cap);
|
|
||||||
if (tmp == NULL)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer->data = tmp;
|
|
||||||
buffer->cap = new_cap;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer->data[buffer->len] = byte;
|
buffer->data[buffer->len] = byte;
|
||||||
buffer->len++;
|
buffer->len++;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int escpos_buffer_append(escpos_buffer *buffer, uint8_t byte[])
|
||||||
|
{
|
||||||
|
int err = buffer_reserve(buffer, buffer->len + sizeof *byte);
|
||||||
|
if (err == -1)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < sizeof *byte; i++)
|
||||||
|
{
|
||||||
|
buffer->data[buffer->len] = byte[i];
|
||||||
|
buffer->len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user