На первый взгляд, этот вопрос может показаться дубликатом Как обнаружить целочисленное переполнение? , однако на самом деле он значительно отличается.
Я обнаружил, что при обнаружении неподписанногоЦелочисленное переполнение довольно тривиально, обнаружить переполнение в виде подписи в C / C ++ на самом деле сложнее, чем думает большинство.
Самый очевидный, но наивный способ сделать это будет что-то вроде:
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;
}
Проблема в том, что согласно стандарту C целочисленное переполнение со знаком равно неопределенному поведению. Другими словами, согласно стандарту, как только вы даже вызоветепереполнение со знаком, ваша программа так же недействительна, как если бы вы разыменовали нулевой указатель.Таким образом, вы не можете вызвать неопределенное поведение, а затем попытаться обнаружить переполнение после факта, как в приведенном выше примере проверки после условия.
Несмотря на то, что вышеупомянутая проверка, вероятно, будет работать на многих компиляторах, выне могу рассчитывать на это.Фактически, поскольку стандарт C говорит, что целочисленное переполнение со знаком не определено, некоторые компиляторы (например, GCC) будут оптимизировать вышеприведенную проверку , когда установлены флаги оптимизации, поскольку компилятор предполагает, что переполнение со знаком невозможно.Это полностью исключает попытку проверки на переполнение.
Итак, еще один возможный способ проверки на переполнение будет выглядеть следующим образом:
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;
}
Это выглядит более многообещающим, поскольку мы на самом деле не добавляемдва целых числа вместе, пока мы заранее не убедимся, что выполнение такого добавления не приведет к переполнению.Таким образом, мы не вызываем неопределенного поведения.
Однако это решение, к сожалению, намного менее эффективно, чем первоначальное решение, поскольку вам нужно выполнить операцию вычитания, чтобы проверить, будет ли работать ваша операция сложения.И даже если вы не заботитесь об этом (небольшом) падении производительности, я все еще не совсем уверен, что это решение адекватно.Выражение lhs <= INT_MIN - rhs
выглядит точно так же, как выражение, которое компилятор может оптимизировать, думая, что переполнение со знаком невозможно.
Так есть ли здесь лучшее решение?Что-то, что гарантировано: 1) не вызывает неопределенного поведения, и 2) не предоставляет компилятору возможность оптимизировать проверку переполнения?Я думал, что мог бы быть какой-то способ сделать это, приведя оба операнда к беззнаковому, и выполнив проверки, свернув арифметику с двумя собственными дополнениями, но я не совсем уверен, как это сделать.