Давайте посмотрим на рассматриваемый код (сокращенный до минимального примера).
bool
dostep(int32_t absoffset)
{
if (absoffset < 0)
absoffset = -absoffset;
return (absoffset >= NTPDATE_THRESHOLD || absoffset < 0);
}
Ясно, что выражение absoffset < 0
, которое является вторым операндом ||
, никогда не может быть истинным без переполнения. Но целочисленное переполнение является неопределенным поведением в C. Следовательно, компилятор вполне может оптимизировать проверку.
Неважно, как машина справится с целочисленным переполнением, если она выполнит инструкцию. Если вы программируете на C, вы не программируете оборудование, вы программируете абстрактную машину, определенную стандартом C. Чтобы оценить производительность, полезно знать, как работает настоящее оборудование. Однако опасно делать предположения о коде, который вызывает неопределенное поведение, исходя из ожиданий того, как компилятор переведет сломанный код в машинный код. Компилятору разрешено генерировать любой код, если он заставляет реальную машину вести себя так, как стандарт требует ее для абстрактной машины. А поскольку в стандарте конкретно ничего не говорится о неопределенном поведении, никаких предположений в этом случае делать нельзя. Проще говоря: код NTP сломан.
Одно из возможных исправлений - это изменить порядок проверки таким образом.
bool
dostep(int32_t absoffset)
{
if (absoffset < 0)
absoffset = (absoffset == INT32_MIN) ? INT32_MAX : -absoffset;
return (absoffset >= NTPDATE_THRESHOLD);
}
Однако в данном конкретном случае существует еще более простое решение.
bool
dostep(const int32_t absoffset)
{
return ((absoffset <= -NTPDATE_THRESHOLD) || (absoffset >= NTPDATE_THRESHOLD));
}
Другой вариант - использовать встроенную сборку, которая не подчиняется правилам стандарта C и, очевидно, не является переносимой.
Честно говоря, реализация может предоставлять свои собственные расширения стандарта C для определения неопределенного поведения. GCC и Clang делают это для целочисленного переполнения, предоставляя флаг -fwrapv
. На странице руководства GCC:
-fwrapv
Этот параметр указывает компилятору предположить, что знаковое арифметическое переполнение сложения, вычитания и умножения происходит вокруг с использованием представления с дополнением до двух. Этот флаг включает одни оптимизации и отключает другие. Эта опция включена по умолчанию для внешнего интерфейса Java, как того требует спецификация языка Java.
Если бы код NTP был скомпилирован с GCC, и этот флаг и целевое оборудование реализовали бы подписанное целочисленное переполнение, чтобы обернуть его вокруг, тогда код был бы правильным. Но это уже не переносимый стандарт C.
person
5gon12eder
schedule
31.01.2016
server->soffset
никогда неINT_MIN
- person Raymond Chen   schedule 31.01.2016sub $v0, $zero, $a0
для$a0 == INT_MIN
MIPS. - person EOF   schedule 31.01.2016subu
иnegu
, которые не захватывают. - person nodakai   schedule 31.01.2016-ftrapv
, который должен использовать подписанные инструкции по захвату. - person ninjalj   schedule 01.02.2016