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