Visual Studio 调试与发布版本:比较 int 和 float 不匹配

Visual Studio debug vs. release build: comparing int and float missmatch

看看这个例子:

#include <stdio.h>

int main() {

  int i= 16777217;
  float f = 16777216.0;
  float g = i;

  if( i == f )
    printf("eq\n");
  else
    printf("neq\n");

  if( g == f )
    printf("eq\n");
  else
    printf("neq\n");

  return 0;
}

在发布模式下使用 Visual Studio 2010 C++ (VS)、gcc 或 g++ (4.9.2),输出为

eq
eq

这对我来说是合理的:在第一次比较期间,i 被隐式转换为浮点数,其中尾数中的有效位被截断。因此,if 都具有相同的位模式,与相等性相比。在第二个 if 中应用相同的转换,但在定义和初始化 g.

时已经执行

但是,在调试模式下使用VS,结果是

neq
eq

看起来,第一个 if 中比较期间的隐式转换(作为 C 和 C++ 中常用算术转换的一部分)并未应用。这是真的?是否有一种 VS 机制可以防止在比较浮点数和整数时出现此类误报(更精确地转换为 int/float)?根据 MSDN VS C++ 遵循标准。

我用this function检查了位表示。对于所有编译器,它产生

i = 00000001000000000000000000000001
f = 01001011100000000000000000000000
g = 01001011100000000000000000000000

float.h 在 VS 状态下 #define FLT_MANT_DIG 24 因此所描述的截断问题也应该成立。

我在同一台机器 (Intel i5-3570K) 上编译了所有内容,但在虚拟框中编译了 VS。在另一台机器上用 VS 编译也打印 neq/eq

编辑:附加汇编代码

differences_debug.asm

; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01 

    TITLE   c:\Users\user\documents\visual studio 2010\Projects\differences\differences\differences.cpp
    .686P
    .XMM
    include listing.inc
    .model  flat

INCLUDELIB MSVCRTD
INCLUDELIB OLDNAMES

PUBLIC  ??_C@_04LMPLCMBC@neq?6?$AA@         ; `string'
PUBLIC  ??_C@_03HNJPMNDP@eq?6?$AA@          ; `string'
PUBLIC  __real@4b800000
PUBLIC  _wmain
EXTRN   __imp__printf:PROC
EXTRN   __fltused:DWORD
EXTRN   __RTC_CheckEsp:PROC
EXTRN   __RTC_Shutdown:PROC
EXTRN   __RTC_InitBase:PROC
;   COMDAT ??_C@_04LMPLCMBC@neq?6?$AA@
; File c:\users\user\documents\visual studio 2010\projects\differences\differences\differences.cpp
CONST   SEGMENT
??_C@_04LMPLCMBC@neq?6?$AA@ DB 'neq', 0aH, 00H      ; `string'
CONST   ENDS
;   COMDAT ??_C@_03HNJPMNDP@eq?6?$AA@
CONST   SEGMENT
??_C@_03HNJPMNDP@eq?6?$AA@ DB 'eq', 0aH, 00H        ; `string'
CONST   ENDS
;   COMDAT __real@4b800000
CONST   SEGMENT
__real@4b800000 DD 04b800000r           ; 1.67772e+007
CONST   ENDS
;   COMDAT rtc$TMZ
rtc$TMZ SEGMENT
__RTC_Shutdown.rtc$TMZ DD FLAT:__RTC_Shutdown
rtc$TMZ ENDS
;   COMDAT rtc$IMZ
rtc$IMZ SEGMENT
__RTC_InitBase.rtc$IMZ DD FLAT:__RTC_InitBase
; Function compile flags: /Odtp /RTCsu /ZI
rtc$IMZ ENDS
;   COMDAT _wmain
_TEXT   SEGMENT
_g$ = -32                       ; size = 4
_f$ = -20                       ; size = 4
_i$ = -8                        ; size = 4
_argc$ = 8                      ; size = 4
_argv$ = 12                     ; size = 4
_wmain  PROC                        ; COMDAT
; Line 7
    push    ebp
    mov ebp, esp
    sub esp, 228                ; 000000e4H
    push    ebx
    push    esi
    push    edi
    lea edi, DWORD PTR [ebp-228]
    mov ecx, 57                 ; 00000039H
    mov eax, -858993460             ; ccccccccH
    rep stosd
; Line 8
    mov DWORD PTR _i$[ebp], 16777217        ; 01000001H
; Line 9
    fld DWORD PTR __real@4b800000
    fstp    DWORD PTR _f$[ebp]
; Line 10
    fild    DWORD PTR _i$[ebp]
    fstp    DWORD PTR _g$[ebp]
; Line 13
    fild    DWORD PTR _i$[ebp]
    fld DWORD PTR _f$[ebp]
    fucompp
    fnstsw  ax
    test    ah, 68                  ; 00000044H
    jp  SHORT $LN4@wmain
; Line 14
    mov esi, esp
    push    OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
    call    DWORD PTR __imp__printf
    add esp, 4
    cmp esi, esp
    call    __RTC_CheckEsp
; Line 15
    jmp SHORT $LN3@wmain
$LN4@wmain:
; Line 16
    mov esi, esp
    push    OFFSET ??_C@_04LMPLCMBC@neq?6?$AA@
    call    DWORD PTR __imp__printf
    add esp, 4
    cmp esi, esp
    call    __RTC_CheckEsp
$LN3@wmain:
; Line 19
    fld DWORD PTR _g$[ebp]
    fld DWORD PTR _f$[ebp]
    fucompp
    fnstsw  ax
    test    ah, 68                  ; 00000044H
    jp  SHORT $LN2@wmain
; Line 20
    mov esi, esp
    push    OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
    call    DWORD PTR __imp__printf
    add esp, 4
    cmp esi, esp
    call    __RTC_CheckEsp
; Line 21
    jmp SHORT $LN1@wmain
$LN2@wmain:
; Line 22
    mov esi, esp
    push    OFFSET ??_C@_04LMPLCMBC@neq?6?$AA@
    call    DWORD PTR __imp__printf
    add esp, 4
    cmp esi, esp
    call    __RTC_CheckEsp
$LN1@wmain:
; Line 24
    xor eax, eax
; Line 26
    pop edi
    pop esi
    pop ebx
    add esp, 228                ; 000000e4H
    cmp ebp, esp
    call    __RTC_CheckEsp
    mov esp, ebp
    pop ebp
    ret 0
_wmain  ENDP
_TEXT   ENDS
END

differences_release.asm

; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01 

    TITLE   c:\Users\user\documents\visual studio 2010\Projects\differences\differences\differences.cpp
    .686P
    .XMM
    include listing.inc
    .model  flat

INCLUDELIB OLDNAMES

PUBLIC  ??_C@_03HNJPMNDP@eq?6?$AA@          ; `string'
PUBLIC  ??_C@_04LMPLCMBC@neq?6?$AA@         ; `string'
EXTRN   @__security_check_cookie@4:PROC
EXTRN   __imp__printf:PROC
;   COMDAT ??_C@_04LMPLCMBC@neq?6?$AA@
CONST   SEGMENT
??_C@_04LMPLCMBC@neq?6?$AA@ DB 'neq', 0aH, 00H      ; `string'
CONST   ENDS
;   COMDAT ??_C@_03HNJPMNDP@eq?6?$AA@
CONST   SEGMENT
??_C@_03HNJPMNDP@eq?6?$AA@ DB 'eq', 0aH, 00H        ; `string'
CONST   ENDS
PUBLIC  _wmain
EXTRN   __fltused:DWORD
; Function compile flags: /Ogtp
;   COMDAT _wmain
_TEXT   SEGMENT
_argc$ = 8                      ; size = 4
_argv$ = 12                     ; size = 4
_wmain  PROC                        ; COMDAT
; File c:\users\user\documents\visual studio 2010\projects\differences\differences\differences.cpp
; Line 7
    push    esi
; Line 14
    mov esi, DWORD PTR __imp__printf
    push    OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
    call    esi
; Line 20
    push    OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
    call    esi
    add esp, 8
; Line 24
    xor eax, eax
    pop esi
; Line 26
    ret 0
_wmain  ENDP
_TEXT   ENDS
END

如果我们分解发行版 ASM:

; Line 14
    push    OFFSET ??_C@_03HNJPMNDP@eq?6?$AA@
    call    DWORD PTR __imp__printf
    add esp, 4
; Line 18
    xor eax, eax
; Line 20
    ret 0

它只是打印 eq 并退出,这表明浮动比较刚刚被完全优化掉。对于调试程序集,我们使用 fldfild 指令查看它:

; Line 9
    fld DWORD PTR __real@4b800000
    fstp    DWORD PTR _f$[ebp]
; Line 10
    fild    DWORD PTR _i$[ebp]
    fstp    DWORD PTR _g$[ebp]
; Line 13
    fild    DWORD PTR _i$[ebp]

这些 IA32 指令是 Visual Studio 2010 中使用的默认架构。我怀疑您使用 /arch:SSE2 而不是您会得到不同的结果。

基本上证实了我刚才所说的。

两组输出均符合 C 行为。

在执行 FP 数学时,C 允许 FP 计算以比操作数格式更高的精度级别进行。

如果代码执行 i == f 作为 double 数学,结果是 "neq"
如果代码执行 i == f 作为 float 数学运算,则结果为 "eq"

int i= 16777217;
float f = 16777216.0;
if( i == f )
  printf("eq\n");
else
  printf("neq\n");

Except for assignment and cast (which remove all extra range and precision), the values yielded by operators with floating operands and values subject to the usual arithmetic conversions and of floating constants are evaluated to a format whose range and precision may be greater than required by the type C11 §5.2.4.2.2 9

现代 C 编译器提供 FLT_EVAL_METHOD 指示使用的内容。


Is there a VS-mechanism which prevents such false positives in comparing floats and ints (conversion to int/float with more precision)?

要强制进行 float 比较,代码可以使用

if((float) i == f )

要强制进行 double 比较,代码可以使用

if((double) i == f )