A primera vista, esta pregunta puede parecer un duplicado de ¿Cómo detectar el desbordamiento de enteros? , sin embargo, en realidad es significativamente diferente.
Descubrí que si bien detectar un desbordamiento de enteros sin firmar es bastante trivial, detectar un desbordamiento con signo en C / C ++ es en realidad más difícil de lo que la mayoría de la gente piensa.
La forma más obvia, pero ingenua, de hacerlo sería algo como:
int add(int lhs, int rhs)
{
int sum = lhs + rhs;
if ((lhs >= 0 && sum < rhs) || (lhs < 0 && sum > rhs)) {
/* an overflow has occurred */
abort();
}
return sum;
}
El problema con esto es que de acuerdo con el estándar C, el desbordamiento de enteros con signo es un comportamiento indefinido. En otras palabras, de acuerdo con el estándar, tan pronto como provoque un desbordamiento firmado, su programa es tan inválido como si hubiera desreferenciado un puntero nulo. Por lo tanto, no puede causar un comportamiento indefinido y luego intentar detectar el desbordamiento después del hecho, como en el ejemplo de verificación posterior a la condición anterior.
Aunque es probable que la comprobación anterior funcione en muchos compiladores, no puede contar con ella. De hecho, debido a que el estándar C dice que el desbordamiento de enteros con signo no está definido, algunos compiladores (como GCC) optimizarán la verificación anterior cuando se establezcan los indicadores de optimización, porque el compilador asume que un desbordamiento firmado es imposible. Esto rompe totalmente el intento de verificar el desbordamiento.
Entonces, otra forma posible de verificar el desbordamiento sería:
int add(int lhs, int rhs)
{
if (lhs >= 0 && rhs >= 0) {
if (INT_MAX - lhs <= rhs) {
/* overflow has occurred */
abort();
}
}
else if (lhs < 0 && rhs < 0) {
if (lhs <= INT_MIN - rhs) {
/* overflow has occurred */
abort();
}
}
return lhs + rhs;
}
Esto parece más prometedor, ya que en realidad no sumamos los dos enteros juntos hasta que nos aseguremos de antemano de que realizar dicha adición no resultará en un desbordamiento. Por lo tanto, no causamos ningún comportamiento indefinido.
Sin embargo, esta solución es, desafortunadamente, mucho menos eficiente que la solución inicial, ya que debe realizar una operación de resta solo para probar si su operación de suma funcionará. E incluso si no le importa este (pequeño) impacto en el rendimiento, todavía no estoy del todo convencido de que esta solución sea adecuada. La expresión lhs <= INT_MIN - rhs
parece exactamente el tipo de expresión que el compilador podría optimizar, pensando que el desbordamiento firmado es imposible.
Entonces, ¿hay una mejor solución aquí? ¿Algo que está garantizado para 1) no causar un comportamiento indefinido y 2) no proporcionar al compilador la oportunidad de optimizar las comprobaciones de desbordamiento? Estaba pensando que podría haber alguna manera de hacerlo convirtiendo ambos operandos a unsigned y realizando comprobaciones rodando su propia aritmética en complemento a dos, pero no estoy realmente seguro de cómo hacerlo.