Win32 üzerinde unsigned int'e çift çevrim 2,147,483,648'e düşüyor


85

Aşağıdaki kodu derlemek:

Çıkışlar (MSVC x86):

INT_MAX: 2147483647
UINT_MAX: 4294967295
Double value: 2147483649.000000
Direct cast value: 2147483648
Indirect cast value: 2147483649

Çıkışlar (MSVC x64):

INT_MAX: 2147483647
UINT_MAX: 4294967295
Double value: 2147483649.000000
Direct cast value: 2147483649
Indirect cast value: 2147483649

In Microsoft belgelerinde gelen dönüşümlerde maksimum değere tamsayı imzalanmış hiçbir söz yoktur doubleiçin unsigned int.

Yukarıdaki tüm değerler , bir işlevin dönüşü olduğunda INT_MAXkesiliyor 2147483648.

Programı oluşturmak için Visual Studio 2019 kullanıyorum . Bu gcc'de olmaz .

Yanlış mı yapıyorum? Dönüştürmek için güvenli bir yolu var mı doublehiç unsigned int?


24
Ve hayır, yanlış bir şey yapmıyorsunuz (belki Microsoft'un "C" derleyicisini kullanmaya çalışmanın yanı sıra)
Antti Haapala

5
My machine ™ üzerinde çalışır, VS2017 v15.9.18 ve VS2019 v16.4.1 üzerinde test edilmiştir. Sürümünüz hakkında onlara bilgi vermek için Yardım> Geri Bildirim Gönder> Hata Bildir'i kullanın.
Hans Passant

5
Yeniden üretebiliyorum, OP ile aynı sonuçlara sahibim. VS2019 16.7.3.
anastaciu

2
@EricPostpischil gerçekten, bu bir bit kalıbıINT_MIN
Antti Haapala

Yanıtlar:


70

Derleyici hatası ...

@Anastaciu tarafından sağlanan derlemeden, __ftol2_ssenumarayı imzalı bir uzunluğa dönüştüren doğrudan yayın kodu çağrıları . Rutin adı, ftol2_ssebunun sse özellikli bir makine olduğu içindir - ancak şamandıra, x87 kayan nokta yazmacında bulunur.

; Line 17
    call    _getDouble
    call    __ftol2_sse
    push    eax
    push    OFFSET ??_C@_0BH@GDLBDFEH@Direct?5cast?5value?3?5?$CFu?6@
    call    _printf
    add esp, 8

Öte yandan dolaylı döküm,

; Line 18
    call    _getDouble
    fstp    QWORD PTR _d$[ebp]
; Line 19
    movsd   xmm0, QWORD PTR _d$[ebp]
    call    __dtoui3
    push    eax
    push    OFFSET ??_C@_0BJ@HCKMOBHF@Indirect?5cast?5value?3?5?$CFu?6@
    call    _printf
    add esp, 8

çift ​​değeri yerel değişkene açar ve saklar, sonra onu bir SSE yazmacına yükler ve __dtoui3imzasız int dönüşüm rutinine çift olan çağrı yapar ...

Doğrudan atmanın davranışı C89'a uymuyor; ne de sonraki revizyonlara uymuyor - C89 bile açıkça şunu söylüyor:

İntegral tipin bir değeri işaretsiz tipe dönüştürüldüğünde yapılan kalan işlem, kayan tipin bir değeri işaretsiz tipe dönüştürüldüğünde yapılmasına gerek yoktur. Dolayısıyla, taşınabilir değerlerin aralığı [0, Utype_MAX + 1) 'dir .


Sorunun 2005'ten itibaren bunun bir devamı olabileceğine inanıyorum - __ftol2muhtemelen bu kod için işe yarayacak olan bir dönüştürme işlevi vardı, yani değeri -2147483647 imzalı bir sayıya dönüştürürdü , bu da doğruyu üretebilirdi işaretsiz bir sayı yorumlandığında sonuç.

Maalesef __ftol2_sse, __ftol2- sadece en az önemli değer bitlerini olduğu gibi almak yerine - aralık dışı hatasını, LONG_MIN/ döndürerek sinyal vereceği için bir drop-in ikamesi 0x80000000değildir. tüm beklenenler. Davranışı __ftol2_sseiçin geçerli olacaktır signed long> çift bir değere dönüştürülmesi gibi LONG_MAXetmek signed longtanımsız davranış olurdu.


23

@ AnttiHaapala'nın cevabını takiben , kodu optimizasyonu kullanarak test ettim /Oxve __ftol2_sseartık kullanılmadığı için bunun hatayı kaldıracağını buldum :

Optimizasyonlar getdouble(), hatayı ortadan kaldırarak çalışma zamanında bir dönüştürme ihtiyacını ortadan kaldırarak sabit ifade değerlendirmesini satır içine aldı ve ekledi.

Merak ettiğim için, daha fazla test yaptım, yani çalışma zamanında float-int dönüşümü zorlamak için kodu değiştirdim. Bu durumda sonuç hala doğrudur, derleyici optimizasyonla __dtoui3her iki dönüşümü de kullanır :

Ancak, satır içi yapmanın engellenmesi __declspec(noinline) double getDouble(){...}hatayı geri getirecektir:

__ftol2_sseher 2147483648iki durumda da her iki dönüşümde de çıkış yapıyor , @zwol şüpheleri doğruydu.


Derleme ayrıntıları:

  • Komut satırını kullanarak:
cl /permissive- /GS /analyze- /W3 /Gm- /Ox /sdl /D "WIN32" program.c        
  • Visual Studio'da:

    • Devre dışı bırakılması RTChalinde Project -> Properties -> Code Generationve ayar Temel Runtime Çekler için varsayılan .

    • Optimizasyon etkinleştirilmesi Project -> Properties -> Optimizationve ayarlanması Optimizasyonu için / Ox .

    • Hata ayıklayıcı x86modunda.


5
"Optimizasyonlar etkinleştirildiğinde tamam, tanımlanmamış davranış gerçekten tanımlanmayacak" => kod aslında doğru çalışıyor: F
Antti Haapala

3
@AnttiHaapala, evet, evet, Microsoft'un en iyisi.
anastaciu

1
Uygulanan optimizasyonlar satır içi ve ardından sürekli ifade değerlendirmesiydi. Artık çalışma zamanında float-int dönüşüm yapmıyor. getDoubleÇizgiyi aşmaya zorlarsanız ve / veya onu derleyicinin sabit olduğunu kanıtlayamayacağı bir değer döndürmek için değiştirirseniz hata geri gelir mi merak ediyorum .
zwol

1
@zwol, haklıydın, çizgiyi aşmaya zorlamak ve sürekli değerlendirmeyi engellemek hatayı geri getirecek, ama bu sefer her iki dönüşümde de.
anastaciu

7

MS'ler için kimse bakmadı __ftol2_sse.

Sonuçtan , güvenli bir şekilde yerine x87'den işaretli int/ long(Windows'ta her iki 32 bit tür) ' e dönüştürüldüğü sonucuna varabiliriz uint32_t.

x86 FP -> tamsayı sonucunu aşan tamsayı komutları sadece kaydırmak / kesmekle kalmaz: Hedefte kesin değer gösterilemediğinde Intel'in "belirsiz tamsayı" dediği şeyi üretirler : yüksek bit seti, diğer bitler temiz. yani0x80000000 .

(Veya FP geçersiz istisnası maskelenmemişse, tetiklenir ve hiçbir değer saklanmaz. Ancak varsayılan FP ortamında, tüm FP istisnaları maskelenir. Bu nedenle FP hesaplamaları için hata yerine bir NaN alabilirsiniz.)

Bu, hem x87 komutlarını fistp(mevcut yuvarlama modunu kullanarak) hem de SSE2 komutlarını cvttsd2si eax, xmm0(0'a doğru kesme kullanarak, ekstra tanlamı budur) içerir.

Yani double-> unsigneddönüşümü bir çağrıya derlemek için bir hatadır __ftol2_sse.


Yan not / teğet:

X86-64'te, FP -> uint32_t, cvttsd2si rax, xmm064-bit işaretli bir hedefe dönüştürülerek, tamsayı hedefin alt yarısında (EAX) istediğiniz uint32_t'yi üreterek derlenebilir.

Sonuç 0,2 ^ 32-1 aralığının dışındaysa C ve C ++ UB'dir, bu nedenle büyük pozitif veya negatif değerlerin RAX'in (EAX) düşük yarısını tam sayı belirsiz bit modelinden sıfır bırakması sorun değildir. (Aksine integer-> tamsayı dönüşümleri değerin modülo azaltma edilir değil garantili. ARM vs x86 C standardında tanımlanan imzasız int? Farklı davranış çift bir negatif döküm davranışı mı . İçinde, hiçbir şey net olmak gerekirse , söz tanımsız ve hatta uygulama tanımlı bir davranış. FP-> int64_t'ye sahipseniz, FP-> uint32_t'yi verimli bir şekilde uygulamak için kullanabileceğinizi belirtmek isterim.fistp 64 bitlik modda yalnızca 64 bit tam sayıları doğrudan işleyebilen SSE2 talimatlarının aksine, 32 bit ve 16 bit modunda bile 64 bitlik tamsayı hedefi yazabilir.


1
Bu koda bakma konusunda cazip olurdum ama neyse ki MSVC'ye sahip değilim ...: D
Antti Haapala

@AnttiHaapala: Evet, ben de istemiyorum
Peter Cordes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.