Обнаружение подписанного переполнения в C / C ++

На первый взгляд, этот вопрос может показаться дубликатом Как обнаружить целочисленное переполнение? , однако на самом деле он существенно отличается.

] Я обнаружил, что хотя обнаружение переполнения беззнакового целого числа довольно тривиально, обнаружение подписанного переполнения в 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 похоже на выражение, которое компилятор мог бы оптимизировать, полагая, что подписанное переполнение невозможно.

Так есть ли здесь лучшее решение? Что-то, что гарантированно 1) не вызывает неопределенного поведения и 2) не предоставляет компилятору возможность оптимизировать проверки переполнения? Я думал, что есть какой-то способ сделать это, приведя оба операнда к беззнаковому и выполнив проверки, свернув свою собственную арифметику с дополнением до двух, но я не совсем уверен, как это сделать.

75
задан Community 23 May 2017 в 12:17
поделиться