Ejercicios de Archivos Binarios¶
Prerrequisitos y Entorno de Ejecución Requerido¶
Para compilar y verificar las soluciones de este módulo bajo el estándar C11 estricto de cátedra, se requiere:
Compilador C11: GCC 9+ o Clang 11+ configurado con flags
-Wall -Wextra -Werror -pedantic -std=c11.Entorno POSIX: Linux o WSL con utilidades estándar, soporte de sistema de archivos y memoria dinámica.
Herramientas de Verificación: Valgrind (memcheck) y AddressSanitizer (
-fsanitize=address,undefined) para auditar fugas de descriptores de archivo, punteros dangling y desbordamientos de búfer en E/S binaria.Conocimientos Previos: Estructuras en C (
struct), tipos fijos, punteros, flujos de E/S estándar y manejo de memoria contigua.
Objetivos Pedagógicos y Competencias (Taxonomía de Bloom)¶
Nivel 2 (Comprensión): Diferenciar flujos de texto vs flujos binarios puros, serialización contigua y desalineación de bytes.
Nivel 3 (Aplicación): Implementar persistencia estructurada mediante
fopen,fread,fwrite,fseekyftell.Nivel 4 (Análisis): Evaluar cálculos de offset seguro con
sizeof(tipo_t), gestión de errores de E/S y cierre garantizado de recursos confclose.Andamiaje Progresivo: Ejercicios andamiados con contratos formales (precondiciones/postcondiciones), matrices de vectores de prueba y suites de aserciones ejecutables con
assert().
Capítulos de Apunte Correspondientes¶
capitulo-archivos-texto
Cuestiones de Estilo Aplicables¶
Chequeo de E/S binaria: Al invocar
freadyfwrite, es mandatorio validar que la cantidad de bloques leídos o escritos coincida con el número solicitado.Acceso aleatorio seguro: Al utilizar
fseek, calcular los desplazamientos multiplicando el índice porsizeof(tipo_t)con origen explícito (SEEK_SET).Limpieza de recursos: Todo archivo abierto con
fopendebe cerrarse confclose.
Estructura de Datos Base¶
Para los siguientes ejercicios, se utilizará la estructura canónica producto_t:
1 2 3 4 5 6 7typedef struct { char sku[12]; char nombre[32]; int stock; float precio; } producto_t;
Escritura y Lectura Secuencial¶
Ejercicio 4.04.01 - Serialización de Inventario en Archivo Binario ⭐⭐☆☆☆¶
Solution to Exercise binarios-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 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#include <assert.h> #include <stdbool.h> #include <stdio.h> #include <string.h> typedef struct { char sku[12]; char nombre[32]; int stock; float precio; } producto_t; bool guardar_inventario(const char *ruta, const producto_t *productos, size_t cantidad) { if (ruta == NULL || (productos == NULL && cantidad > 0)) { return false; } FILE *archivo = fopen(ruta, "wb"); if (archivo == NULL) { return false; } if (cantidad == 0) { fclose(archivo); return true; } size_t escritos = fwrite(productos, sizeof(producto_t), cantidad, archivo); fclose(archivo); return escritos == cantidad; } int main(void) { const char *test_path = "test_inventario_tmp.dat"; producto_t catalogo[2] = { {"SKU-001", "Sensor DHT22", 15, 1250.50f}, {"SKU-002", "Arduino Nano", 8, 4500.00f} }; // Caso normal assert(guardar_inventario(test_path, catalogo, 2) == true); // Caso cero elementos assert(guardar_inventario(test_path, catalogo, 0) == true); // Caso defensivo puntero nulo assert(guardar_inventario(NULL, catalogo, 2) == false); remove(test_path); return 0; }
Ejercicio 4.04.02 - Carga y Conteo de Inventario Persistido ⭐⭐☆☆☆¶
Solution to Exercise binarios-2
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#include <assert.h> #include <stdbool.h> #include <stdio.h> #include <string.h> typedef struct { char sku[12]; char nombre[32]; int stock; float precio; } producto_t; size_t cargar_inventario(const char *ruta, producto_t *destino, size_t capacidad_maxima) { if (ruta == NULL || destino == NULL || capacidad_maxima == 0) { return 0; } FILE *archivo = fopen(ruta, "rb"); if (archivo == NULL) { return 0; } size_t leidos = 0; while (leidos < capacidad_maxima && fread(&destino[leidos], sizeof(producto_t), 1, archivo) == 1) { leidos++; } fclose(archivo); return leidos; } int main(void) { const char *test_path = "test_carga_tmp.dat"; producto_t catalogo[3] = { {"A01", "Resistencia 10k", 100, 25.0f}, {"A02", "Capacitor 100uF", 50, 45.5f}, {"A03", "Transistor 2N2222", 30, 80.0f} }; FILE *f = fopen(test_path, "wb"); assert(f != NULL); fwrite(catalogo, sizeof(producto_t), 3, f); fclose(f); producto_t buffer[5]; // Caso normal size_t total = cargar_inventario(test_path, buffer, 5); assert(total == 3); assert(strcmp(buffer[0].sku, "A01") == 0); assert(strcmp(buffer[2].sku, "A03") == 0); // Caso capacidad acotada size_t parcial = cargar_inventario(test_path, buffer, 2); assert(parcial == 2); // Caso archivo inexistente assert(cargar_inventario("archivo_fantasma_no_existe.dat", buffer, 5) == 0); remove(test_path); return 0; }
Acceso Aleatorio con fseek¶
Ejercicio 4.04.03 - Lectura de Registro Específico mediante Desplazamiento ⭐⭐☆☆☆¶
Solution to Exercise binarios-3
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#include <assert.h> #include <stdbool.h> #include <stdio.h> #include <string.h> typedef struct { char sku[12]; char nombre[32]; int stock; float precio; } producto_t; bool leer_registro(const char *ruta, size_t n, producto_t *resultado) { if (ruta == NULL || resultado == NULL) { return false; } FILE *archivo = fopen(ruta, "rb"); if (archivo == NULL) { return false; } long offset = (long)(n * sizeof(producto_t)); if (fseek(archivo, offset, SEEK_SET) != 0) { fclose(archivo); return false; } size_t leidos = fread(resultado, sizeof(producto_t), 1, archivo); fclose(archivo); return leidos == 1; } int main(void) { const char *test_path = "test_seek_tmp.dat"; producto_t lote[3] = { {"S0", "Item Cero", 10, 100.0f}, {"S1", "Item Uno", 20, 200.0f}, {"S2", "Item Dos", 30, 300.0f} }; FILE *f = fopen(test_path, "wb"); assert(f != NULL); fwrite(lote, sizeof(producto_t), 3, f); fclose(f); producto_t reg; // Caso inicio (n = 0) assert(leer_registro(test_path, 0, ®) == true); assert(strcmp(reg.sku, "S0") == 0); // Caso intermedio (n = 1) assert(leer_registro(test_path, 1, ®) == true); assert(strcmp(reg.sku, "S1") == 0); // Caso último (n = 2) assert(leer_registro(test_path, 2, ®) == true); assert(strcmp(reg.sku, "S2") == 0); // Caso fuera de rango (n = 5) assert(leer_registro(test_path, 5, ®) == false); remove(test_path); return 0; }
Ejercicio 4.04.04 - Modificación In-Situ de un Registro Binario ⭐⭐⭐☆☆¶
Solution to Exercise binarios-4
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 87 88#include <assert.h> #include <stdbool.h> #include <stdio.h> #include <string.h> typedef struct { char sku[12]; char nombre[32]; int stock; float precio; } producto_t; bool actualizar_stock(const char *ruta, size_t n, int nuevo_stock) { if (ruta == NULL || nuevo_stock < 0) { return false; } FILE *archivo = fopen(ruta, "rb+"); if (archivo == NULL) { return false; } long offset = (long)(n * sizeof(producto_t)); if (fseek(archivo, offset, SEEK_SET) != 0) { fclose(archivo); return false; } producto_t reg; if (fread(®, sizeof(producto_t), 1, archivo) != 1) { fclose(archivo); return false; } reg.stock = nuevo_stock; if (fseek(archivo, offset, SEEK_SET) != 0) { fclose(archivo); return false; } size_t escritos = fwrite(®, sizeof(producto_t), 1, archivo); fclose(archivo); return escritos == 1; } int main(void) { const char *test_path = "test_update_tmp.dat"; producto_t lote[2] = { {"A1", "Item A", 10, 100.0f}, {"B2", "Item B", 20, 200.0f} }; FILE *f = fopen(test_path, "wb"); assert(f != NULL); fwrite(lote, sizeof(producto_t), 2, f); fclose(f); // Actualizar registro 1 (B2) assert(actualizar_stock(test_path, 1, 99) == true); // Verificar persistencia del registro 1 y que el registro 0 no se alteró f = fopen(test_path, "rb"); assert(f != NULL); producto_t verificacion[2]; fread(verificacion, sizeof(producto_t), 2, f); fclose(f); assert(verificacion[0].stock == 10); assert(verificacion[1].stock == 99); assert(strcmp(verificacion[1].sku, "B2") == 0); // Caso fuera de rango assert(actualizar_stock(test_path, 10, 50) == false); remove(test_path); return 0; }
Ejercicio 4.04.05 - Borrado Lógico y Auditoría de Registros Binarios ⭐⭐⭐⭐☆¶
Solution to Exercise binarios-5
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119#include <assert.h> #include <stdbool.h> #include <stdio.h> #include <string.h> typedef struct { char sku[12]; char nombre[32]; int stock; float precio; bool activo; } item_inventario_t; bool marcar_baja_logica(const char *ruta, size_t n) { if (ruta == NULL) { return false; } FILE *archivo = fopen(ruta, "rb+"); if (archivo == NULL) { return false; } long offset = (long)(n * sizeof(item_inventario_t)); if (fseek(archivo, offset, SEEK_SET) != 0) { fclose(archivo); return false; } item_inventario_t reg; if (fread(®, sizeof(item_inventario_t), 1, archivo) != 1) { fclose(archivo); return false; } reg.activo = false; if (fseek(archivo, offset, SEEK_SET) != 0) { fclose(archivo); return false; } size_t escritos = fwrite(®, sizeof(item_inventario_t), 1, archivo); fclose(archivo); return escritos == 1; } size_t contar_registros_activos(const char *ruta) { if (ruta == NULL) { return 0; } FILE *archivo = fopen(ruta, "rb"); if (archivo == NULL) { return 0; } size_t total_activos = 0; item_inventario_t reg; while (fread(®, sizeof(item_inventario_t), 1, archivo) == 1) { if (reg.activo) { total_activos++; } } fclose(archivo); return total_activos; } int main(void) { const char *test_path = "test_baja_logica_tmp.dat"; item_inventario_t lote[3] = { {"SKU-1", "Teclado", 15, 45.0f, true}, {"SKU-2", "Mouse", 30, 25.0f, true}, {"SKU-3", "Monitor", 8, 180.0f, true} }; FILE *f = fopen(test_path, "wb"); assert(f != NULL); fwrite(lote, sizeof(item_inventario_t), 3, f); fclose(f); // Conteo inicial assert(contar_registros_activos(test_path) == 3); // Baja del registro intermedio (Mouse) assert(marcar_baja_logica(test_path, 1) == true); assert(contar_registros_activos(test_path) == 2); // Baja del primer registro (Teclado) assert(marcar_baja_logica(test_path, 0) == true); assert(contar_registros_activos(test_path) == 1); // Intento fuera de rango assert(marcar_baja_logica(test_path, 10) == false); assert(contar_registros_activos(test_path) == 1); // Archivo inexistente assert(contar_registros_activos("archivo_que_no_existe_xyz.dat") == 0); remove(test_path); return 0; }
Ejercicio 4.04.06 - Integridad de Cabecera Binaria con Magic Number y Checksum ⭐⭐⭐☆☆¶
Solution to Exercise binarios-6
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124#include <stdio.h> #include <stdint.h> #include <stdbool.h> #include <assert.h> #define MAGIC_UNRN 0x50314150U /* "P1AP" */ typedef struct { uint32_t magic; uint16_t version; uint16_t cant_bytes; uint32_t checksum; } cabecera_archivo_t; static uint32_t calcular_checksum(const uint8_t *datos, uint16_t tam) { uint32_t suma = 0; for (uint16_t i = 0; i < tam; i++) { suma += datos[i]; } return suma; } bool escribir_archivo_protegido(const char *ruta, const uint8_t *payload, uint16_t tam) { if (ruta == NULL || (tam > 0 && payload == NULL)) { return false; } FILE *f = fopen(ruta, "wb"); if (f == NULL) { return false; } cabecera_archivo_t hdr; hdr.magic = MAGIC_UNRN; hdr.version = 1; hdr.cant_bytes = tam; hdr.checksum = calcular_checksum(payload, tam); if (fwrite(&hdr, sizeof(cabecera_archivo_t), 1, f) != 1) { fclose(f); return false; } if (tam > 0 && fwrite(payload, sizeof(uint8_t), tam, f) != tam) { fclose(f); return false; } fclose(f); return true; } bool verificar_y_leer_archivo(const char *ruta, uint8_t *buffer_out, uint16_t cap, uint16_t *bytes_leidos) { if (ruta == NULL || buffer_out == NULL || bytes_leidos == NULL) { return false; } FILE *f = fopen(ruta, "rb"); if (f == NULL) { return false; } cabecera_archivo_t hdr; if (fread(&hdr, sizeof(cabecera_archivo_t), 1, f) != 1) { fclose(f); return false; } if (hdr.magic != MAGIC_UNRN || hdr.version != 1 || hdr.cant_bytes > cap) { fclose(f); return false; } if (hdr.cant_bytes > 0 && fread(buffer_out, sizeof(uint8_t), hdr.cant_bytes, f) != hdr.cant_bytes) { fclose(f); return false; } fclose(f); uint32_t suma_calculada = calcular_checksum(buffer_out, hdr.cant_bytes); if (suma_calculada != hdr.checksum) { return false; } *bytes_leidos = hdr.cant_bytes; return true; } int main(void) { const char *test_file = "test_chk_tmp.bin"; uint8_t datos_orig[4] = {0x10, 0x20, 0x30, 0x40}; uint8_t buffer_dest[16]; uint16_t leidos = 0; // Caso Normal assert(escribir_archivo_protegido(test_file, datos_orig, 4) == true); assert(verificar_y_leer_archivo(test_file, buffer_dest, sizeof(buffer_dest), &leidos) == true); assert(leidos == 4); assert(buffer_dest[0] == 0x10 && buffer_dest[3] == 0x40); // Buffer insuficiente assert(verificar_y_leer_archivo(test_file, buffer_dest, 2, &leidos) == false); // Corrupción de payload en disco FILE *f = fopen(test_file, "rb+"); assert(f != NULL); assert(fseek(f, (long)sizeof(cabecera_archivo_t), SEEK_SET) == 0); fputc(0xFF, f); // Altera el primer byte del payload fclose(f); assert(verificar_y_leer_archivo(test_file, buffer_dest, sizeof(buffer_dest), &leidos) == false); // Corrupción de magic number f = fopen(test_file, "rb+"); assert(f != NULL); assert(fseek(f, 0, SEEK_SET) == 0); uint32_t bad_magic = 0; fwrite(&bad_magic, sizeof(uint32_t), 1, f); fclose(f); assert(verificar_y_leer_archivo(test_file, buffer_dest, sizeof(buffer_dest), &leidos) == false); remove(test_file); return 0; }
Ejercicio 4.04.07 - Compactación Física de Archivos Binarios con Borrado Lógico ⭐⭐⭐⭐☆¶
Solution to Exercise binarios-7
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100#include <stdio.h> #include <stdbool.h> #include <string.h> #include <assert.h> typedef struct { int id; char descripcion[32]; double monto; bool activo; } registro_cuenta_t; bool compactar_archivo_cuentas(const char *ruta_origen, size_t *purgados) { if (purgados != NULL) { *purgados = 0; } if (ruta_origen == NULL) { return false; } FILE *f_in = fopen(ruta_origen, "rb"); if (f_in == NULL) { return false; } const char *ruta_tmp = "tmp_compactacion_aux.dat"; FILE *f_tmp = fopen(ruta_tmp, "wb"); if (f_tmp == NULL) { fclose(f_in); return false; } registro_cuenta_t reg; size_t count_purgados = 0; while (fread(®, sizeof(registro_cuenta_t), 1, f_in) == 1) { if (reg.activo) { if (fwrite(®, sizeof(registro_cuenta_t), 1, f_tmp) != 1) { fclose(f_in); fclose(f_tmp); remove(ruta_tmp); return false; } } else { count_purgados++; } } fclose(f_in); fclose(f_tmp); // Reemplazo atómico if (remove(ruta_origen) != 0 || rename(ruta_tmp, ruta_origen) != 0) { remove(ruta_tmp); return false; } if (purgados != NULL) { *purgados = count_purgados; } return true; } int main(void) { const char *test_path = "test_cuentas_compactar.dat"; // 1. Crear archivo con 4 registros (2 activos, 2 inactivos) registro_cuenta_t inicial[4] = { {1, "Cuenta Alpha", 1500.0, true}, {2, "Cuenta Beta", 0.0, false}, {3, "Cuenta Gamma", 420.5, true}, {4, "Cuenta Delta", -50.0, false} }; FILE *f = fopen(test_path, "wb"); assert(f != NULL); assert(fwrite(inicial, sizeof(registro_cuenta_t), 4, f) == 4); fclose(f); size_t purgados = 0; assert(compactar_archivo_cuentas(test_path, &purgados) == true); assert(purgados == 2); // Verificar contenido compacto en disco FILE *f_post = fopen(test_path, "rb"); assert(f_post != NULL); registro_cuenta_t leidos[4]; size_t total_leidos = fread(leidos, sizeof(registro_cuenta_t), 4, f_post); fclose(f_post); assert(total_leidos == 2); assert(leidos[0].id == 1 && leidos[0].activo == true); assert(leidos[1].id == 3 && leidos[1].activo == true); // 2. Archivo inexistente assert(compactar_archivo_cuentas("archivo_inexistente_xyz.dat", &purgados) == false); remove(test_path); return 0; }