Целочисленное переполнение со знаком, внутренние свойства и неопределенное поведение - PullRequest
0 голосов
/ 01 марта 2019

Является ли приведенный ниже очень простой код подверженным неопределенному поведению, так как целочисленное переполнение в результате операции?

static volatile LONG x = LONG_MAX;

InterlockedIncrement(&x);

В соответствии со стандартом, целочисленное переполнение со знаком является неопределенным поведением.Однако здесь мы вышли за рамки стандарта, так как вызываем встроенную функцию компилятора, которая встроена в некоторую сборку.Кроме того, значение x нигде не используется (функция используется только как барьер памяти).

ответ на аналогичный вопрос предполагает, что это не UB.

1 Ответ

0 голосов
/ 01 марта 2019

Я утверждаю, что здесь нет UB, ни в соответствии со стандартом языка (стандарт не распространяется на эту функцию / встроенную функцию), ни в соответствии с реализацией, и есть простой переход.

Вот мои рассуждения ...

InterlockedIncrement() концептуально очень прост, и если бы у него был особый случай, было бы очень трудно пропустить его и не задокументировать его.И в документации не упоминалось ни одного особого случая здесь в течение более 15 лет.

Как бы вы это реализовали?

Если у вас 80486 или лучше, самая естественная реализацияиспользует инструкцию XADD с префиксом LOCK, который атомарно добавляет значение в переменную памяти.Сама по себе инструкция не генерирует никаких исключений переполнения, однако она изменяет регистр EFLAGS, как и инструкция обычного сложения ADD, поэтому можно обнаружить переполнение и действовать в соответствии с ним.В частности, вы можете добавить команду INTO, чтобы превратить условие переполнения в исключение.Или вы можете использовать команду условного перехода JO, чтобы перейти к обработчику переполнения.

Если вы используете 80386 или выше, вы также можете использовать инструкцию XCHG (LOCK - этонеявно с этой инструкцией), чтобы создать цикл, который попытался бы атомарно обновить переменную памяти (так могут быть реализованы InterlockedExchange () и InterlockedCompareExchange (), есть также более удобная (для этой цели) CMPXCHG инструкция, начиная с 80486).В этом случае вам нужно выполнить увеличение регистра как обычно, с помощью инструкции ADD или с помощью инструкции INC, и вы можете опционально обнаружить любое условие переполнения (в EFLAGS.OF) и обработать его, как упомянуто ранее.

Теперь, вы бы хотели бросить INTO или JO во все случаи InterlockedIncrement()?Наверное, нет, определенно не по умолчанию.Людям нравятся их атомные операции, маленькие и быстрые.

Это «немедленный» UB.Как насчет "ползучего" UB?Если бы у вас был такой код C:

  int a = INT_MAX;
  if (a + 1 < a)
    puts("Overflow!");

В настоящее время вы, скорее всего, получите ничего не напечатанное .Современные компиляторы знают, что a + 1 не может юридически (!) Переполниться, и поэтому условие в операторе if может быть взято как ложное независимо от значения a.

Можете ли вы провести аналогичную оптимизацию сInterlockedIncrement()?

Что ж, учитывая, что переменная равна volatile и может действительно измениться в другой поток в любой момент, компилятор не может принять неизменным a из двух чтений из памяти (вы 'Скорее всего, мы напишем a + 1 < a или аналогичные как несколько операторов, и каждый a должен будет быть выбран, если он изменчив).

Также было бы странным контекстом попытаться выполнить оптимизацию.

...