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.

Regla 0x4005h: Prescindí del uso de offsets y posiciones fijas codificadas a mano en archivos binarios sin validar sus dimensiones

Archivos y E/S (0x40XX)

Universidad Nacional de Río Negro

0x4005h: Prescindí del uso de offsets y posiciones fijas codificadas a mano en archivos binarios sin validar sus dimensiones

Enunciado normativo

NO DEBE accederse a posiciones arbitrarias de un archivo binario con offsets literales sin comprobar antes que la posición pertenece al rango válido del archivo. El tamaño DEBE calcularse con fseek/ftell o conocerse por metadatos, nunca suponerse.

Un offset es un índice: antes de saltar hay que verificar que cae dentro del archivo y que la lectura posterior puede completarse.

¿Por qué existe esta regla?

El problema

fseek permite posicionar el cursor más allá del fin de archivo: el estándar lo autoriza y la operación puede retornar éxito. El error no aparece en el fseek sino en la lectura siguiente, que devuelve cero elementos. Si el programa no verifica el offset ni el retorno de fread, procesa un búfer sin inicializar como si contuviera datos válidos.

Hay un segundo problema, típico de la E/S binaria: el tamaño serializado de un registro no tiene por qué coincidir con sizeof de la struct en memoria. El compilador inserta relleno (padding) para alinear los campos, y ese relleno no se escribe necesariamente en el archivo. Además, el orden de bytes (endianness) puede diferir entre plataformas. Un offset calculado como indice * sizeof(struct registro) es, por lo tanto, una suposición frágil.

Consecuencias de violarla

Tipo de consecuenciaEfecto concreto
Bug silenciosoUna lectura fuera de rango devuelve cero elementos y el búfer queda con basura.
Datos incorrectosUn registro se interpreta corrido porque el padding cambió el tamaño real.
No portabilidadEl formato binario asumido deja de funcionar al cambiar de compilador o arquitectura.
Ruptura por cambio de formatoAgregar un campo a la struct invalida todos los offsets literales.

Fundamento en el estándar y en la cátedra

ISO/IEC 9899:2011 §7.21.9.2 describe fseek, que retorna cero si tiene éxito y un valor distinto de cero si falla; no garantiza que la posición exista. §7.21.9.4 define ftell, que combinado con fseek(..., SEEK_END) permite obtener el tamaño. La cátedra adopta la regla para prevenir accesos fuera de rango (0x300Ch: Verificá siempre los límites de los arreglos antes de acceder a sus elementos) y para desaconsejar formatos binarios que dependan del layout en memoria.

Alcance y excepciones

Aplica a todo acceso posicional con fseek, fsetpos, ftell o fgetpos sobre archivos binarios, en lectura o escritura. No prohíbe los offsets: exige que se deriven de constantes con nombre, dimensiones calculadas o metadatos del formato, y que se validen antes de usar.

Excepción razonable: un archivo cuyo formato fija posiciones constantes documentadas (por ejemplo, una cabecera en el byte 0) puede usar constantes simbólicas con nombre (ver 0x010Fh: Prohibición de constantes numéricas mágicas en índices de arreglos), siempre que valide el tamaño.

Ejemplos exhaustivos

❌ Contraejemplo 1 — Offset literal sin comprobar el tamaño

fseek(f, 1000, SEEK_SET);
fread(buf, 1, 10, f);

Por qué falla: si el archivo tiene menos de 1000 bytes, fseek posiciona más allá del fin (o falla, y su retorno se ignora), y fread devuelve cero. El búfer buf queda con datos indeterminados que el programa tratará como válidos.

❌ Contraejemplo 2 — Asumir que el layout en memoria es el layout en disco

struct registro r;
fseek(f, indice * sizeof(struct registro), SEEK_SET);
fread(&r, sizeof(r), 1, f);

Por qué falla: sizeof(struct registro) incluye padding que puede no estar serializado, y los campos pueden tener otro orden de bytes. El offset calculado apunta a un lugar incorrecto aunque el índice sea válido.

✅ Ejemplo conforme 1 — Calcular y validar dimensiones antes de saltar

long tamano_archivo(FILE *f)
{
    if (fseek(f, 0, SEEK_END) != 0) {
        return -1;
    }
    long fin = ftell(f);
    fseek(f, 0, SEEK_SET);
    return fin;
}

El tamaño se obtiene de la fuente autoritativa y queda disponible para validar cualquier offset antes de usarlo; el retorno de cada fseek se controla.

✅ Ejemplo conforme 2 — Offset validado y lectura verificada

long tam = tamano_archivo(f);
long pos = (long)indice * (long)TAM_REGISTRO;
if (tam < 0 || pos < 0 || pos + TAM_REGISTRO > tam) {
    return -1;
}
if (fseek(f, pos, SEEK_SET) != 0) {
    return -1;
}
size_t leidos = fread(&r, 1, TAM_REGISTRO, f);
if (leidos != TAM_REGISTRO) {
    return -1;
}

La constante TAM_REGISTRO documenta el tamaño serializado (no el de la struct), la cota se comprueba con < y > antes del salto, y tanto fseek como fread se verifican (0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos).

⚠️ Casos límite

Cómo detectarla

HerramientaComandoSeñal
gaffgaff check archivo.cRegla 0x4005h: fseek con literal sin validación previa.
gccgcc -Wall -Wextra -std=c11 archivo.cSin señal directa; requiere revisión.
Revisión manualTodo fseek debe estar precedido por un cálculo de tamaño y una cota.

Checklist de autocontrol

Reglas relacionadas