Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Campos de Bits y Operaciones Bitwise

Operadores a nivel de bits y máscaras en C

Universidad Nacional de Río Negro

Prerrequisitos: representación binaria de enteros, operadores lógicos, uint32_t y máscaras.

Objetivos: 1. Construir una máscara para consultar o modificar un bit. 2. Diferenciar campos de bits de una representación binaria portable.

Comprobación de salida: calculá una máscara, aplicala a un valor y justificá qué bits cambian.


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

1
2
3
4
5
6
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 aliasing

Esta 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:

  1. 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.

  2. Uso de memcpy: Copiar los bytes mediante memcpy es 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
#include <stdint.h>
#include <stdio.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_inspect

Aná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.


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 = 200

Endianness

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.

Ejemplo con el valor 0x1A2B3C4D (un int de 32 bits):

DirecciónLittle-endianBig-endian
0x1004D1A
0x1013C2B
0x1022B3C
0x1031A4D

¿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
16
#include <stdint.h>
#include <stdio.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.

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.

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.

4. NOT a nivel de bits (~)

El operador NOT (complemento a uno) invierte todos los bits de su operando. 0 se convierte en 1 y 1 en 0.

5. Desplazamientos (<< y >>)

Ejercicios de Aplicación (Recetario)

1. Obtener el N-ésimo Bit

2. Establecer el N-ésimo Bit

3. Limpiar el N-ésimo Bit

4. Contar Bits Encendidos (Hamming Weight)

5. Verificar si es Potencia de Dos

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.

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
27
28
typedef 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
13
typedef 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
26
errores_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
27
// 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
19
// 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
16
int 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
70
71
72
73
typedef 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
typedef 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
typedef 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
typedef 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
30
typedef 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
26
27
char *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
14
/**
 * 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
// 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
// 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
typedef 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:

Lista:

Integración con Otros Patrones

Con Único Retorno

1
2
3
4
5
6
7
8
9
10
errores_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
typedef 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:

  1. Eficiencia: Representar múltiples errores en un solo entero

  2. Expresividad: Operaciones bitwise claras y concisas

  3. Completitud: Reportar todos los problemas simultáneamente

  4. Flexibilidad: Combinar, agregar, remover errores fácilmente

  5. Performance: Operaciones muy rápidas (solo operaciones de bits)

Cuándo usar:

Cuándo evitar:

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.