Считает ли Visual С++ подписанное целочисленное переполнение undefined?

В последнее время большое внимание уделяется тому, что подписанное целочисленное переполнение официально undefined в C и С++. Однако данная реализация может решить ее определить; в С++ реализация может установить std::numeric_limits<signed T>::is_modulo в true, чтобы указать, что знаковое целочисленное переполнение корректно определено для этого типа и обертывается как целые числа без знака.

Visual С++ устанавливает std::numeric_limits<signed int>::is_modulo в true. Это вряд ли было надежным показателем, поскольку GCC установил это значение в течение многих лет и имеет undefined подписанное переполнение. Я никогда не сталкивался с ситуацией, в которой оптимизатор Visual С++ сделал что-либо, но при условии, что поведение wraparound для целых чисел со знаком - до этой недели.

Я нашел случай, когда оптимизатор испустил код сборки x86-64, который действовал неправильно, если значение точно INT_MAX было передано определенной функции. Я не могу сказать, является ли это ошибкой, потому что Visual С++, похоже, не определяет, считается ли объявленное целочисленное переполнение. Так что мне интересно, это должно быть определено в Visual С++?

EDIT: я нашел это, когда читал о неприятной ошибке в Visual С++ 2013 Update 2, которая не была в обновлении 1, где следующий цикл генерирует плохой машинный код, если оптимизация включена:

void func (int *b, int n)
{
  for (int i = 0; i < n; i++)
    b[i * (n + 1)] = 1;
}

Ошибка Update 2 приводит к повторной строке, генерирующей код, как если бы она была b[i] = 1;, что явно неверно. Он превратился в rep stosd.

Что было действительно интересно, так это то, что в предыдущей версии было обновлено, обновление 1. Это сгенерированный код, который неправильно обрабатывал случай, когда n в точности равнялся INT_MAX. В частности, если n были INT_MAX, умножение будет действовать так, как если бы n были long long вместо int - другими словами, добавление n + 1 не привело бы к тому, что результат стал INT_MIN как он должен.

Это был код сборки в обновлении 1:

    movsxd  rax, edx          ; RDX = 0x000000007FFFFFFF; RAX = 0x000000007FFFFFFF.
    test    edx, edx
    jle     short locret_76   ; Branch not taken, because EDX is nonnegative.
    lea     rdx, ds:4[rax*4]  ; RDX = RAX * 4 + 4; RDX becomes 0x0000000200000000.
    nop                       ; But it wrong. RDX should now be 0xFFFFFFFE00000000.
loc_68:
    mov     dword ptr [rcx], 1
    add     rcx, rdx
    dec     rax
    jnz     short loc_68
locret_76:
    retn

Проблема в том, что я не знаю, является ли это ошибкой компилятора - в GCC и Clang это не будет ошибкой компилятора, потому что эти компиляторы считают, что подписанное целочисленное переполнение/нижнее течение undefined. Является ли это ошибкой в ​​Visual С++, зависит от того, считает ли Visual С++ подписанное целочисленное переполнение /underflow undefined.

Каждый другой случай, который я видел помимо этого, показал, что Visual С++ рассматривает подписанный переполнение/нижний поток для определения, следовательно, тайну.

Ответ 1

В вашем примере, вероятно, есть поведение undefined для n == INT_MAX, но не только из-за того, что подписанное целочисленное переполнение undefined (что может быть не в компиляторе Microsoft). Скорее всего, вы, вероятно, вызываете undefined арифметику указателей out-of-bounds.

Ответ 2

Нашел интересную новость прошлого 2016 года (VS2015 Update 3):

Они рассказывают о новом оптимизаторе SSA, который хотят внедрить в VS2015:

C++ Team Blog - Представляем новый усовершенствованный оптимизатор кода Visual C++

.........

Исторически сложилось так, Visual C++ не воспользоваться тем, что С и C++ стандарты считают результат перелива подписанные операции не определено. Другие компиляторы очень агрессивны в этом отношении, что побудило решение реализовать некоторые шаблоны, использующие преимущество неопределенного поведения целочисленного переполнения. Мы реализовали те, которые мы считали безопасными, и не создавали ненужных угроз безопасности в сгенерированном коде.

Так что у вас есть это. Я прочитал это как: "мы никогда не программировали никаких дополнительных битов, чтобы использовать этот UB", но начиная с VS2015/Update3 у нас будет несколько.

Я должен отметить, что даже до этого я был бы крайне осторожен, потому что для 64-битного кода и 32-битных переменных, если компилятор/оптимизатор просто помещает 32-битное знаковое int в 64-битный регистр, вы будете иметь неопределенное значение, несмотря ни на что. (Как показано в разделе "Как не кодировать: неопределенное поведение ближе, чем вы думаете" - к сожалению, из сообщения в блоге неясно, использовал ли он VS2015 до или после обновления 3).

Таким образом, я полагаю, что MSVC всегда считал его UB, хотя предыдущая версия оптимизатора не пользовалась особым преимуществом. Кажется, новый оптимизатор SAA работает точно. (Было бы интересно проверить, работает ли переключатель –d2UndefIntOverflow–.)