# Structs ## Definir un struct Definir es dar los campos. Es el equivalente a `type Point struct { ... }` de Go: ```c struct point { int x; int y; }; /* ← punto y coma obligatorio */ ``` Diferencias con Go: | Go | C | |---|---| | `X int` (nombre, tipo) | `int x;` (tipo, nombre) | | Sin `;` | Cada campo acaba en `;`, y la llave final también: `};` | | El nombre del tipo es `Point` | El nombre del tipo es `struct point`: la palabra `struct` forma parte del nombre | | Mayúscula = exportado | No hay campos privados: o ves la definición entera o no ves nada | ## Declarar un struct (tipo incompleto) Declarar es decir "existe un struct con este nombre" sin dar sus campos: ```c struct point; ``` Hasta que aparezca la definición, `struct point` es un **tipo incompleto**. El compilador no conoce su tamaño ni sus campos, así que: | Con un tipo incompleto | ¿Se puede? | |---|---| | Declarar un puntero: `struct point *p;` | Sí. Todos los punteros a struct miden lo mismo | | Pasar o devolver punteros en funciones | Sí | | Crear una variable: `struct point p;` | No: no sabe cuántos bytes reservar | | `sizeof(struct point)` | No | | Acceder a un campo: `p->x` | No: no sabe que existe `x` | Esto es justo lo que hace funcionar el **tipo opaco**: la cabecera pública declara, el `.c` define. El código cliente solo maneja punteros y llama a funciones de la librería. Dentro del `.c`, donde está la definición, el tipo está completo y se usan los campos con normalidad. Es parecido a devolver una interfaz en Go para ocultar el tipo concreto, pero sin coste de llamada indirecta: es solo un puntero. ### Struct público frente a struct opaco Definir el struct completo en la cabecera es perfectamente válido. Es una decisión de diseño: | | Struct definido en el `.h` (público) | Struct opaco | |---|---|---| | Crear uno | En la pila, sin `malloc`: `struct point p;` | Solo con la función `_new` de la librería (montón) | | Campos | Cualquiera puede leerlos **y cambiarlos** | Solo a través de funciones | | Invariantes (`len <= cap`) | El cliente puede romperlas: `b.len = 9999` y luego un desbordamiento | La librería las garantiza | | Cambiar los campos | Rompe a quien ya compiló contra la versión anterior (el tamaño y los desplazamientos van dentro de su `.o`) | Libre: el cliente nunca supo cómo era | | Coste | Acceso directo, sin llamadas | Una llamada a función por cada acceso | Se usa **público** para datos simples sin reglas internas, que el usuario rellena: `struct tm` (fechas) o `struct sockaddr_in` (direcciones de red, fase 4). Se usa **opaco** para objetos con estado e invariantes: `FILE` en la práctica, o nuestro `escpos_buffer`. **En Go** es la diferencia entre `type Point struct { X, Y int }` (campos exportados) y `type Buffer struct { data []byte }` (campos sin exportar, con constructor y métodos). La diferencia es que en Go el tamaño del struct no importa entre paquetes, porque todo se recompila junto. Para este proyecto el handle opaco es obligatorio: los bindings de cgo (fase 12) necesitan una API que no cambie aunque cambien las tripas. ## `typedef`: un alias para no escribir `struct` ```c typedef struct point point; ``` Se lee así: "`point` es otro nombre para `struct point`". A partir de ahí, `point *p` equivale a `struct point *p`. Puede ir antes de la definición: sirve igual para un tipo incompleto. Es lo más parecido a cómo nombra Go los tipos. Es habitual usar el mismo nombre para el struct y para el alias. No chocan, porque C guarda los nombres de struct en un espacio de nombres aparte. La sintaxis es `typedef ;`. El nombre nuevo va **al final**, como en la declaración de una variable. Error típico: escribir `typedef struct escpos_buffer;` sin el nombre nuevo. gcc solo avisa (`useless storage class specifier in empty declaration`): declara el struct pero no crea ningún alias. **En Go**, el alias equivalente es `type Nuevo = Existente`, con el orden al revés. ## Tipos que necesitan un `#include` En Go, `byte`, `int` o `uint8` existen siempre. En C solo vienen de serie los tipos básicos (`char`, `int`, `long`...). Los de tamaño fijo y los de tamaños son `typedef` declarados en cabeceras estándar: | Tipo | Cabecera | Equivalente Go | |---|---|---| | `uint8_t`, `int32_t`, `uint64_t`... | `` | `uint8`, `int32`, `uint64` | | `size_t` (tamaños y longitudes) | `` (también la traen ``, ``, ``) | el `int` de `len()`, pero sin signo | | `bool`, `true`, `false` | `` | `bool` | | `NULL` | `` (y las mismas que `size_t`) | `nil` | Sin el `#include`: `error: unknown type name 'uint8_t'`. El editor (IntelliSense de VS Code) lo muestra como `identifier "uint8_t" is undefined`. ### `uint8_t` Entero **sin signo** (*u*nsigned) de **exactamente 8 bits**: valores de 0 a 255. Es el `byte`/`uint8` de Go. El sufijo `_t` es la convención para los nombres de tipos creados con `typedef`. ¿Por qué no `char`, que también mide un byte? Porque el estándar no fija si `char` tiene signo. En x86 lo tiene (de -128 a 127), y en ARM, el procesador de casi todos los Android, no. Un byte `0xE9` (la `é` en algunas páginas de códigos) valdría -23 en tu PC y 233 en el móvil. `uint8_t` es 0-255 en todas partes. ### `size_t` Entero **sin signo** pensado para **tamaños en bytes y número de elementos**: - Es lo que devuelve `sizeof` y lo que recibe `malloc`. - Mide lo mismo que un puntero: 64 bits en x86-64 y 32 bits en un ARM de 32 bits. Así siempre cabe el tamaño de cualquier bloque que puedas direccionar. - En `printf` se imprime con `%zu`. **En Go** su papel lo hace `int`, que es lo que devuelven `len()` y `cap()`. Pero `int` tiene signo y `size_t` no. Trampa de los tipos sin signo: no hay negativos. `size_t n = 0; n - 1` **no da -1**: da la vuelta al valor máximo (18446744073709551615 en 64 bits). Un bucle `for (size_t i = n - 1; i >= 0; i--)` no termina nunca, porque `i >= 0` siempre es verdad. Go, con `int`, no tiene este problema. ### Qué tipo usar Se elige por **lo que representa el valor**, no por ahorrar memoria: | Representa | Tipo | Go | |---|---|---| | Un número cualquiera, un contador o un código de resultado (puede ser negativo: `-1`) | `int` | `int` | | Un tamaño, una longitud o un índice en memoria | `size_t` | `int` (`len`, `cap`) | | Un byte de datos crudos (lo que se envía a la impresora) | `uint8_t` | `byte` | | Un valor con anchura fija por protocolo o formato (2 bytes de un comando, 4 de una cabecera) | `uint16_t`, `uint32_t`... | `uint16`, `uint32` | | Sí o no | `bool` (``) | `bool` | | Texto | `char` / `char *` | `string` | Usar un tipo pequeño para una variable local o un valor de retorno **no ahorra nada**. El valor viaja en un registro de 64 bits igualmente, y en las operaciones C lo convierte a `int` (*promoción entera*). Solo complica las cosas. **Ejemplo real de fallo**: una función que devuelve `uint8_t` y hace `return -1;`. Un `uint8_t` no tiene negativos, así que `-1` se convierte en `255`. Después, `if (err == -1)` compara `255` con `-1`: **siempre es falso**, y el error no se detecta nunca. gcc con los flags del proyecto no avisa. Clang sí (`comparison of constant -1 with expression of type 'uint8_t' is always false`), y gcc también lo hace con `-Wconversion`. Cada fichero incluye lo que usa él mismo. No hay que fiarse de que otra cabecera lo traiga de rebote. ## Acceder a los campos: `.` y `->` | Tienes | C | Go | |---|---|---| | Un valor: `struct point p` | `p.x` | `p.X` | | Un puntero: `struct point *p` | `p->x` | `p.X` (Go desreferencia solo) | `p->x` es una abreviatura de `(*p).x`: "sigue el puntero y coge el campo `x`". Si usas `.` con un puntero o `->` con un valor, el compilador da error. ## Inicializar ```c struct point a = { .x = 1, .y = 2 }; /* inicializadores designados */ struct point b = { 0 }; /* todo a cero */ ``` Los campos que no nombras quedan a cero. Como en un literal de Go (`Point{X: 1}`). Pero esto solo vale al **crear una variable**. Un struct reservado con `malloc` es basura. Hay que rellenar cada campo a mano con `->`, o usar `calloc`, que lo pone todo a cero. ## Dónde va cada cosa en el proyecto | Fichero | Contenido | Quién lo ve | |---|---|---| | `include/escpos.h` | `typedef struct escpos_buffer escpos_buffer;` (declaración + alias) | Todo el mundo | | `src/buffer.c` | `struct escpos_buffer { ... };` (definición) | Solo `buffer.c` |