Regla 0x5008h: Prohibición de funciones obsoletas o inseguras (gets, atoi)
Compilacion, preprocesador y seguridad (0x50XX)
0x5008h: Prohibición de funciones obsoletas o inseguras (gets, atoi)¶
Enunciado normativo¶
NO DEBEN usarse
gets(eliminada en C11) niatoi. Para leer texto se usafgets; para convertir a entero,strtolcon verificación de errores.
¿Por qué existe esta regla?¶
El problema¶
gets lee sin conocer el tamaño del destino, así que desborda el buffer con
entradas largas; el comité de C la retiró por ser imposible de usar con
seguridad. atoi convierte una cadena a int pero no informa si la
conversión fue válida: si la cadena no es numérica devuelve 0 (indistinguible
de un cero legítimo) y si el valor excede el rango de int el comportamiento es
indefinido. El programa sigue como si nada.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Comportamiento indefinido | gets desborda la pila; atoi con desbordamiento es UB (C11 §7.22.1.2). |
| Bug silencioso | atoi("abc") devuelve 0; el error nunca se detecta. |
| Compilación/enlace | gets no existe en C11 ni en glibc reciente: error de enlace. |
Fundamento en el estándar y en la cátedra¶
ISO/IEC 9899:2011 §7.22.1.2 describe atoi y explicita que si el valor no puede
representarse el comportamiento es indefinido. gets fue eliminada del
estándar, por lo que su uso es directamente no conforme. strtol (§7.22.1.4)
provee endptr y errno, y fgets (§7.21.7.2) acota la lectura. La cátedra
evalúa con entradas adversarias, de modo que ambas prohibiciones son
obligatorias.
Alcance y excepciones¶
Aplica a todo el código. Excepción discutible: atoi("42") sobre un literal
no puede fallar, pero la cátedra prefiere uniformidad y prohíbe la función
siempre. Para punto flotante rige lo mismo con atof, reemplazada por strtod.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — gets para leer una línea¶
#include <stdio.h>
void pedir_nombre(char *buf)
{
printf("Nombre: ");
gets(buf);
}Por qué falla: gets no sabe cuánto mide buf y escribe toda la línea. Además,
el archivo no compila con -std=c11 porque la función ya no está declarada.
❌ Contraejemplo 2 — atoi sin control de error¶
#include <stdlib.h>
int convertir(const char *texto)
{
return atoi(texto);
}Por qué falla: atoi("hola") devuelve 0, y atoi("999999999999") desborda con
comportamiento indefinido. No hay forma de saber, desde el retorno, si la
conversión fue correcta.
✅ Ejemplo conforme 1 — fgets acotado con control de retorno¶
#include <stdio.h>
#include <string.h>
int pedir_nombre(char *buf, size_t tam)
{
if (fgets(buf, tam, stdin) == NULL) {
return -1;
}
buf[strcspn(buf, "\n")] = '\0';
return 0;
}fgets nunca escribe más de tam - 1 bytes y avisa con NULL si hubo error o
fin de archivo. El retorno entero comunica el fallo al llamador.
✅ Ejemplo conforme 2 — strtol con validación completa¶
#include <errno.h>
#include <stdlib.h>
int convertir(const char *texto, long *resultado)
{
char *fin = NULL;
errno = 0;
long valor = strtol(texto, &fin, 10);
if (fin == texto || *fin != '\0' || errno == ERANGE) {
return -1;
}
*resultado = valor;
return 0;
}strtol informa por partida doble: fin señala dónde terminó la conversión (si
no avanzó o sobró texto, falló) y errno == ERANGE detecta el desbordamiento de
long. Convive con 0x500Bh: Inclusión obligatoria de cabeceras estándar para funciones de la biblioteca C (incluir cabeceras) y con 0x5002h: Desarrollá y compilá siempre con todas las advertencias del compilador activadas.
⚠️ Casos límite¶
atoisobre literal: técnicamente seguro, pero prohibido por uniformidad.strtolconerrno: hay que ponerlo en0antes de llamar; si no, se arrastra un error previo.Base explícita: pasá
10; con base0,"0x10"es hexadecimal.Rango de
intvslong:strtolpuede dar unlongválido que no entra enint; compará contraINT_MIN/INT_MAXde<limits.h>.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Reporta 0x5008h ante gets y atoi. |
gcc / clang | gcc -std=c11 -Wall -Wextra -Werror -pedantic archivo.c | implicit declaration of function 'gets' y fallo de enlace. |
Checklist de autocontrol¶
¿Reemplacé
getsporfgetscon el tamaño del buffer?¿Reemplacé
atoiporstrtol?¿Verifiqué
endptrpara detectar texto no consumido?¿Puse
errno = 0antes destrtoly controléERANGEy el rango?
Reglas relacionadas¶
0x5006h: Preferí fgets sobre gets y scanf para leer cadenas — la contraparte segura para leer cadenas.
0x500Bh: Inclusión obligatoria de cabeceras estándar para funciones de la biblioteca C — incluí
<stdlib.h>,<errno.h>y<limits.h>para validar las firmas.0x5002h: Desarrollá y compilá siempre con todas las advertencias del compilador activadas —
-Wall -Wextra -Werrorpone en evidencia las advertencias asociadas.
Antipatrón: Invocación de la función prohibida gets()¶
Síntoma en el código del estudiante¶
Lecturas de cadenas con gets a un búfer de tamaño fijo:
char buf[100];
gets(buf);También aparece la conversión ciega con atoi, que la misma regla prohíbe:
int cantidad = atoi(entrada);Ambas funciones carecen de toda verificación de límites o de errores.
Diagnóstico¶
Mecanismo del defecto¶
gets lee caracteres desde stdin hasta encontrar un salto de línea o el
fin de archivo, y no recibe el tamaño del búfer destino. Escribe cuantos
caracteres lleguen, agregando después el '\0': cualquier línea más larga
que el arreglo desborda la pila. Por ese motivo fue declarada obsoleta en
C99 y eliminada de la biblioteca estándar en C11. No existe una versión
segura de gets; su hueco lo ocupa fgets.
atoi falla en silencio: devuelve 0 tanto si la cadena es "0" como si
es "hola" o si el valor excede el rango de int (desbordamiento
indefinido). No hay forma de distinguir una entrada válida de una basura.
Consecuencia observable¶
Con glibc moderna y
-std=c11, el compilador ni siquiera declaragets: falla la compilación con una declaración implícita.En entornos que la conservan, una línea larga produce
*** stack smashing detected ***oSIGSEGV.Con
atoi, el programa acepta"abc"como0y opera con un dato inválido sin avisar.
Fundamento en el estándar C11¶
gets ya no forma parte de ISO C11: se retiró de <stdio.h> por
irrecuperablemente insegura. La función de reemplazo es fgets
(C11 §7.21.7.2), que recibe n y lee a lo sumo n - 1 caracteres más el
terminador, por lo que nunca desborda. Para conversiones robustas se usa
strtol (C11 §7.22.1.4), que informa mediante endptr dónde se detuvo el
análisis y permite detectar errores de rango con errno.
Corrección idiomática¶
❌ Código con el antipatrón¶
#include <stdio.h>
#include <stdlib.h>
void leer_nombre(void)
{
char buf[100];
gets(buf);
int cantidad = atoi(buf);
printf("%s %d\n", buf, cantidad);
}✅ Código refactorizado¶
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void leer_nombre(void)
{
char buf[100];
if (fgets(buf, sizeof(buf), stdin) == NULL) {
return;
}
buf[strcspn(buf, "\n")] = '\0';
char *fin = NULL;
errno = 0;
long cantidad = strtol(buf, &fin, 10);
if (fin == buf || *fin != '\0' || errno == ERANGE) {
printf("Entrada invalida\n");
return;
}
printf("%s %ld\n", buf, cantidad);
}fgets conserva el salto de línea; se recorta con strcspn para no
arrastrarlo a la comparación ni a la conversión.
Errores típicos al compilar o ejecutar¶
aviso.c:6:5: error: implicit declaration of function 'gets'; did you mean 'fgets'? [-Wimplicit-function-declaration]
6 | gets(buf);
| ^~~~
| fgetsEn ejecución, cuando el binario aún enlaza una versión vieja:
*** stack smashing detected ***: terminated
Aborted (core dumped)Checklist de verificación¶
¿Aparece
getsen el código? (debe ser cero ocurrencias)¿Toda lectura de cadena usa
fgetsconsizeofdel búfer?¿Recorté el
'\n'que dejafgets?¿Reemplacé
atoi/atofporstrtol/strtodcon validación deendptryerrno?¿Compilé con
-std=c11 -Wall -Wextra?
Reglas relacionadas¶
0x5008h: Prohibición de funciones obsoletas o inseguras (gets, atoi) — prohíbe
getsyatoipor obsoletas e inseguras.0x5006h: Preferí fgets sobre gets y scanf para leer cadenas — canoniza
fgetscomo lectura segura de cadenas.0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos — verificá el retorno de las operaciones de lectura.