13

Take a look at this piece of code

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;
}

It invokes undefined behavior on typical platform, but the "behavior" is quite different from what I expect -- it acts as if it were an infinite loop. I can think of a scene that it bites.

Of course undefined is undefined, but I have checked the output assembly, it is using a plain idiv -- why does it not trap? For the sake of comparison, a divide by zero causes an immediate abort.

Using Windows 7 64 bit and MingW64

Can anyone explain this to me?

EDIT

I tried a few options, and the results were always the same.

Here is the assembly:

    .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"
ntysdd
  • 1,206
  • 2
  • 9
  • 19
  • Speaking of [UB causing infinite loops](http://stackoverflow.com/q/24296571/1708801) – Shafik Yaghmour Aug 18 '14 at 12:33
  • 5
    Could you show the generated assembly, since that is relevant in this case? The last time I divided `INT_MIN` by `-1`, it trapped with a “floating-point exception”, same as if I had divided by zero (but that was under Mac OS X). – Pascal Cuoq Aug 18 '14 at 12:33
  • 1
    I don't get it. Where is UB here? – sharptooth Aug 18 '14 at 12:54
  • 2
    @sharptooth Dividing `INT_MIN` by `-1` is Undefined Behavior. It is no special case at all: this is an ordinary signed arithmetic overflow. But on the x86, the instruction normally raises a hardware exception instead of wrapping around. (Note that in C11, `INT_MIN % -1` was made UB too, despite not being a case of arithmetic overflow). – Pascal Cuoq Aug 18 '14 at 13:02
  • 1
    @PascalCuoq: I added comments into the code. Yes, when I compile this code with Visual C++ I observe an integer overflow. – sharptooth Aug 18 '14 at 13:13
  • Why is `x` `volatile`? Is that relevant? – Fiddling Bits Aug 18 '14 at 13:22
  • 2
    Just to keep the compiler optimizing the division away. – ntysdd Aug 18 '14 at 13:26
  • 4
    This has pretty well defined behavior on Windows, the instruction raises the #DE processor exception, it is reflected back into the process with an SEH exception with code STATUS_INTEGER_OVERFLOW. That's however where the [buck stops for GCC](https://gcc.gnu.org/wiki/WindowsGCCImprovements), it puts the U back in UB. – Hans Passant Aug 18 '14 at 13:51
  • 2
    A quality of implementation problem – ntysdd Aug 18 '14 at 14:20
  • @ntysdd: Unfortunately, some compiler writers seen to interpret "quality of implementation" issues as an excuse to jump the rails, rather than as an excuse to select among reasonable possibly-platform-specific behaviors. Given that any operations involving `idiv` are going to be slow, the relative cost of constraining behaviors should be slight, whether the Standard mandates such constraints or not. – supercat Jul 16 '18 at 16:53

1 Answers1

5

The infinite loop you observe is arguably a bug in MinGW-w64.

MinGW-w64 partially supports SEH, and if you run your code in a debugger, you will see that an exception handler (function named "_gnu_exception_handler") is called as a result of the invalid idiv. (for instance run your program in gdb and set a breakpoint on _gnu_exception_handler)

Said simply, what this exception handler does in case of an integer overflow is simply dismissing the exception and continuing execution at the point where the exception occurred (idiv). The idiv operation is then executed again, resulting in the same overflow trigger the same error handler, and your CPU goes back and forth between idiv and the exception handler. (This is where the behavior of MinGW-w64 can be seen as a bug.)

You can see it directly in the source here if you want to go deep.

The value "EXCEPTION_CONTINUE_EXECUTION" returned by _gnu_exception_handler when it deals with an EXCEPTION_INT_OVERFLOW (integer overflow) is what fuels this behavior (when the system sees that the handler returned EXCEPTION_CONTINUE_EXECUTION, it jumps back to the instruction that generated the exception and tries to execute it again.)

If you are interested in more details, here is a good resources to understand how SEH works on Windows.

aboivine
  • 106
  • 1
  • 5
  • Well, I don't really need to look at the details to understand the whole picture, in fact I have considered the possibility of an exception handler, but then dismissed that. I just don't understand why the implementation doesn't do the right thing (ie. doing nothing at all). – ntysdd Aug 30 '14 at 18:59