Win32 पर अहस्ताक्षरित इंट के लिए डबल कास्ट 2,147,483,648 पर छंटनी कर रहा है


86

निम्नलिखित कोड का संकलन:

double getDouble()
{
    double value = 2147483649.0;
    return value;
}

int main()
{
     printf("INT_MAX: %u\n", INT_MAX);
     printf("UINT_MAX: %u\n", UINT_MAX);

     printf("Double value: %f\n", getDouble());
     printf("Direct cast value: %u\n", (unsigned int) getDouble());
     double d = getDouble();
     printf("Indirect cast value: %u\n", (unsigned int) d);

     return 0;
}

आउटपुट (MSVC x86):

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

आउटपुट (MSVC x64):

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

में माइक्रोसॉफ्ट प्रलेखन से रूपांतरण में अधिकतम मूल्य पूर्णांक पर हस्ताक्षर किए के लिए कोई जिक्र नहीं है doubleकरने के लिए unsigned int

उपरोक्त सभी मानों को किसी फ़ंक्शन की वापसी के समय INT_MAXछोटा किया जा रहा 2147483648है।

मैं प्रोग्राम बनाने के लिए विजुअल स्टूडियो 2019 का उपयोग कर रहा हूं । यह gcc पर नहीं होता है ।

क्या मैं कुछ गलत कर रहा हूँ? वहाँ एक सुरक्षित तरीका है कन्वर्ट doubleकरने के लिए unsigned int?


24
और नहीं, आप कुछ भी गलत नहीं कर रहे हैं (शायद माइक्रोसॉफ्ट के "सी" संकलक का उपयोग करने की कोशिश कर रहे हैं)
एंटि हापला

5
मेरे मशीन ™ पर काम करता है, VS2017 v15.9.18 और VS2019 v16.4.1 पर परीक्षण किया गया। मदद> फ़ीडबैक भेजें> अपने संस्करण के बारे में उन्हें बताने के लिए बग रिपोर्ट करें।
हंस पैजेंट

5
मैं पुन: पेश करने में सक्षम हूं, मेरे पास ओपी के समान परिणाम हैं। VS2019 16.7.3।
आष्टासीयू

2
@ EricPostpischil वास्तव में, यह बिट-पैटर्न हैINT_MIN
एंटिटी हवाला

जवाबों:


71

एक कंपाइलर बग ...

@Anastaciu द्वारा प्रदान की गई असेंबली से, डायरेक्ट कास्ट कोड कॉल __ftol2_sse, जो संख्या को एक हस्ताक्षरित लंबे में परिवर्तित करता है । रूटीन नाम ftol2_sseइसलिए है क्योंकि यह एक sse- इनेबल्ड मशीन है - लेकिन फ्लोट x87 फ्लोटिंग पॉइंट रजिस्टर में है।

; 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

दूसरी ओर अप्रत्यक्ष कलाकार करता है

; 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

जो लोकल वैरिएबल के लिए डबल वैल्यू को पॉप और स्टोर करता है, फिर उसे SSE रजिस्टर में लोड करता है और कॉल __dtoui3करता है जो कि अहस्ताक्षरित कस्टम रूपांतरण रूटीन के लिए एक डबल ...

प्रत्यक्ष कलाकारों का व्यवहार C89 के अनुरूप नहीं है; न ही यह किसी भी बाद के संशोधन के अनुरूप है - यहां तक ​​कि C89 स्पष्ट रूप से कहता है कि:

शेष प्रकार के अभिन्न प्रकार में परिवर्तित होने पर किया जाने वाला शेष संचालन तब किया जाता है जब अस्थायी प्रकार का मान अहस्ताक्षरित प्रकार में परिवर्तित किया जाता है। इस प्रकार पोर्टेबल मानों की सीमा [0, Utype_MAX + 1) है


मेरा मानना ​​है कि यह समस्या 2005 से निरंतरता हो सकती है - एक रूपांतरण फ़ंक्शन हुआ करता था जिसे __ftol2शायद इस कोड के लिए काम किया जाता था, अर्थात यह मान को एक हस्ताक्षरित संख्या -2147483647 में बदल देता था, जो सही उत्पादन करता था परिणाम जब एक अहस्ताक्षरित संख्या की व्याख्या की।

दुर्भाग्य __ftol2_sseसे, एक ड्रॉप-इन प्रतिस्थापन के लिए नहीं है __ftol2, क्योंकि यह होगा - के रूप में सिर्फ कम से कम महत्वपूर्ण मूल्य बिट्स के रूप में है - लौटने से LONG_MIN/ बाहर त्रुटि सीमा का संकेत है 0x80000000, जो, के रूप में यहाँ लंबे समय के रूप में समझाया नहीं है सब उम्मीद थी। के व्यवहार __ftol2_sseके लिए मान्य होगा signed long, एक डबल एक मूल्य के रूपांतरण के रूप में> LONG_MAXको signed longअपरिभाषित व्यवहार होगा।


23

बाद @ AnttiHaapala का जवाब , मैं अनुकूलन का उपयोग कर कोड का परीक्षण किया /Oxऔर पाया कि इस बग के रूप में निकाल देंगे __ftol2_sseनहीं रह गया है प्रयोग किया जाता है:

//; 17   :     printf("Direct cast value: %u\n", (unsigned int)getDouble());

    push    -2147483647             //; 80000001H
    push    OFFSET $SG10116
    call    _printf

//; 18   :     double d = getDouble();
//; 19   :     printf("Indirect cast value: %u\n", (unsigned int)d);

    push    -2147483647             //; 80000001H
    push    OFFSET $SG10117
    call    _printf
    add esp, 28                 //; 0000001cH

अनुकूलन getdouble()ने निरंतर अभिव्यक्ति मूल्यांकन को जोड़ा और इस प्रकार बग को दूर करते हुए रनटाइम में रूपांतरण की आवश्यकता को दूर किया।

जिज्ञासा से बाहर, मैंने कुछ और परीक्षण किए, अर्थात् रनटाइम पर फ्लोट-टू-इंट रूपांतरण के लिए कोड को बदलना। इस स्थिति में परिणाम अभी भी सही है, कंपाइलर, अनुकूलन के साथ, __dtoui3दोनों रूपांतरणों में उपयोग करता है:

//; 19   :     printf("Direct cast value: %u\n", (unsigned int)getDouble(d));

    movsd   xmm0, QWORD PTR _d$[esp+24]
    add esp, 12                 //; 0000000cH
    call    __dtoui3
    push    eax
    push    OFFSET $SG9261
    call    _printf

//; 20   :     double db = getDouble(d);
//; 21   :     printf("Indirect cast value: %u\n", (unsigned int)db);

    movsd   xmm0, QWORD PTR _d$[esp+20]
    add esp, 8
    call    __dtoui3
    push    eax
    push    OFFSET $SG9262
    call    _printf

हालाँकि, इनलाइनिंग को रोकना, __declspec(noinline) double getDouble(){...}बग को वापस लाएगा:

//; 17   :     printf("Direct cast value: %u\n", (unsigned int)getDouble(d));

    movsd   xmm0, QWORD PTR _d$[esp+76]
    add esp, 4
    movsd   QWORD PTR [esp], xmm0
    call    _getDouble
    call    __ftol2_sse
    push    eax
    push    OFFSET $SG9261
    call    _printf

//; 18   :     double db = getDouble(d);

    movsd   xmm0, QWORD PTR _d$[esp+80]
    add esp, 8
    movsd   QWORD PTR [esp], xmm0
    call    _getDouble

//; 19   :     printf("Indirect cast value: %u\n", (unsigned int)db);

    call    __ftol2_sse
    push    eax
    push    OFFSET $SG9262
    call    _printf

__ftol2_sse2147483648दोनों स्थितियों में आउटपुट बनाने वाले दोनों रूपांतरणों में कहा जाता है, @zwol संदेह सही थे।


संकलन विवरण:

  • कमांड लाइन का उपयोग करना:
cl /permissive- /GS /analyze- /W3 /Gm- /Ox /sdl /D "WIN32" program.c        
  • विज़ुअल स्टूडियो में:

    • अक्षम करना RTCमें Project -> Properties -> Code Generationऔर स्थापना के मूल क्रम चेकों को डिफ़ॉल्ट

    • ऑप्टिमाइज़ेशन को इन / ऑक्सिमाइज़ेशन में ऑप्टिमाइज़ करना Project -> Properties -> Optimizationऔर सेट करना ।

    • x86मोड में डिबगर के साथ ।


5
मजेदार है कि वे कैसे "अनुकूलन के साथ ठीक हैं, अपरिभाषित हैं, अपरिभाषित व्यवहार वास्तव में अपरिभाषित होंगे" => कोड वास्तव में सही ढंग से काम करता है: एफ
एंटटी हवाला

3
@AnttiHaapala, हाँ, हाँ, Microsoft अपने सबसे अच्छे रूप में।
अन्तासियू

1
लागू किए गए ऑप्टिमाइज़ेशन इनलाइनिंग और फिर निरंतर अभिव्यक्ति मूल्यांकन थे। यह अब रनटाइम में फ्लोट-टू-इंट रूपांतरण नहीं कर रहा है। मुझे आश्चर्य है कि अगर बग वापस आता है यदि आप getDoubleलाइन से बाहर निकलते हैं और / या एक मान वापस करने के लिए इसे बदलते हैं तो संकलक साबित नहीं हो सकता है।
zwol

1
@zwol, आप सही थे, आउट-ऑफ-लाइन मजबूर करने और निरंतर मूल्यांकन को रोकने से बग वापस आ जाएगा, लेकिन इस बार दोनों धर्मांतरण में।
अन्तासियू

7

एमएस के लिए किसी ने भी इस पर गौर नहीं किया __ftol2_sse

परिणाम से, हम यह अनुमान लगा सकते हैं कि यह शायद x87 से हस्ताक्षरित int/ long(विंडोज़ पर दोनों 32-बिट प्रकार) में परिवर्तित हो गया, सुरक्षित रूप से इसके बजाय uint32_t

x86 FP -> पूर्णांक निर्देश जो पूर्णांक परिणाम को ओवरफ्लो करते हैं / लपेटते नहीं हैं: वे उस चीज का उत्पादन करते हैं जो गंतव्य में प्रतिनिधित्व योग्य नहीं होने पर इंटेल "पूर्णांक अनिश्चितकालीन" कहता है: उच्च बिट सेट, अन्य बिट्स स्पष्ट। यानी0x80000000

(या यदि FP अमान्य अपवाद को नकाबपोश नहीं किया गया है, तो यह आग लग जाती है और कोई मूल्य संग्रहीत नहीं किया जाता है। लेकिन डिफ़ॉल्ट FP वातावरण में, सभी FP अपवादों को नकाबपोश किया जाता है। यही कारण है कि FP गणना के लिए आप एक गलती के बजाय NaN प्राप्त कर सकते हैं।)

इसमें दोनों x87 निर्देश जैसे fistp(वर्तमान राउंडिंग मोड का उपयोग करके) और SSE2 निर्देश जैसे cvttsd2si eax, xmm0(0 की ओर ट्रंकेशन का उपयोग करना, यही अतिरिक्त tसाधन है) शामिल हैं।

इसलिए यह कॉल करने के लिए double-> unsignedरूपांतरण संकलन करने के लिए एक बग है __ftol2_sse


साइड-नोट / स्पर्शरेखा:

X86-64 पर, FP -> uint32_t को cvttsd2si rax, xmm064-बिट हस्ताक्षरित गंतव्य में परिवर्तित किया जा सकता है, आप पूर्णांक गंतव्य के निम्न आधे (EAX) में चाहते हैं।

यदि परिणाम 0..2 ^ 32-1 सीमा के बाहर है तो यह C और C ++ UB है, इसलिए यह ठीक है कि विशाल सकारात्मक या नकारात्मक मान पूर्णांक अनिश्चित-बिट बिट पैटर्न से RAX (EAX) के निचले आधे भाग को छोड़ देंगे। (पूर्णांक के विपरीत-> पूर्णांक रूपांतरण, मूल्य के मोडुलो की कमी की गारंटी नहीं हैक्या सी मानक में परिभाषित अहस्ताक्षरित दोहरे नकारात्मक कास्टिंग का व्यवहार एआरएम बनाम x86 पर अलग व्यवहार है । स्पष्ट होने के लिए, प्रश्न में कुछ भी नहीं है। अपरिभाषित या यहां तक ​​कि कार्यान्वयन-परिभाषित व्यवहार है। मैं केवल यह इंगित कर रहा हूं कि यदि आपके पास FP-> int64_t है, तो आप इसका उपयोग कुशलतापूर्वक FP-> uint32_t को लागू करने के लिए कर सकते हैं। इसमें x87 भी शामिल है।fistp जो SSE2 निर्देशों के विपरीत 64-बिट पूर्णांक गंतव्य को 32-बिट और 16-बिट मोड में भी लिख सकता है, जो केवल 64-बिट पूर्णांकों को सीधे 64-बिट मोड में संभाल सकता है।


1
मुझे उस कोड को देखने के लिए लुभाया जाएगा, लेकिन सौभाग्य से मेरे पास MSVC नहीं है ...: D
एंटी हापाला

@AnttiHaapala: हाँ, न तो मैं
पीटर कॉर्ड्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.