Я видел багги код в C, который использовался для проверки того, добавляет ли результат переполнение или нет. Он отлично работает с char
, но дает неправильный ответ, когда аргументы являются int
и я не мог понять, почему.
Вот код с short
аргументами.
short add_ok( short x, short y ){ short sum = x+y; return (sum-x==y) && (sum-y==x); }
Эта версия работает нормально, проблема возникает при изменении аргументов в int
(вы можете проверить его с помощью INT_MAX
)
Вы видите, что здесь не так?
Поскольку в дополнении 2s целые числа могут быть расположены в круг (в смысле модульной арифметики ). Добавление y, а затем вычитание y всегда возвращает вас туда, где вы начали (несмотря на неопределенное поведение).
В вашем коде добавление не переполняется, если int
имеет такой же размер, как short
. Из-за рекламных акций по умолчанию x+y
выполняется для значений x
и y
направленных на int
, а затем результат усекается до short
в соответствии с реализацией.
Почему бы просто не сделать: return x+y<=SHRT_MAX && x+y>=SHRT_MIN;
В языке программирования C целые числа со знаком при преобразовании в меньшие значащие целые числа, например char (для простоты), имеют определенную реализацию. Несмотря на то, что многие системы и программисты предполагают переполнение оболочки, это не стандарт. Итак, что такое переполнение?
Переполнение обтекания в системах дополнений Two происходит так, что когда значение больше не может быть представлено в текущем типе, оно искажается вокруг самого высокого или самого низкого числа, которое может быть представлено. Так что это значит? Взглянуть.
В подписанном символе наибольшее значение может быть представлено 127, а самое низкое – -128. Тогда что происходит, когда мы делаем: «char i = 128», является то, что значение, хранящееся в i, становится -128. Поскольку значение было больше, чем тип подписанного интеграла, он обернулся вокруг самого низкого значения, и если он был «char i = 129», тогда я буду содержать -127. Видишь? Всякий раз, когда конец достигает своего максимума, он обтекает другой конец (знак). И наоборот, если «char i = -129», тогда я буду содержать 127, а если это «char i = -130», он будет содержать 126, поскольку он достиг своего максимума и обернут вокруг самого высокого значения.
(самый высокий) 127, 126, 125, …, -126, -127, -128 (самый низкий)
Если значение очень велико, оно продолжает обертываться до тех пор, пока оно не достигнет значения, которое может быть представлено в его диапазоне.
ОБНОВЛЕНИЕ: причина, по которой int
не работает в противопоставлении char
и short
заключается в том, что при добавлении обоих чисел существует возможность переполнения (независимо от того, является ли int
, short
или char
, а не забывает интегральную раскрутку), а потому "short"
и char
имеют меньшие размеры, чем int
и поскольку они продвигаются до int
в выражениях, они снова отображаются без усечения в этой строке:
return (sum-x==y) && (sum-y==x);
Таким образом, любое переполнение обнаруживается, как объясняется ниже, но когда с int
он не продвигается ни на что, то происходит переполнение. Например, если я делаю INT_MAX+1
, то результат будет INT_MIN, и если я проверил на переполнение INT_MIN-1 == INT_MAX, результат будет TRUE! Это связано с тем, что «short» и char получают до int, оцениваются и затем усекаются (переполняются). Тем не менее, int сначала переполняется, а затем оценивается, потому что их не повышают до большего размера.
Подумайте о типе char без продвижения по службе и попробуйте сделать переполнение и проверить их, используя приведенную выше иллюстрацию. Вы обнаружите, что добавление или вычитание значений, вызывающих переполнение, возвращает вас туда, где вы были. Однако это не то, что происходит в C, потому что char и «short» продвигаются до int, поэтому обнаруживается переполнение, что неверно в int, потому что это примечание, увеличенное до большего размера.
КОНЕЦ ОБНОВЛЕНИЯ
На ваш вопрос, я проверил ваш код в MinGW и Ubuntu 12.04, кажется, работает нормально. Позднее я обнаружил, что код действительно работает в системах, где short меньше, чем int, и когда значения не превышают диапазон int. Эта строка:
return (sum-x==y) && (sum-y==x);
истинно, потому что «sum-x» и «y» оцениваются как (int), поэтому не происходит обертывания, где это произошло в предыдущей строке (при назначении):
short sum = x+y;
Вот тест. Если я ввел 32767 для первого и 2 для второго, тогда, когда:
short sum = x+y;
сумма будет содержать -32767, из-за обертки. Однако, когда:
return (sum-x==y) && (sum-y==x);
«sum-x» (-32767 – 32767) будет равняться y (2) (тогда багги), если произойдет обход-раунд, но из-за цельной рекламы это никогда не произойдет, и значение «sum-x» станет – 65534, который не равен y, что приводит к правильному обнаружению.
Вот код, который я использовал:
#include short add_ok( short x, short y ){ short sum = x+y; return (sum-x==y) && (sum-y==x); } int main(void) { short i, ii; scanf("%hd %hd", &i, &ii); getchar(); printf("%hd", add_ok(i, ii)); return 0; }
Проверьте здесь и здесь .
Вам необходимо предоставить архитектуру, над которой вы работаете, и каковы экспериментальные значения, которые вы тестировали, потому что не каждый сталкивается с тем, что вы говорите, и из-за специфики реализации вашего вопроса.
Ссылка: C99 6.3.1.3 здесь и руководство GNU C здесь .
Компилятор, вероятно, просто заменяет все вызовы этого выражения на 1, потому что это верно в каждом случае. Оптимизирующая процедура будет выполнять распространение копии по сумме и получить
return (y==y) && (x==x);
а потом:
return 1
Это верно в каждом случае, потому что подписанное целочисленное переполнение является неопределенным поведением – следовательно, компилятор может гарантировать, что x + yy == x и y + xx == y.
Если это была неподписанная операция, она потерпела бы неудачу аналогично, поскольку переполнение просто выполняется как операция по модулю, довольно просто доказать, что
x+y mod SHRT_MAX - y mod SHRT_MAX == x
и аналогично для обратного случая.