# 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[:]`. ## 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 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'`".