Campos de Bits (Bit-fields): Ahorro Extremo de Memoria¶
Los bit-fields permiten definir miembros de una struct con un ancho en bits
exacto, ideal para empaquetar flags o valores pequeños.
Sintaxis y Ejemplo¶
typedef struct {
unsigned int activo : 1; // 1 bit
unsigned int modo_op : 3; // 3 bits (valores 0-7)
unsigned int prioridad : 4; // 4 bits (valores 0-15)
} config_t;El compilador empaquetará estos 8 bits en un solo byte (si es posible).
Acceso y Type Punning Seguro¶
Cuando se trabaja con estructuras de campos de bits o representaciones de bajo nivel, suele ser necesario interpretar una estructura empaquetada como una secuencia cruda de bytes (por ejemplo, para transmitirla por red) o viceversa.
Un error común para lograr esto es castear la dirección de la estructura directamente:
packed_byte_t data;
uint8_t byte_crudo = *(uint8_t*)&data; // ¡ERROR! Violación de strict aliasingEsta técnica, llamada type punning mediante casteo de punteros, está prohibida en C moderno. El compilador asume que dos punteros de tipos incompatibles no apuntan al mismo objeto en memoria (regla de strict aliasing). Optimizar el código bajo este supuesto permite mejoras de rendimiento significativas, pero si violamos la regla, el compilador puede reorganizar los accesos y producir un comportamiento indefinido.
Existen dos formas válidas y seguras de realizar type punning en C:
El uso de uniones (
union): En C estándar, escribir en un miembro de una unión y leer de otro diferente es un comportamiento bien definido y el método preferido para reinterpretación de datos.Uso de
memcpy: Copiar los bytes mediantememcpyes seguro y los optimizadores modernos suelen eliminar la llamada física a la función, generando código máquina óptimo.
Laboratorio 2: Inspección de Bit-fields con Uniones¶
bitfield_inspect.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25#include <stdio.h> #include <stdint.h> typedef struct { uint8_t a : 2; uint8_t b : 3; uint8_t c : 3; } packed_byte_t; typedef union { packed_byte_t campos; uint8_t valor_raw; } packed_byte_u; int main() { packed_byte_u data; data.campos.a = 3; // 11b data.campos.b = 5; // 101b data.campos.c = 7; // 111b // Imprimimos la estructura de forma segura respetando el strict aliasing printf("sizeof(packed_byte_t) = %zu\n", sizeof(packed_byte_t)); printf("Byte resultante: 0x%02X\n", data.valor_raw); return 0; }
Compilación y Ejecución:
gcc -Wextra -Wall bitfield_inspect.c -o bitfield_inspect
./bitfield_inspectAnálisis: La salida 0xFD (o 11111101 en binario) puede parecer
sorprendente. El orden en que el compilador asigna los bits dentro del byte es
implementation-defined. No asumas un orden específico si necesitás
portabilidad.
Solution to Exercise 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34#include <stdio.h> #include <stdint.h> typedef struct { uint8_t es_ack : 1; uint8_t es_fin : 1; uint8_t tipo_paquete : 3; uint8_t checksum : 3; } estado_paquete_t; typedef union { estado_paquete_t campos; uint8_t byte_completo; } paquete_decoder_t; void imprimir_estado_paquete(uint8_t byte_estado) { paquete_decoder_t decoder; decoder.byte_completo = byte_estado; printf("--- Estado del Paquete (0x%02X) ---\n", byte_estado); printf(" ACK: %s\n", decoder.campos.es_ack ? "Sí" : "No"); printf(" FIN: %s\n", decoder.campos.es_fin ? "Sí" : "No"); printf(" Tipo: %u\n", decoder.campos.tipo_paquete); printf(" Checksum: %u\n", decoder.campos.checksum); printf("----------------------------------\n"); } int main() { // Ejemplo: ACK=1, FIN=0, Tipo=5 (101b), Checksum=3 (011b) // Binario: 011 101 0 1 -> 0xDA uint8_t paquete = 0b01110101; imprimir_estado_paquete(paquete); return 0; }
Operadores a Nivel de Bits¶
Los Campos de Bits del apartado anterior operan físicamente con bits individuales. Para manipularlos de forma explícita en código, C provee un conjunto de operadores bitwise que actúan sobre la representación binaria de los enteros.
Introducción: El Poder a Nivel de Bit¶
En el corazón de cada operación que realiza una computadora se encuentran los bits. La manipulación de bits es el conjunto de técnicas que nos permite interactuar directamente con esta representación binaria de los datos. En C, dominar estas técnicas es una habilidad práctica y poderosa para optimizar el rendimiento, ahorrar memoria e interactuar con hardware.
Representación Binaria¶
Todos los tipos de datos en C se almacenan como una secuencia de bits. Un
unsigned char (1 byte) que representa el número 200, en binario es 11001000.
Bit: 7 6 5 4 3 2 1 0
Valor: 128 64 32 16 8 4 2 1
-----------------------------------------
Binario: 1 1 0 0 1 0 0 0
Suma: 128 + 64+ 0 + 0 + 8 + 0 + 0 + 0 = 200Endianness¶
El endianness define el orden en que se almacenan en memoria los bytes que
componen un tipo de dato multi-byte (como int o double). Es un detalle de la
arquitectura del procesador que puede ser crucial en programación de sistemas,
redes o al trabajar con formatos de archivo binarios.
Little-endian: El byte menos significativo se almacena en la dirección de memoria más baja. Es la arquitectura dominante hoy en día (Intel x86, AMD64, Apple Silicon).
Big-endian: El byte más significativo se almacena en la dirección de memoria más baja. Era común en arquitecturas más antiguas (Motorola 68k, SPARC) y se sigue usando como el orden estándar en redes (Network Byte Order).
Ejemplo con el valor 0x1A2B3C4D (un int de 32 bits):
| Dirección | Little-endian | Big-endian |
|---|---|---|
0x100 | 4D | 1A |
0x101 | 3C | 2B |
0x102 | 2B | 3C |
0x103 | 1A | 4D |
¿Por qué es importante?
Si escribís un int a un archivo en una máquina little-endian y lo leés en una
big-endian, el valor será incorrecto.
Cómo detectar el endianness en C:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15#include <stdio.h> #include <stdint.h> int main(void) { uint32_t i = 1; char *c = (char*)&i; if (*c) { printf("Little-endian\n"); } else { printf("Big-endian\n"); } return 0; }
Este código funciona porque si es little-endian, el byte 01 se almacena en la
primera dirección, y *c será 1. Si es big-endian, el primer byte será 00,
y *c será 0.
Los Operadores a Nivel de Bits¶
1. AND a nivel de bits (&)¶
El bit del resultado es 1 solo si ambos bits correspondientes en los operandos
son 1. Su uso principal es para verificar o apagar bits.
Solution to Exercise 2
#include <stdbool.h>
bool es_par(int numero) {
// La máscara 1 (00000001) aísla el último bit.
// Si el resultado de (numero & 1) es 0, el bit era 0.
return (numero & 1) == 0;
}<!-- {solution} espar -->
(2-or-a-nivel-de-bits)=
### 2. OR a nivel de bits (`|`)
El bit del resultado es `1` si al menos uno de los bits correspondientes es `1`.
Su uso principal es para **encender** bits.
:::::::::{exercise} activar
:label: activar
Encender un Flag Dado un `unsigned char` que representa un
conjunto de 8 flags, escribí una función que encienda el 4to bit (bit en la
posición 3) sin modificar los demás. :::
:::{solution} activar
:class: dropdown
```{code-block} c
void activar_flag_4(unsigned char *estado) {
// La máscara (1 << 3) es 8 (00001000).
// El OR encenderá ese bit sin tocar los otros.
*estado = *estado | (1 << 3);
}
```
<!-- {code-block} c -->
3. XOR a nivel de bits (^)¶
El bit del resultado es 1 solo si los bits correspondientes son
diferentes. Su uso principal es para alternar (toggle) bits.
Solution to Exercise 4
// Máscara para el 2do bit: (1 << 1) -> 00000010
// Máscara invertida: ~(1 << 1) -> 11111101
unsigned char mascara_apagado = ~(1 << 1);5. Desplazamientos (<< y >>)¶
<<: Desplaza los bits a la izquierda. Equivale a multiplicar por potencias de 2.Ejemplo:
5 << 200000101 (5) << 2 --> 00010100 (20)>>: Desplaza los bits a la derecha. Equivale a dividir por potencias de 2.Ejemplo:
20 >> 200010100 (20) >> 2 --> 00000101 (5)
Multiplicación y División Rápida Escribí dos macros,
MULT_POR_8(x) y DIV_POR_4(x), que usen operadores de desplazamiento para
realizar las operaciones. :::
Solution to Exercise 5
#define MULT_POR_8(x) ((x) << 3) // 2^3 = 8
#define DIV_POR_4(x) ((x) >> 2) // 2^2 = 4Ejercicios de Aplicación (Recetario)¶
1. Obtener el N-ésimo Bit¶
Escribí una función int get_bit(int numero, int n) que devuelva
el valor (0 o 1) del bit en la posición n. :::
Solution to Exercise 6
int get_bit(int numero, int n) {
// Desplaza el bit n a la posición 0 y usa AND con 1 para aislarlo.
return (numero >> n) & 1;
}2. Establecer el N-ésimo Bit¶
Escribí una función void set_bit(int *numero, int n) que
encienda el bit en la posición n. :::
Solution to Exercise 7
void set_bit(int *numero, int n) {
// Crea una máscara con el bit n encendido (ej: 00001000) y aplica OR.
*numero |= (1 << n);
}3. Limpiar el N-ésimo Bit¶
Escribí una función void clear_bit(int *numero, int n) que
apague el bit en la posición n. :::
Solution to Exercise 8
void clear_bit(int *numero, int n) {
// Crea una máscara con el bit n en 0 y el resto en 1, y aplica AND.
*numero &= ~(1 << n);
}4. Contar Bits Encendidos (Hamming Weight)¶
Implementar una función que cuente el número de bits que están en
1 en la representación binaria de un número, usando el algoritmo de Brian
Kernighan.
Solution to Exercise 9
Algoritmo de Brian Kernighan: La operación
n & (n - 1) apaga el bit encendido menos significativo. El número de veces que
se puede hacer esta operación antes de que n sea 0 es el número de bits
encendidos.
1 2 3 4 5 6 7 8int contar_bits_encendidos(int n) { int contador = 0; while (n > 0) { n = n & (n - 1); // Apaga el bit '1' de más a la derecha contador++; } return contador; }
5. Verificar si es Potencia de Dos¶
Escribí una función que determine si un número entero positivo es una potencia de dos (2, 4, 8, 16...). :::
Solution to Exercise 10
Propiedad: Un número que es potencia de dos
tiene exactamente un bit encendido en su representación binaria (ej. 8 es
1000). El número n-1 tendrá todos los bits a la derecha de ese bit
encendidos (ej. 7 es 0111). Por lo tanto, n & (n - 1) será siempre cero.
1 2 3 4 5 6 7#include <stdbool.h> bool es_potencia_de_dos(int n) { // n > 0 asegura que no se incluya el 0. // (n & (n - 1)) == 0 verifica que solo haya un bit encendido. return (n > 0) && ((n & (n - 1)) == 0); }
Glosario¶
- Máscara de Bits (Bitmask)
- Un valor numérico utilizado para seleccionar, modificar o consultar bits específicos de otro valor mediante operaciones a nivel de bits como AND, OR y XOR.
- Endianness
- Se refiere al orden en que se almacenan en memoria los bytes que componen un tipo de dato multibyte. Es una consideración crucial para la portabilidad de datos binarios.
Bitmasks para Errores Múltiples¶
Una aplicación directa de los operadores bitwise en ingeniería de sistemas es la codificación de múltiples errores en un único entero mediante bitmasks. Este patrón es ubicuo en drivers, sistemas embebidos y APIs de bajo nivel.
Introducción¶
En muchas situaciones reales, una operación puede fallar por múltiples razones simultáneas. Por ejemplo, al validar datos de un formulario, pueden existir varios campos inválidos al mismo tiempo. Reportar solo el primer error obliga al usuario a corregir y reintentar múltiples veces. Una mejor solución es reportar todas las causas de error simultáneamente.
Este apunte presenta técnicas para manejar múltiples códigos de error usando bitmasks con enumeraciones, permitiendo representar y comunicar combinaciones de errores de forma eficiente y expresiva.
Cuando múltiples validaciones pueden fallar independientemente, es más útil reportar todas las fallas simultáneamente en lugar de forzar al usuario a corregir de a uno. Las bitmasks permiten representar combinaciones de errores en un solo valor entero.
Problema: Un Solo Código de Error¶
Enfoque Tradicional (Un Error)¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27typedef enum { ERROR_NINGUNO = 0, ERROR_NOMBRE_VACIO, ERROR_EMAIL_INVALIDO, ERROR_EDAD_FUERA_RANGO, ERROR_TELEFONO_INVALIDO } error_validacion_t; error_validacion_t validar_usuario(const usuario_t* usuario) { if (usuario->nombre == NULL || strlen(usuario->nombre) == 0) { return ERROR_NOMBRE_VACIO; // Retorna solo el primero } if (!es_email_valido(usuario->email)) { return ERROR_EMAIL_INVALIDO; // Nunca llega aquí si nombre falla } if (usuario->edad < 18 || usuario->edad > 120) { return ERROR_EDAD_FUERA_RANGO; } if (!es_telefono_valido(usuario->telefono)) { return ERROR_TELEFONO_INVALIDO; } return ERROR_NINGUNO; }
Problema: Si el nombre está vacío Y el email es inválido, el usuario solo sabrá del nombre. Deberá corregir y volver a intentar para descubrir el siguiente error.
Solución: Códigos de Error con Bitmasks¶
Definición con Potencias de 2¶
Para representar múltiples errores simultáneamente, cada código de error debe ser una potencia de 2 (un único bit activado):
1 2 3 4 5 6 7 8 9 10 11 12 13typedef enum { ERROR_NINGUNO = 0, // 0b00000000 ERROR_NOMBRE_VACIO = 1 << 0, // 0b00000001 ERROR_EMAIL_INVALIDO = 1 << 1, // 0b00000010 ERROR_EDAD_FUERA_RANGO = 1 << 2, // 0b00000100 ERROR_TELEFONO_INVALIDO = 1 << 3, // 0b00001000 ERROR_DNI_INVALIDO = 1 << 4, // 0b00010000 ERROR_DIRECCION_VACIA = 1 << 5, // 0b00100000 ERROR_CIUDAD_INVALIDA = 1 << 6 // 0b01000000 } errores_validacion_t; // Tipo para almacenar combinaciones typedef unsigned int errores_t;
Acumular Múltiples Errores¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26errores_t validar_usuario(const usuario_t* usuario) { errores_t errores = ERROR_NINGUNO; // Validar cada campo independientemente if (usuario->nombre == NULL || strlen(usuario->nombre) == 0) { errores |= ERROR_NOMBRE_VACIO; // OR para acumular } if (!es_email_valido(usuario->email)) { errores |= ERROR_EMAIL_INVALIDO; } if (usuario->edad < 18 || usuario->edad > 120) { errores |= ERROR_EDAD_FUERA_RANGO; } if (!es_telefono_valido(usuario->telefono)) { errores |= ERROR_TELEFONO_INVALIDO; } if (!es_dni_valido(usuario->dni)) { errores |= ERROR_DNI_INVALIDO; } return errores; }
Verificar Presencia de Errores¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26// Verificar si hay algún error bool hay_errores(errores_t errores) { return errores != ERROR_NINGUNO; } // Verificar un error específico bool tiene_error(errores_t errores, errores_validacion_t error_especifico) { return (errores & error_especifico) != 0; } // Uso errores_t resultado = validar_usuario(&usuario); if (hay_errores(resultado)) { if (tiene_error(resultado, ERROR_NOMBRE_VACIO)) { printf("Error: El nombre no puede estar vacío\n"); } if (tiene_error(resultado, ERROR_EMAIL_INVALIDO)) { printf("Error: El formato del email es inválido\n"); } if (tiene_error(resultado, ERROR_EDAD_FUERA_RANGO)) { printf("Error: La edad debe estar entre 18 y 120\n"); } }
Operaciones con Bitmasks de Error¶
Agregar Errores¶
errores_t errores = ERROR_NINGUNO;
// Agregar un error
errores |= ERROR_NOMBRE_VACIO;
// Agregar múltiples errores a la vez
errores |= (ERROR_EMAIL_INVALIDO | ERROR_EDAD_FUERA_RANGO);Remover Errores¶
// Remover un error específico
errores &= ~ERROR_EMAIL_INVALIDO;
// Remover múltiples errores
errores &= ~(ERROR_NOMBRE_VACIO | ERROR_DNI_INVALIDO);Alternar (Toggle) Errores¶
// Alternar un error (si está, lo quita; si no está, lo agrega)
errores ^= ERROR_TELEFONO_INVALIDO;Verificar Todos o Alguno¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18// Verificar si TODOS los errores especificados están presentes bool tiene_todos(errores_t errores, errores_t conjunto) { return (errores & conjunto) == conjunto; } // Verificar si ALGUNO de los errores está presente bool tiene_alguno(errores_t errores, errores_t conjunto) { return (errores & conjunto) != 0; } // Uso if (tiene_todos(resultado, ERROR_NOMBRE_VACIO | ERROR_EMAIL_INVALIDO)) { printf("Faltan tanto nombre como email\n"); } if (tiene_alguno(resultado, ERROR_EDAD_FUERA_RANGO | ERROR_DNI_INVALIDO)) { printf("Problema con edad o DNI\n"); }
Contar Errores¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16int contar_errores(errores_t errores) { int contador = 0; // Contar bits activados while (errores) { contador += errores & 1; errores >>= 1; } return contador; } // Alternativa más eficiente (GCC builtin) int contar_errores_rapido(errores_t errores) { return __builtin_popcount(errores); }
Casos Prácticos¶
Caso 1: Validación de Formulario Web¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70typedef enum { ERROR_FORM_NINGUNO = 0, ERROR_FORM_USUARIO_VACIO = 1 << 0, ERROR_FORM_PASSWORD_CORTO = 1 << 1, ERROR_FORM_PASSWORD_DEBIL = 1 << 2, ERROR_FORM_EMAIL_INVALIDO = 1 << 3, ERROR_FORM_EDAD_INVALIDA = 1 << 4, ERROR_FORM_TERMINOS_NO_ACEPTADOS = 1 << 5 } errores_formulario_t; errores_t validar_registro(const formulario_registro_t* form) { errores_t errores = ERROR_FORM_NINGUNO; if (form->usuario == NULL || strlen(form->usuario) < 3) { errores |= ERROR_FORM_USUARIO_VACIO; } if (form->password == NULL || strlen(form->password) < 8) { errores |= ERROR_FORM_PASSWORD_CORTO; } else if (!password_es_fuerte(form->password)) { errores |= ERROR_FORM_PASSWORD_DEBIL; } if (!es_email_valido(form->email)) { errores |= ERROR_FORM_EMAIL_INVALIDO; } if (form->edad < 13) { errores |= ERROR_FORM_EDAD_INVALIDA; } if (!form->acepta_terminos) { errores |= ERROR_FORM_TERMINOS_NO_ACEPTADOS; } return errores; } void mostrar_errores_formulario(errores_t errores) { if (errores == ERROR_FORM_NINGUNO) { printf("Formulario válido\n"); return; } printf("Errores en el formulario:\n"); if (errores & ERROR_FORM_USUARIO_VACIO) { printf(" - El nombre de usuario debe tener al menos 3 caracteres\n"); } if (errores & ERROR_FORM_PASSWORD_CORTO) { printf(" - La contraseña debe tener al menos 8 caracteres\n"); } if (errores & ERROR_FORM_PASSWORD_DEBIL) { printf(" - La contraseña debe contener mayúsculas, minúsculas y números\n"); } if (errores & ERROR_FORM_EMAIL_INVALIDO) { printf(" - El formato del email es inválido\n"); } if (errores & ERROR_FORM_EDAD_INVALIDA) { printf(" - Debe ser mayor de 13 años para registrarse\n"); } if (errores & ERROR_FORM_TERMINOS_NO_ACEPTADOS) { printf(" - Debe aceptar los términos y condiciones\n"); } }
Caso 2: Verificación de Permisos¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49typedef enum { PERMISO_NINGUNO = 0, PERMISO_LEER = 1 << 0, // 0b00000001 PERMISO_ESCRIBIR = 1 << 1, // 0b00000010 PERMISO_EJECUTAR = 1 << 2, // 0b00000100 PERMISO_ELIMINAR = 1 << 3, // 0b00001000 PERMISO_COMPARTIR = 1 << 4, // 0b00010000 PERMISO_ADMIN = 1 << 5 // 0b00100000 } permisos_t; typedef unsigned int permisos_usuario_t; // Constantes útiles const permisos_usuario_t PERMISOS_LECTURA_ESCRITURA = PERMISO_LEER | PERMISO_ESCRIBIR; const permisos_usuario_t PERMISOS_COMPLETOS = PERMISO_LEER | PERMISO_ESCRIBIR | PERMISO_EJECUTAR | PERMISO_ELIMINAR | PERMISO_COMPARTIR; bool puede_realizar_accion(permisos_usuario_t permisos_usuario, permisos_t permiso_requerido) { return (permisos_usuario & permiso_requerido) == permiso_requerido; } permisos_usuario_t otorgar_permiso(permisos_usuario_t actual, permisos_t nuevo_permiso) { return actual | nuevo_permiso; } permisos_usuario_t revocar_permiso(permisos_usuario_t actual, permisos_t permiso_a_revocar) { return actual & ~permiso_a_revocar; } // Uso permisos_usuario_t mis_permisos = PERMISO_LEER | PERMISO_ESCRIBIR; if (puede_realizar_accion(mis_permisos, PERMISO_ELIMINAR)) { eliminar_archivo(); } else { printf("No tienes permiso para eliminar\n"); } // Otorgar permiso de ejecución mis_permisos = otorgar_permiso(mis_permisos, PERMISO_EJECUTAR); // Revocar permiso de escritura mis_permisos = revocar_permiso(mis_permisos, PERMISO_ESCRIBIR);
Caso 3: Estado de Conexión de Red¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87typedef enum { RED_OK = 0, RED_SIN_CONEXION = 1 << 0, RED_TIMEOUT = 1 << 1, RED_DNS_FALLO = 1 << 2, RED_SSL_ERROR = 1 << 3, RED_CERTIFICADO_INVALIDO = 1 << 4, RED_PUERTO_BLOQUEADO = 1 << 5, RED_PROXY_ERROR = 1 << 6 } errores_red_t; typedef struct { errores_t errores; int codigo_http; char* mensaje; } resultado_conexion_t; resultado_conexion_t conectar_servidor(const char* url) { resultado_conexion_t resultado = { .errores = RED_OK, .codigo_http = 0, .mensaje = NULL }; // Intentar resolver DNS if (!resolver_dns(url)) { resultado.errores |= RED_DNS_FALLO; } // Verificar conectividad básica if (!hay_conexion_internet()) { resultado.errores |= RED_SIN_CONEXION; } // Intentar conexión if (!conectar_con_timeout(url, 5000)) { resultado.errores |= RED_TIMEOUT; } // Verificar SSL si es HTTPS if (es_https(url) && !verificar_ssl(url)) { resultado.errores |= RED_SSL_ERROR; if (!certificado_valido(url)) { resultado.errores |= RED_CERTIFICADO_INVALIDO; } } return resultado; } void diagnosticar_conexion(const resultado_conexion_t* resultado) { if (resultado->errores == RED_OK) { printf("Conexión exitosa\n"); return; } printf("Problemas detectados:\n"); if (resultado->errores & RED_SIN_CONEXION) { printf(" [CRÍTICO] No hay conexión a Internet\n"); } if (resultado->errores & RED_DNS_FALLO) { printf(" [ERROR] No se pudo resolver el nombre de dominio\n"); } if (resultado->errores & RED_TIMEOUT) { printf(" [ERROR] Tiempo de espera agotado\n"); } if (resultado->errores & RED_SSL_ERROR) { printf(" [ADVERTENCIA] Error en la conexión SSL\n"); } if (resultado->errores & RED_CERTIFICADO_INVALIDO) { printf(" [ADVERTENCIA] El certificado no es válido o ha expirado\n"); } // Sugerencias según la combinación if ((resultado->errores & RED_SIN_CONEXION) && (resultado->errores & RED_DNS_FALLO)) { printf("\nSugerencia: Verifica tu conexión de red\n"); } else if (resultado->errores & RED_CERTIFICADO_INVALIDO) { printf("\nSugerencia: Verifica la fecha del sistema\n"); } }
Caso 4: Validación de Documento¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61typedef enum { DOC_VALIDO = 0, DOC_ENCABEZADO_INVALIDO = 1 << 0, DOC_FORMATO_CORRUPTO = 1 << 1, DOC_VERSION_NO_SOPORTADA = 1 << 2, DOC_FIRMA_INVALIDA = 1 << 3, DOC_CHECKSUM_ERROR = 1 << 4, DOC_METADATOS_FALTANTES = 1 << 5, DOC_CONTENIDO_TRUNCADO = 1 << 6 } errores_documento_t; errores_t validar_documento(const documento_t* doc) { errores_t errores = DOC_VALIDO; // Validaciones independientes if (!validar_encabezado(doc)) { errores |= DOC_ENCABEZADO_INVALIDO; } if (!validar_formato(doc)) { errores |= DOC_FORMATO_CORRUPTO; } if (doc->version > VERSION_MAX_SOPORTADA) { errores |= DOC_VERSION_NO_SOPORTADA; } if (!verificar_firma_digital(doc)) { errores |= DOC_FIRMA_INVALIDA; } if (!verificar_checksum(doc)) { errores |= DOC_CHECKSUM_ERROR; } if (!tiene_metadatos_requeridos(doc)) { errores |= DOC_METADATOS_FALTANTES; } if (doc->tamanio_real < doc->tamanio_esperado) { errores |= DOC_CONTENIDO_TRUNCADO; } return errores; } bool es_error_critico(errores_t errores) { // Errores que impiden procesar el documento const errores_t ERRORES_CRITICOS = DOC_FORMATO_CORRUPTO | DOC_CHECKSUM_ERROR | DOC_CONTENIDO_TRUNCADO; return (errores & ERRORES_CRITICOS) != 0; } bool es_error_recuperable(errores_t errores) { // Errores que permiten continuar con precaución const errores_t ERRORES_RECUPERABLES = DOC_METADATOS_FALTANTES | DOC_VERSION_NO_SOPORTADA; return (errores & ERRORES_RECUPERABLES) != 0 && !es_error_critico(errores); }
Funciones Auxiliares Genéricas¶
Conversión a Cadenas¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30typedef struct { errores_validacion_t codigo; const char* mensaje; } mapeo_error_t; const mapeo_error_t MENSAJES_ERROR[] = { {ERROR_NOMBRE_VACIO, "El nombre no puede estar vacío"}, {ERROR_EMAIL_INVALIDO, "Formato de email inválido"}, {ERROR_EDAD_FUERA_RANGO, "La edad debe estar entre 18 y 120"}, {ERROR_TELEFONO_INVALIDO, "Formato de teléfono inválido"}, {ERROR_DNI_INVALIDO, "DNI inválido"}, {ERROR_DIRECCION_VACIA, "La dirección no puede estar vacía"}, {ERROR_CIUDAD_INVALIDA, "Ciudad no válida"} }; void imprimir_errores(errores_t errores) { if (errores == ERROR_NINGUNO) { printf("Sin errores\n"); return; } const int num_errores = sizeof(MENSAJES_ERROR) / sizeof(MENSAJES_ERROR[0]); printf("Errores encontrados:\n"); for (int i = 0; i < num_errores; i++) { if (errores & MENSAJES_ERROR[i].codigo) { printf(" - %s\n", MENSAJES_ERROR[i].mensaje); } } }
Construcción de JSON con Errores¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26char* errores_a_json(errores_t errores) { if (errores == ERROR_NINGUNO) { return strdup("{\"errores\": []}"); } // Buffer dinámico (simplificado) char buffer[1024] = "{\"errores\": ["; bool primero = true; const int num_errores = sizeof(MENSAJES_ERROR) / sizeof(MENSAJES_ERROR[0]); for (int i = 0; i < num_errores; i++) { if (errores & MENSAJES_ERROR[i].codigo) { if (!primero) { strcat(buffer, ", "); } strcat(buffer, "\""); strcat(buffer, MENSAJES_ERROR[i].mensaje); strcat(buffer, "\""); primero = false; } } strcat(buffer, "]}"); return strdup(buffer); }
Límites y Consideraciones¶
Número Máximo de Errores¶
Con un unsigned int (32 bits), podés representar hasta 32 errores
diferentes. Si necesitás más:
1 2 3 4 5 6 7 8 9// Para 64 errores typedef unsigned long long errores_extendido_t; typedef enum { ERROR_1 = 1ULL << 0, ERROR_2 = 1ULL << 1, // ... ERROR_64 = 1ULL << 63 } errores_64_t;
Arrays de Bitmasks¶
Para sistemas muy complejos con cientos de posibles errores:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25#define NUM_PALABRAS_ERROR 4 // 4 * 32 = 128 errores posibles typedef struct { unsigned int palabras[NUM_PALABRAS_ERROR]; } errores_multiples_t; void agregar_error(errores_multiples_t* errores, int numero_error) { int palabra = numero_error / 32; int bit = numero_error % 32; if (palabra < NUM_PALABRAS_ERROR) { errores->palabras[palabra] |= (1U << bit); } } bool tiene_error_multiples(const errores_multiples_t* errores, int numero_error) { int palabra = numero_error / 32; int bit = numero_error % 32; if (palabra < NUM_PALABRAS_ERROR) { return (errores->palabras[palabra] & (1U << bit)) != 0; } return false; }
Buenas Prácticas¶
1. Documentar los Códigos¶
1 2 3 4 5 6 7 8 9 10 11 12 13/** * Códigos de error para validación de usuarios. * Pueden combinarse usando OR bitwise (|). * * Ejemplo: * errores_t resultado = ERROR_NOMBRE_VACIO | ERROR_EMAIL_INVALIDO; */ typedef enum { ERROR_NINGUNO = 0, ///< Sin errores ERROR_NOMBRE_VACIO = 1 << 0, ///< Nombre NULL o vacío ERROR_EMAIL_INVALIDO = 1 << 1, ///< Formato email inválido ERROR_EDAD_FUERA_RANGO = 1 << 2 ///< Edad < 18 o > 120 } errores_validacion_t;
2. Usar Nombres Descriptivos¶
1 2 3 4 5 6 7 8// Bien: nombres claros ERROR_NOMBRE_VACIO ERROR_EMAIL_INVALIDO // Mal: nombres crípticos ERR_1 ERR_NOM E_MAIL
3. Agrupar Errores Relacionados¶
1 2 3 4 5 6 7 8 9 10 11 12// Errores de entrada const errores_t ERRORES_ENTRADA = ERROR_NOMBRE_VACIO | ERROR_EMAIL_INVALIDO | ERROR_DNI_INVALIDO; // Errores de rango const errores_t ERRORES_RANGO = ERROR_EDAD_FUERA_RANGO | ERROR_FECHA_INVALIDA; // Verificar grupo if (errores & ERRORES_ENTRADA) { printf("Hay problemas con los datos de entrada\n"); }
4. Separar Errores de Advertencias¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17typedef enum { // Errores (bits 0-15) ERROR_NOMBRE_VACIO = 1 << 0, ERROR_EMAIL_INVALIDO = 1 << 1, // Advertencias (bits 16-31) WARN_PASSWORD_DEBIL = 1 << 16, WARN_NOMBRE_LARGO = 1 << 17 } validacion_t; const validacion_t MASCARA_ERRORES = 0x0000FFFF; const validacion_t MASCARA_ADVERTENCIAS = 0xFFFF0000; bool solo_advertencias(validacion_t resultado) { return (resultado & MASCARA_ERRORES) == 0 && (resultado & MASCARA_ADVERTENCIAS) != 0; }
Comparación con Alternativas¶
vs. Array de Códigos¶
Bitmask:
errores_t errores = ERROR_NOMBRE_VACIO | ERROR_EMAIL_INVALIDO;
// Tamaño: 4 bytes (un int)
// Verificación: O(1)Array:
int errores[] = {ERROR_NOMBRE_VACIO, ERROR_EMAIL_INVALIDO};
int num_errores = 2;
// Tamaño: 8+ bytes (dos ints + contador)
// Verificación: O(n)vs. Lista Enlazada¶
Bitmask:
Tamaño fijo y predecible
No requiere memoria dinámica
Operaciones muy rápidas
Limitado a número fijo de errores
Lista:
Tamaño variable
Requiere malloc/free
Operaciones más lentas
Ilimitado número de errores
Integración con Otros Patrones¶
Con Único Retorno¶
1 2 3 4 5 6 7 8 9 10 11errores_t procesar_formulario(const formulario_t* form) { errores_t resultado = ERROR_NINGUNO; // Acumular errores resultado |= validar_campos(form); resultado |= validar_formato(form); resultado |= validar_coherencia(form); // Único retorno con todos los errores acumulados return resultado; }
Con Códigos de Estado¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22typedef struct { bool exito; errores_t errores; void* datos; } resultado_operacion_t; resultado_operacion_t realizar_operacion(const datos_t* entrada) { resultado_operacion_t resultado = { .exito = false, .errores = ERROR_NINGUNO, .datos = NULL }; resultado.errores = validar_entrada(entrada); if (resultado.errores == ERROR_NINGUNO) { resultado.datos = procesar(entrada); resultado.exito = (resultado.datos != NULL); } return resultado; }
Resumen¶
Ventajas de códigos de error con bitmasks:
Eficiencia: Representar múltiples errores en un solo entero
Expresividad: Operaciones bitwise claras y concisas
Completitud: Reportar todos los problemas simultáneamente
Flexibilidad: Combinar, agregar, remover errores fácilmente
Performance: Operaciones muy rápidas (solo operaciones de bits)
Cuándo usar:
Validaciones con múltiples campos independientes
Permisos y flags de estado
Diagnóstico de problemas complejos
APIs que deben reportar errores detallados
Cuándo evitar:
Si solo hay un posible error a la vez
Cuando necesitás más de 32-64 códigos diferentes
Si la información del error es muy compleja (usa estructuras)
Los códigos de error con bitmasks son una herramienta poderosa para mejorar la usabilidad de APIs y aplicaciones, permitiendo comunicar de forma eficiente y completa todas las causas de un problema.