На перший погляд, це питання може здатися дублікатом Як виявити переповнення цілих чисел? , проте насправді це суттєво відрізняється.
Я виявив, що виявлення переповнення цілого числа без підпису є досить тривіальним, виявляючи підписане переповнення в 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) не надасть компілятору можливості оптимізувати перевірки від переповнення? Я думав, що, можливо, є якийсь спосіб зробити це, перекинувши обидва операнди на непідписані та виконуючи перевірки, прокатуючи власну арифметику доповнення двох, але я не дуже впевнений, як це зробити.