Regla 0x7003h: No ignores valores de retorno que pueden indicar fallo
Robustez y manejo de errores (0x70XX)
0x7003h: No ignores valores de retorno que pueden indicar fallo¶
Enunciado normativo¶
Todo valor de retorno de una función que pueda señalar un fallo (error, valor centinela, puntero
NULL, conteo inesperado) DEBE ser examinado. Si el retorno no se puede manejar localmente, DEBE propagarse explícitamente; NO DEBE descartarse en silencio.
¿Por qué existe esta regla?¶
El problema¶
C expresa los errores por valor de retorno, no por excepciones. Cuando el retorno se ignora, el error se convierte en un dato inválido que circula silenciosamente hasta causar un fallo lejano, difícil de atribuir a su causa.
El descarte de retornos es además una de las fuentes más comunes de vulnerabilidades: una asignación de memoria fallida, una lectura truncada o una escritura parcial ignoradas derivan en desreferencias nulas y desbordamientos.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Comportamiento indefinido | Desreferenciar un malloc que retornó NULL. |
| Datos corruptos | Una lectura parcial se trata como completa. |
| Fallo tardío | El error aparece lejos del punto que lo originó. |
| Seguridad | Escrituras parciales silenciadas comprometen la integridad. |
Fundamento en la cátedra¶
Es la regla general que las reglas 0x4002h, 0x3001h y 0x4008h aplican a
casos concretos. El compilador colabora: -Wunused-result advierte cuando se
descarta un retorno declarado warn_unused_result.
Alcance y excepciones¶
Aplica a funciones de biblioteca y a funciones propias que devuelvan un
estado. No es obligatorio examinar retornos que por contrato nunca fallan
(printf puede fallar, pero en el curso se admite ignorarlo en salida
estándar) ni los de funciones de notificación sin efecto. La clave es
documentar cuándo un retorno puede ignorarse.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — malloc ignorado¶
nodo_t *nuevo = malloc(sizeof(*nuevo));
nuevo->dato = valor;
nuevo->sig = lista;Por qué falla: si la memoria se agota, malloc retorna NULL y el acceso a
nuevo->dato desreferencia un puntero nulo. Viola 0x3001h: Siempre verificá la asignación exitosa de memoria dinámica.
❌ Contraejemplo 2 — Lectura ignorada¶
fread(buffer, sizeof(int), cantidad, archivo);
procesar(buffer, cantidad);Por qué falla: fread puede leer menos elementos (fin de archivo o error).
Procesar cantidad elementos usa datos no inicializados.
✅ Ejemplo conforme 1 — Verificación y propagación¶
nodo_t *nuevo = malloc(sizeof(*nuevo));
if (nuevo == NULL) {
return NULL;
}
nuevo->dato = valor;
nuevo->sig = lista;El fallo se detecta donde ocurre y se propaga como NULL, dejando la
decisión al llamador.
✅ Ejemplo conforme 2 — Lectura verificada¶
size_t leidos = fread(buffer, sizeof(int), cantidad, archivo);
if (leidos != cantidad) {
if (ferror(archivo)) {
perror("fread");
}
return ERROR_LECTURA;
}
procesar(buffer, leidos);Se usa el conteo real y se distingue error de fin de archivo.
⚠️ Casos límite¶
printf/fprintf: pueden fallar (disco lleno), pero en el curso se admite no verificar la salida por pantalla; no así la de archivo (0x4008h: Validación obligatoria del valor de retorno de fclose() en modo escritura).Funciones que devuelven un valor útil y un error combinados: convertir a una variable y decidir con un contrato claro.
(void)cast: descartar un retorno a propósito es aceptable sólo si se documenta; un cast sin explicación es un descarte encubierto.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gcc | gcc -Wall -Wextra | -Wunused-result. |
cppcheck | cppcheck archivo.c | Retornos ignorados. |
kaneda | auditoría de seguridad | Descarto de retornos de funciones críticas. |
Checklist de autocontrol¶
¿Toda función que puede fallar tiene su retorno verificado?
¿Distingo “fin de archivo” de “error”?
¿Propago el error cuando no puedo manejarlo localmente?
Si descarto un retorno, ¿lo documenté?
Reglas relacionadas¶
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — verificación de memoria dinámica.
0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos — retornos de operaciones de archivo.
0x4008h: Validación obligatoria del valor de retorno de fclose() en modo escritura — retorno de
fcloseen escritura.0x7004h: No llamés exit() en funciones de biblioteca: propagá el error — propagar el error sin abortar el programa.
Antipatrón: Descarte del valor retornado por funciones de conversión numérica¶
Síntoma en el código del estudiante¶
Llamadas a strtol, atoi, strtod o scanf cuyo resultado se ignora, o que
se usan sin comprobar errno ni el puntero endptr. El programa “lee el
número” pero no distingue una cadena válida de una basura.
Diagnóstico¶
Mecanismo del defecto¶
strtol convierte la mayor cantidad posible de caracteres y deja de leer en el
primer carácter inválido. Si la cadena empieza con letras, devuelve 0 y el
llamador cree que el usuario ingresó un cero. Si el número excede el rango de
long, la función satura en LONG_MAX o LONG_MIN y activa errno = ERANGE;
si ese error se ignora, el valor saturado se usa como si fuera real.
El descarte del retorno es el mismo defecto que ignorar el de main: se tira
información que el sistema puso a disposición. La regla 0x2012h: Tipo de retorno obligatorio ‘int’ en la función main() recuerda
que el canal de retorno es la forma de reportar el resultado de la operación;
acá ese canal es la tupla (valor, endptr, errno).
Consecuencia observable¶
Entradas inválidas se aceptan como cero o como el valor saturado, y el programa continúa con datos corruptos. No hay error de compilación; la falla es silenciosa y depende de la entrada.
Fundamento en el estándar C11¶
C11 declara strtol, strtoll, strtod y familia en <stdlib.h> (§7.22.1).
La norma especifica que, si el valor no es representable, se devuelve
LONG_MAX/LONG_MIN/LONG_LONG_MAX/LONG_LONG_MIN y se asigna ERANGE a
errno (§7.22.1.4). También define errno en <errno.h> (§7.5). Corresponde
al llamador decidir el comportamiento ante el error; ignorar el retorno es
descartar la parte explícita del contrato de la función.
Corrección idiomática¶
❌ Código con el antipatrón¶
strtol(cadena, NULL, 10);✅ Código refactorizado¶
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
int convertir(const char *cadena, long *resultado)
{
if (cadena == NULL || resultado == NULL) {
return ERROR_PUNTERO_NULO;
}
char *endptr = NULL;
errno = 0;
long valor = strtol(cadena, &endptr, 10);
if (endptr == cadena || *endptr != '\0') {
return ERROR_FORMATO;
}
if (errno == ERANGE) {
return ERROR_RANGO;
}
*resultado = valor;
return OK;
}Se verifica que la conversión haya consumido toda la cadena (*endptr == '\0'),
que no esté vacía (endptr != cadena) y que no haya habido ERANGE. El
llamador recibe un código de error con nombre simbólico (0x2007h: Los valores de retorno numéricos deben definirse como constantes de preprocesador o enums).
Errores típicos al compilar o ejecutar¶
# No hay error de compilación.
$ ./programa
Ingrese un numero: abc
Usted ingreso: 0 # el 0 viene del error, no de la entrada
$ ./programa
Ingrese un numero: 999999999999999999999999
Usted ingreso: 9223372036854775807 # valor saturado por ERANGEChecklist de verificación¶
¿Guardo el resultado de
strtolen una variable?¿Verifiqué
endptrpara saber cuánto se consumió?¿Puse
errno = 0antes y verifiquéERANGEdespués?¿Propagué el error al llamador con un código simbólico?
Reglas relacionadas¶
0x2012h: Tipo de retorno obligatorio ‘int’ en la función main() — regla que norma este defecto: no descartar el canal de retorno.
0x2007h: Los valores de retorno numéricos deben definirse como constantes de preprocesador o enums — los errores de conversión se reportan con símbolos.
0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos — todo retorno de E/S o conversión debe verificarse.
0x2003h: Todas las funciones deben incluir documentación completa y estructurada — documentar el contrato y los valores de retorno posibles.