Странный результат сравнения целочисленного неравенства 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
не должно фактически ничего менять.
Я пытаюсь зарезервировать "это ошибка компилятора" в качестве последнего средства, но это похоже на ошибку компилятора.