Странный результат сравнения целочисленного неравенства C

#include <limits.h>
#include <stdio.h>
int main() {
    long ival = 0;
    printf("ival: %li, min: %i, max: %i, too big: %i, too small: %i\n",
           ival, INT_MIN, INT_MAX, ival > INT_MAX, ival < INT_MIN);
}

Это дает результат:

ival: 0, min: -2147483648, max: 2147483647, too big: 0, too small: 1

Как это возможно?

(Я действительно попал в эту проблему/ошибку в CPython 2.7.3 в getargs.c: convertsimple. Если вы посмотрите код, в case 'i', существует проверка ival < INT_MIN, которая всегда была правдой для меня. См. также источник тестового примера с дальнейшими ссылками.)


Ну, я протестировал несколько разных компиляторов. GCC/Clang, скомпилированные для x86, возвращают ожидаемый (слишком маленький: 0). Неожиданный вывод из Clang в инструментальной цепочке Xcode при компиляции для armv7.


Если вы хотите воспроизвести:

Это точная команда компиляции: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -arch armv7 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.1.sdk test-int.c

Это Xcode 4.3.2.

Я скопировал полученный a.out на мой iPhone и выполнил его.

Если кому-то интересен код ассемблера, сгенерированный этим:

    .section    __TEXT,__text,regular,pure_instructions
    .section    __TEXT,__textcoal_nt,coalesced,pure_instructions
    .section    __TEXT,__const_coal,coalesced
    .section    __TEXT,__picsymbolstub4,symbol_stubs,none,16
    .section    __TEXT,__StaticInit,regular,pure_instructions
    .syntax unified
    .section    __TEXT,__text,regular,pure_instructions
    .globl  _main
    .align  2
    .code   16
    .thumb_func _main
_main:
    push    {r7, lr}
    mov r7, sp
    sub sp, #20
    movw    r0, #65535
    movt    r0, #32767
    movs    r1, #0
    movt    r1, #0
    str r1, [sp, #16]
    str r1, [sp, #12]
    ldr r1, [sp, #12]
    ldr r2, [sp, #12]
    cmp r2, r0
    movw    r0, #0
    it  gt
    movgt   r0, #1
    and r0, r0, #1
    ldr r2, [sp, #12]
    cmn.w   r2, #-2147483648
    movw    r2, #0
    it  lt
    movlt   r2, #1
    and r2, r2, #1
    mov r3, sp
    str r2, [r3, #4]
    str r0, [r3]
    mov.w   r2, #-2147483648
    mvn r3, #-2147483648
    movw    r0, :lower16:(L_.str-(LPC0_0+4))
    movt    r0, :upper16:(L_.str-(LPC0_0+4))
LPC0_0:
    add r0, pc
    blx _printf
    ldr r1, [sp, #16]
    str r0, [sp, #8]
    mov r0, r1
    add sp, #20
    pop {r7, pc}

    .section    __TEXT,__cstring,cstring_literals
L_.str:
    .asciz   "ival: %li, min: %i, max: %i, too big: %i, too small: %i\n"


.subsections_via_symbols

Ответы

Ответ 1

Это ошибка. В стандарте C нет места для too small, чтобы быть чем-то другим, кроме 0. Здесь как это работает:

  • Так как INT_MIN является int, он преобразуется в long во время "обычных арифметических преобразований". Это происходит из-за того, что long имеет более высокий ранг, чем int (и оба являются подписанными типами). Никаких рекламных акций не происходит, поскольку все операнды имеют ранг не менее int. Не вызывается undefined или поведение, указанное в реализации.

  • Во время преобразования значение INT_MIN сохраняется. Поскольку он преобразуется из int в long, и гарантируется, что long имеет по крайней мере диапазон int, значение INT_MIN должно сохраняться во время преобразования. Не вызывается undefined или поведение, указанное в реализации. Разрешены модульные преобразования, только для неподписанных типов.

  • Результат сравнения должен быть 0.

Нет места для маневра для расширения знака или других подобных вещей. Кроме того, поскольку вызов printf верен, там нет проблем.

Если вы можете воспроизвести его в другой системе или отправить ее кому-то другому, кто может ее воспроизвести, вы должны сообщить об ошибке непосредственно своему поставщику инструментальной цепочки.

Попытка воспроизвести ошибку: Я не смог воспроизвести поведение в любой из следующих комбинаций, все с оптимизацией включены и выключены:

  • GCC 4.0, PPC + PPC64
  • GCC 4.2, PPC + PPC64
  • GCC 4.3, x64
  • GCC 4.4, x64
  • Clang 3.0, x64

Ответ 2

Что это печатает?

#include <limits.h>

printf("%016ld\n", LONG_MAX);

long l_int_min = (long)INT_MIN;
printf("%016lx\n", l_int_min);

Мне интересно, будет ли INT_MIN принуждаться к long без расширения знака. Это сделало бы 0 меньше результирующего значения.

EDIT: хорошо, результат первого printf() был 0000002147483647, а это означает, что long на этой платформе 32-бит, как и int. Поэтому приведение int в long не должно фактически ничего менять.

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