Сборка / арка ЦП: всегда ли -INT_MIN равно INT_MIN?

Во-первых, извините за злоупотребление терминологией C "INT_MIN", когда я говорю о программировании на ассемблере. Но позвольте мне продолжить ...

Например, в Linux i386 или x86_64 функция C int neg(int x) { return -x; } будет обычно преобразовываться в movl %edi, %eax; negl %eax; ret. Поскольку x86 neg определяется как принимающий два дополнения к операнду (см. справочное руководство Intel64 для подробностей) f применяется к INT_MIN, битовое представление которого 0x80000000u, возвращает число, битовое представление которого равно 0x7fffffffu + 1 == 0x80000000u или INT_MIN, без каких-либо исключений / ловушек.

Мой вопрос: существуют ли (современные) архитектуры ЦП, в которых самая фундаментальная инструкция «отрицания» не делает то же самое? ARM, SPARC, MIPS, мощность? Встроенные процессоры? Вызывают ли они какие-либо исключения или впадают в неопределенное поведение? (Кстати, я предполагаю, что у некоторых из них есть только инструкция «вычитания».)

Мне просто любопытно, насколько переносим этот фрагмент кода в ntpdate(8):

(Грубо говоря, он концептуально делает dostep = (NTPDATE_THRESHOLD <= abs(server->soffset));, принимая abs(INT_MIN) == INT_MIN из-за вышеуказанного факта.)


person nodakai    schedule 30.01.2016    source источник
comment
Хотя технически это может сработать, я считаю, что стандарт C определяет это как неопределенное поведение, которое позволяет компилятору генерировать любой код, который он хочет, поэтому эта программа неверна (если она действительно делает то, что вы говорите, что на первый взгляд не очевидно) . Обратите внимание, что, например, gcc становится все более агрессивным в (ab) с использованием неопределенного поведения.   -  person Jester    schedule 31.01.2016
comment
Код - это C, а не сборка, и поведение не определено в C, поэтому компиляторам разрешено (и некоторые делают) оптимизацию, исходя из предположения, что server->soffset никогда не INT_MIN   -  person Raymond Chen    schedule 31.01.2016
comment
Я полагаю, вы получите ловушку при использовании sub $v0, $zero, $a0 для $a0 == INT_MIN MIPS.   -  person EOF    schedule 31.01.2016
comment
@EOF Спасибо, искал такую ​​информацию. Да, на MIPS (и на Alpha, судя по некоторым поисковым запросам в Интернете) такой хакерство вызывает подозрения, но, по крайней мере, мой Clang 3.7 предпочитает использовать subu и negu, которые не захватывают.   -  person nodakai    schedule 31.01.2016
comment
@nodakai: gcc имеет параметр -ftrapv, который должен использовать подписанные инструкции по захвату.   -  person ninjalj    schedule 01.02.2016


Ответы (1)


Давайте посмотрим на рассматриваемый код (сокращенный до минимального примера).

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
comment
bool dostep (int32_t absoffset) ... Извините, а что такое bool? Ваше исправление даже не компилируется с компилятором, совместимым с C99, не говоря уже о переносимости :-) - person nodakai; 31.01.2016
comment
Кроме того, код NTP пытается быть переносимым на платформы без C99 intNN_t или даже long long. Предполагая, что у нас есть GCC и такое оборудование, как вы описали, слишком много. На самом деле ntpdate - просто гражданин второго сорта в пакете NTP и, как известно, имеет так много проблем, что команда NTP даже отказалась от него. Это одна из причин, по которой вышеупомянутый сомнительный фрагмент кода сохранился до сегодняшнего дня. - person nodakai; 31.01.2016
comment
В C99 есть <stdbool.h> и <cstdint.h>, которые предоставляют bool и int32_t. - person 5gon12eder; 31.01.2016
comment
Позор мне, вы совершенно правы. Я думал, что C99 идет только с _Bool. Но в любом случае, ... - person nodakai; 31.01.2016