Как педантичный программист предохранится от этого, не делая никаких предположений, не гарантированных стандартом?
Один из методов заключается в использовании целых чисел без знака. Поведение переполнения целых чисел без знака четко определено, как и поведение при преобразовании целого числа со знаком в целое число без знака.
Поэтому я думаю, что следующее должно быть безопасным (оказывается, оно ужасно сломано в некоторых действительно малоизвестных системах, см. далее в посте улучшенную версию)
uintmax_t j = i;
if (j > (uintmax_t)INTMAX_MAX) {
j = -j;
}
printf("Result: |%jd| = %ju\n", i, j);
Так как же это работает?
uintmax_t j = i;
Это преобразует целое число со знаком в беззнаковое. ЕСЛИ оно положительное, значение остается прежним, если отрицательное, значение увеличивается на 2n (где n – количество битов). Это преобразует его в большое число (больше, чем INTMAX_MAX)
if (j > (uintmax_t)INTMAX_MAX) {
Если исходное число было положительным (и, следовательно, меньше или равно INTMAX_MAX), это ничего не делает. Если исходное число было отрицательным, выполняется внутренняя часть блока if.
j = -j;
Число отрицается. Результат отрицания явно отрицательный и поэтому не может быть представлен как целое число без знака. Таким образом, оно увеличивается на 2n.
Итак, алгебраически результат для отрицательного i выглядит так
j = - (i + 2n) + 2n = -i
Умное, но это решение делает предположения. Это не удается, если INTMAX_MAX == UINTMAX_MAX, что разрешено стандартом C.
Хм, давайте посмотрим на это (я читаю https://busybox.net/~landley/c99-draft.html, который, по-видимому, является последним черновиком C99 до стандартизации, если что-то изменилось в окончательном стандарте, пожалуйста, сообщите мне.
Когда определены имена typedef, отличающиеся только отсутствием или наличием начального u, они должны обозначать соответствующие типы со знаком и без знака, как описано в 6.2.5; реализация не должна предоставлять тип без предоставления соответствующего типа.
В 6.2.5 вижу
Для каждого целочисленного типа со знаком существует соответствующий (но другой) целочисленный тип без знака (обозначенный ключевым словом unsigned), который использует тот же объем памяти (включая информацию о знаке) и имеет те же требования к выравниванию.
В 6.2.6.2 вижу
#1
Для целочисленных типов без знака, отличных от unsigned char, биты представления объекта должны быть разделены на две группы: биты значения и биты заполнения (последние не обязательно должны быть). Если имеется N битов значения, каждый бит должен представлять разную степень числа 2 от 1 до 2N-1, так что >объекты этого типа должны быть способны представлять значения от 0 до 2N-1 >используя чисто двоичное представление; это должно быть известно как представление значения. Значения любых битов заполнения не указаны.39)
#2
Для целочисленных типов со знаком биты представления объекта должны быть разделены на три группы: биты значения, биты заполнения и бит знака. Не должно быть битов заполнения; должен быть ровно один бит знака. Каждый бит, являющийся битом значения, должен иметь то же значение, что и тот же бит в объектном представлении соответствующего беззнакового типа (если имеется M битов значения в знаковом типе и N в беззнаковом типе, тогда M‹=N). Если знаковый бит равен нулю, это не должно влиять на результирующее значение.
Так что да, похоже, вы правы, хотя подписанный и неподписанный типы должны быть одинакового размера, кажется, что неподписанный тип имеет на один дополнительный бит больше, чем подписанный тип.
Хорошо, основываясь на приведенном выше анализе, обнаружившем недостаток в моей первой попытке, я написал более параноидальный вариант. Это имеет два изменения по сравнению с моей первой версией.
Я использую i ‹ 0 вместо j > (uintmax_t)INTMAX_MAX для проверки отрицательных чисел. Это означает, что алгоритм выдает правильные результаты для чисел, больших или равных -INTMAX_MAX, даже если INTMAX_MAX == UINTMAX_MAX.
Я добавляю обработку для случая ошибки, когда INTMAX_MAX == UINTMAX_MAX, INTMAX_MIN == -INTMAX_MAX -1 и i == INTMAX_MIN. Это приведет к j=0 внутри условия if, которое мы можем легко проверить.
Из требований стандарта C видно, что INTMAX_MIN не может быть меньше -INTMAX_MAX -1, так как имеется только один бит знака, а количество битов значения должно быть таким же или меньшим, чем в соответствующем беззнаковом типе. Просто не осталось битовых шаблонов для представления меньших чисел.
uintmax_t j = i;
if (i < 0) {
j = -j;
if (j == 0) {
printf("your platform sucks\n");
exit(1);
}
}
printf("Result: |%jd| = %ju\n", i, j);
@plugwash Я думаю, что 2501 правильный. Например, значение -UINTMAX_MAX становится равным 1: (-UINTMAX_MAX + (UINTMAX_MAX + 1)), и оно не перехватывается вашим if. - Хайд 58 минут назад
ммм,
предполагая, что INTMAX_MAX == UINTMAX_MAX и i = -INTMAX_MAX
uintmax_t j = i;
после этой команды j = -INTMAX_MAX + (UINTMAX_MAX + 1) = 1
if (i < 0) {
i меньше нуля, поэтому мы запускаем команды внутри if
j = -j;
после этой команды j = -1 + (UINTMAX_MAX + 1) = UINTMAX_MAX
который является правильным ответом, поэтому не нужно ловить его в случае ошибки.
person
plugwash
schedule
07.02.2016
%d
или других спецификаторов сканирования с целыми числами или с плавающей запятой. Используйте семействоstrto
. И есть только один вид неопределенного поведения — плохой. - person M.M   schedule 07.02.2016fscanf
в стандарте C. В C11 это 7.21.6.2/10, если результат преобразования не может быть представлен в объекте, поведение не определено. Таким образом, семействоscanf
по большей части не подходит для использования в производстве. - person M.M   schedule 08.02.2016