213 lines
7.9 KiB
Markdown
213 lines
7.9 KiB
Markdown
# Punteros y cadenas
|
|
|
|
## En C no hay tipo `string`
|
|
|
|
En Go, `string` es un tipo propio: por dentro guarda un puntero y una
|
|
longitud, y lo manejas como un valor.
|
|
|
|
En C, una cadena es solo una secuencia de bytes en memoria terminada en `'\0'`.
|
|
La longitud **no se guarda en ningún sitio**: para saberla hay que recorrer la
|
|
cadena hasta encontrar el `'\0'`.
|
|
|
|
```
|
|
"1.0.0" → '1' '.' '0' '.' '0' '\0' (6 bytes, no 5)
|
|
```
|
|
|
|
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
|
|
su primer byte**:
|
|
|
|
```c
|
|
const char *version = escpos_version();
|
|
```
|
|
|
|
- `char`: en esa dirección hay caracteres.
|
|
- `*`: `version` es un puntero; guarda una dirección, no un carácter.
|
|
- `const`: a través de este puntero no se pueden modificar los caracteres.
|
|
|
|
`char` es un **entero de 1 byte**. Si guardas un puntero en un `char`, pierdes
|
|
la dirección. Por eso el error dice `makes pointer from integer`.
|
|
|
|
## Los dos significados de `*`
|
|
|
|
| Dónde aparece | Qué significa |
|
|
|---|---|
|
|
| En una **declaración**: `const char *p` | "`p` es un puntero" |
|
|
| En una **expresión**: `*p` | "el valor que hay en la dirección de `p`" (desreferenciar) |
|
|
|
|
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()`).
|
|
|
|
## 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
|
|
|
|
El primer argumento de `printf` es el **formato**. Nunca pases ahí una cadena
|
|
variable:
|
|
|
|
```c
|
|
printf(s); /* mal */
|
|
printf("%s\n", s); /* bien */
|
|
```
|
|
|
|
Si `s` contuviera un `%`, `printf` iría a leer argumentos que no existen, lo
|
|
que es comportamiento indefinido y un agujero de seguridad clásico
|
|
(*format string*). `%s` significa "imprime los bytes desde esta dirección hasta
|
|
el `'\0'`".
|