Различные результаты для инструкции idiv

Взгляните на этот фрагмент кода

int main()
{
    int i = 1U << 31; // assume this yields INT_MIN
    volatile int x;
    x = -1;
    x = i / x; //dividing INT_MIN by -1 is UB
    return 0;
}

Он вызывает неопределенное поведение на типичной платформе, но «поведение» сильно отличается от того, что я ожидаю — оно действует так, как если бы это был бесконечный цикл. Я могу придумать сцену, которую он кусает.

Конечно, undefined есть undefined, но я проверил выходную сборку, она использует простую idiv -- почему она не перехватывает? Для сравнения, деление на ноль вызывает немедленное прерывание.

Использование Windows 7 64 бит и MingW64

Кто-нибудь может мне это объяснить?

ИЗМЕНИТЬ

Я пробовал несколько вариантов, и результаты всегда были одинаковыми.

Вот сборка:

    .file   "a.c"
    .def    __main; .scl    2;  .type   32; .endef
    .section    .text.startup,"x"
    .p2align 4,,15
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    subq    $56, %rsp
    .seh_stackalloc 56
    .seh_endprologue
    call    __main
    movl    $-1, 44(%rsp)
    movl    $-2147483648, %eax
    movl    44(%rsp), %ecx
    cltd
    idivl   %ecx
    movl    %eax, 44(%rsp)
    xorl    %eax, %eax
    addq    $56, %rsp
    ret
    .seh_endproc
    .ident  "GCC: (x86_64-posix-sjlj, built by strawberryperl.com project) 4.8.2"

person ntysdd    schedule 18.08.2014    source источник
comment
Говоря о UB, вызывающем бесконечные циклы   -  person Shafik Yaghmour    schedule 18.08.2014
comment
Не могли бы вы показать сгенерированную сборку, так как это актуально в данном случае? В последний раз, когда я разделил INT_MIN на -1, он попал в ловушку с «исключением с плавающей запятой», таким же, как если бы я разделил на ноль (но это было в Mac OS X).   -  person Pascal Cuoq    schedule 18.08.2014
comment
Я не понимаю. Где здесь УБ?   -  person sharptooth    schedule 18.08.2014
comment
@sharptooth Деление INT_MIN на -1 — это неопределенное поведение. Это вовсе не особый случай: это обычное арифметическое переполнение со знаком. Но на x86 инструкция обычно вызывает аппаратное исключение, а не зацикливается. (Обратите внимание, что в C11 INT_MIN % -1 тоже было преобразовано в UB, несмотря на то, что это не случай арифметического переполнения).   -  person Pascal Cuoq    schedule 18.08.2014
comment
@PascalCuoq: я добавил комментарии в код. Да, когда я компилирую этот код с помощью Visual C++, я наблюдаю целочисленное переполнение.   -  person sharptooth    schedule 18.08.2014
comment
Почему x volatile? Это актуально?   -  person Fiddling Bits    schedule 18.08.2014
comment
Просто чтобы компилятор не оптимизировал деление.   -  person ntysdd    schedule 18.08.2014
comment
Это имеет довольно четко определенное поведение в Windows, инструкция вызывает исключение процессора #DE, оно отражается обратно в процесс с помощью исключения SEH с кодом STATUS_INTEGER_OVERFLOW. Однако именно там, где остановится для GCC, он помещает U обратно в UB.   -  person Hans Passant    schedule 18.08.2014
comment
Проблема качества реализации   -  person ntysdd    schedule 18.08.2014
comment
@ntysdd: К сожалению, некоторые авторы компиляторов интерпретируют проблемы с качеством реализации как предлог для того, чтобы прыгнуть с рельсов, а не как предлог для выбора разумного, возможно, специфичного для платформы поведения. Учитывая, что любые операции, связанные с idiv, будут медленными, относительная стоимость ограничения поведения должна быть небольшой, вне зависимости от того, предписывает ли Стандарт такие ограничения или нет.   -  person supercat    schedule 16.07.2018


Ответы (1)


Бесконечный цикл, который вы наблюдаете, возможно, является ошибкой в ​​MinGW-w64.

MinGW-w64 частично поддерживает SEH, и если вы запустите свой код в отладчике, вы увидите, что в результате неверного idiv вызывается обработчик исключений (функция с именем «_gnu_exception_handler»). (например, запустите свою программу в gdb и установите точку останова на _gnu_exception_handler)

Проще говоря, в случае целочисленного переполнения этот обработчик исключения просто отклоняет исключение и продолжает выполнение в точке, где произошло исключение (idiv). Затем операция idiv выполняется снова, в результате чего то же самое переполнение запускает тот же обработчик ошибок, и ваш ЦП переключается между idiv и обработчиком исключений. (Именно здесь поведение MinGW-w64 можно рассматривать как ошибку.)

Вы можете увидеть это непосредственно в источнике здесь, если вы хотите углубиться.

Значение «EXCEPTION_CONTINUE_EXECUTION», возвращаемое _gnu_exception_handler, когда он имеет дело с EXCEPTION_INT_OVERFLOW (целочисленное переполнение), — это то, что подпитывает такое поведение (когда система видит, что обработчик вернул EXCEPTION_CONTINUE_EXECUTION, она возвращается к инструкции, сгенерировавшей исключение, и пытается выполнить ее снова .)

Если вас интересуют дополнительные сведения, здесь вы найдете хорошие ресурсы для понимания как SEH работает в Windows.

person aboivine    schedule 22.08.2014
comment
Что ж, мне не нужно вдаваться в подробности, чтобы понять всю картину, на самом деле я рассматривал возможность обработчика исключений, но затем отклонил это. Я просто не понимаю, почему реализация работает неправильно (т.е. вообще ничего не делает). - person ntysdd; 30.08.2014