Regla 0x2018h: Los comparadores no deben usar resta sujeta a desbordamiento
Funciones, contratos y modularizacion (0x20XX)
0x2018h: Los comparadores no deben usar resta sujeta a desbordamiento¶
Enunciado normativo¶
Los comparadores NO DEBEN restar sus argumentos; DEBEN devolver -1, 0 o 1 mediante comparaciones sin desbordamiento.
Síntoma en el código del estudiante¶
El comparador clásico de qsort escrito como una sola resta:
return *(int *)a - *(int *)b;. La función entra en pocas líneas, compila sin
advertencias y ordena bien la mayoría de las listas. El problema aparece solo
con valores extremos.
Diagnóstico¶
Mecanismo del defecto¶
La resta entre dos int puede desbordar el rango representable. Si a es
INT_MAX y b es negativo, a - b supera INT_MAX y el resultado no es
representable. En C, el desborde de enteros con signo es comportamiento
indefinido: el procesador puede dar la vuelta (wraparound), saturar o producir
un valor cualquiera. Cuando da la vuelta, el signo del resultado puede invertirse
y el orden de esos dos elementos queda mal.
El error es silencioso en la mayoría de las pruebas porque los valores chicos no se acercan a los bordes. Además, el patrón concentra varias operaciones —desreferenciar, castear, restar— en una sola expresión, lo que lo emparenta con la compactación que la regla 0x2011h: Prohibición de asignaciones múltiples a una variable sin lectura intermedia (dead store) desaconseja.
Consecuencia observable¶
Un arreglo con INT_MAX e INT_MIN puede ordenarse de forma incorrecta o el
programa abortar con SIGFPE/SIGABRT según la plataforma. Con
-fsanitize=undefined aparece runtime error: signed integer overflow.
Fundamento en el estándar C11¶
C11 §6.5 párrafo 5 establece que si una operación produce un resultado no
representable en el tipo, el comportamiento es indefinido. El desborde con
signo no es modular (a diferencia del no signado). qsort está declarada en
<stdlib.h> (§7.22.5) y exige que el comparador devuelva un entero negativo,
cero o positivo, no la diferencia exacta.
Corrección idiomática¶
❌ Código con el antipatrón¶
int cmp(const void *a, const void *b)
{
return *(int *)a - *(int *)b;
}✅ Código refactorizado¶
int cmp(const void *a, const void *b)
{
int va = *(const int *)a;
int vb = *(const int *)b;
if (va < vb) {
return -1;
}
if (va > vb) {
return 1;
}
return 0;
}Las comparaciones nunca desbordan y el resultado tiene el signo correcto en todo
el dominio. Además los punteros se reciben como const (0x3007h: Los argumentos de tipo puntero deben ser const siempre que la función no los modifique) y no se
mezclan varias operaciones en una sola expresión.
Errores típicos al compilar o ejecutar¶
$ gcc -fsanitize=undefined comparador.c && ./a.out
comparador.c:3:22: runtime error: signed integer overflow:
2147483647 - (-2147483648) cannot be represented in type 'int'Checklist de verificación¶
¿El comparador resta directamente los valores?
¿Probé con
INT_MAXeINT_MIN?¿El resultado usa comparaciones en lugar de la diferencia?
¿Recibí los punteros como
const void *?
Reglas relacionadas¶
0x2011h: Prohibición de asignaciones múltiples a una variable sin lectura intermedia (dead store) — regla que norma este defecto: evitar expresiones compactas que descartan o arriesgan valores.
0x3007h: Los argumentos de tipo puntero deben ser const siempre que la función no los modifique — los punteros de solo lectura se declaran
const.0x2007h: Los valores de retorno numéricos deben definirse como constantes de preprocesador o enums — los resultados del comparador se documentan como contrato.
0x2003h: Todas las funciones deben incluir documentación completa y estructurada — documentar precondiciones del comparador y su dominio.