malloc, free, append

This commit is contained in:
2026-10-02 11:36:59 +02:00
parent b8141311aa
commit 9d517f3607
13 changed files with 960 additions and 13 deletions
+95
View File
@@ -17,6 +17,20 @@ Los literales como `"1.0.0"` viven en **memoria de solo lectura**. Si
intentas escribir en ellos, el comportamiento es indefinido; en la práctica,
el programa se cierra.
## `'A'` frente a `"A"`
| Literal | Qué es | Tipo | Ocupa |
|---|---|---|---|
| `'A'` (comillas simples) | Un **carácter**: el número 65 | `int` (cabe en un `uint8_t`) | 1 byte guardado en un `uint8_t` |
| `"A"` (comillas dobles) | Una **cadena**: los bytes `'A'` y `'\0'` en memoria de solo lectura | `char *`: un puntero a esos bytes | 2 bytes, más el puntero |
**En Go** es la misma diferencia que entre `'A'` (un `rune`) y `"A"` (un
`string`).
Pasar `"A"` donde se espera un byte es pasar **una dirección** donde se
espera **un número**. gcc lo detecta: `makes integer from pointer without a
cast` o `differ in signedness`.
## `const char *`
Una función que "devuelve una cadena" en realidad devuelve **la dirección de
@@ -44,6 +58,87 @@ Si `p` apunta a `"1.0.0"`, `*p` es solo el `'1'`.
**En Go**: es lo mismo que `var p *T` frente a `*p`.
### Regla práctica: ¿quiero la dirección o la cosa?
Un puntero es un papel con la dirección de una casa:
- `buffer` es **el papel**, la dirección.
- `*buffer` es **la casa**, el struct que está en esa dirección.
- `buffer->len` es "ve a la casa y coge `len`". La flecha ya va a la casa,
así que no lleva `*`. Equivale a `(*buffer).len`.
- `&x` es lo contrario de `*`: "dame la dirección de `x`".
Antes de escribir, pregúntate: **¿esta función o esta operación necesita la
dirección o la cosa?**
Aplicado a `buffer.c`:
| Código | ¿`*`? | Por qué |
|---|---|---|
| `escpos_buffer *buffer = ...` | Sí | Declaración: dice que `buffer` **es** un puntero |
| `malloc(sizeof *buffer)` | Sí | Quiero el tamaño **de la casa** (el struct, 24 bytes). `sizeof buffer` sería el tamaño **del papel** (el puntero, 8 bytes) |
| `if (buffer == NULL)` | No | Compruebo si el papel tiene una dirección válida |
| `buffer->data = NULL` | No | `->` ya va a la casa |
| `return buffer;` | No | Devuelvo el papel; la función devuelve `escpos_buffer *` |
| `free(buffer)` | No | `free` necesita la dirección del bloque |
**En Go** las reglas son las mismas, pero Go oculta casi todos los `*`:
`p.X` desreferencia solo, y los métodos con receptor puntero también. Por eso en
Go casi solo ves `*` en los tipos (`*Buffer`) y rara vez en expresiones
(`*p = v`). En C la diferencia entre papel y casa siempre se escribe.
## Dónde va el `*`: siempre con el tipo
Los espacios alrededor del `*` dan igual. Estas tres líneas son idénticas:
```c
escpos_buffer* escpos_buffer_new(void);
escpos_buffer * escpos_buffer_new(void);
escpos_buffer *escpos_buffer_new(void);
```
En todas, el tipo de retorno es `escpos_buffer *` ("puntero a
`escpos_buffer`"). El `*` **no** es de la función, es del tipo que devuelve.
En Go se lee igual: `func New() *Buffer`.
La costumbre en C es pegarlo al nombre por esta trampa:
```c
int *a, b; /* a es int *, pero b es int, NO un puntero */
```
En una declaración múltiple, el `*` solo afecta al nombre que tiene al lado.
Pegarlo al nombre lo deja a la vista. Mejor aún: una variable por línea.
## Nombres de parámetros en las declaraciones
En una **declaración** (el prototipo de la cabecera), el nombre del parámetro
es opcional: `void escpos_buffer_free(escpos_buffer *);` es válido. El
compilador solo necesita los tipos.
En la **definición** (en el `.c`) sí hace falta, porque el cuerpo lo usa.
Aun así, conviene ponerlo también en la cabecera: es documentación. Con
`void copy(char *, const char *)` no sabes cuál es el destino;
con `void copy(char *dst, const char *src)` sí.
**En Go** pasa lo mismo en los tipos función y en las interfaces:
`func(int) error` es válido sin nombres.
## Paso por valor: un struct entero frente a un puntero
C copia siempre los argumentos, igual que Go. Si un parámetro es
`escpos_buffer` (sin `*`), la función recibiría **una copia del struct
entero**. Eso tiene dos problemas:
1. Con un tipo opaco ni siquiera compila: el struct está incompleto fuera de
su `.c` y no se sabe cuánto copiar.
2. Liberar una copia no sirve de nada: el bloque que reservó `malloc` es el
original.
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()`).
## `printf` y el formato
El primer argumento de `printf` es el **formato**. Nunca pases ahí una cadena