(ए + बी + सी) ≠ (ए + सी + बी) और कंपाइलर रीक्रोडिंग


108

दो 32-बिट पूर्णांक जोड़ने से एक पूर्णांक अतिप्रवाह हो सकता है:

uint64_t u64_z = u32_x + u32_y;

यदि 32-बिट पूर्णांकों में से एक को पहले डाला जाता है या 64-बिट पूर्णांक में जोड़ा जाता है, तो इस अतिप्रवाह से बचा जा सकता है।

uint64_t u64_z = u32_x + u64_a + u32_y;

हालांकि, यदि कंपाइलर अतिरिक्त को फिर से तय करने का फैसला करता है:

uint64_t u64_z = u32_x + u32_y + u64_a;

पूर्णांक ओवरफ़्लो अभी भी हो सकता है।

क्या कंपाइलरों को इस तरह की पुनरावृत्ति करने की अनुमति है या क्या हम परिणाम असंगति को नोटिस करने और अभिव्यक्ति के क्रम को बनाए रखने के लिए उन पर भरोसा कर सकते हैं?


15
आप वास्तव में पूर्णांक अतिप्रवाह नहीं दिखाते हैं क्योंकि आपको जोड़े गए uint32_tमूल्य दिखाई देते हैं - जो अतिप्रवाह नहीं करते हैं, वे लपेटते हैं। ये अलग व्यवहार नहीं हैं।
मार्टिन बोनर

5
C ++ मानकों के अनुभाग 1.9 को देखें, यह सीधे आपके प्रश्न का उत्तर देता है (यहां तक ​​कि एक उदाहरण भी है जो लगभग आपके जैसा ही है)।
होल्ट

3
@ ताल: जैसा कि अन्य पहले ही कह चुके हैं: पूर्णांकों का कोई अतिप्रवाह नहीं है। अनसाइनड को रैप करने के लिए परिभाषित किया गया है, हस्ताक्षरित के लिए यह अपरिभाषित व्यवहार है, इसलिए कोई भी कार्यान्वयन, नाक डायमन्स सहित करेगा।
इस साइट के लिए बहुत ही ईमानदार

5
@ ताल: बकवास! जैसा कि मैंने पहले ही लिखा है: मानक बहुत स्पष्ट है और रैपिंग की आवश्यकता है, न कि संतृप्त करना (जो कि हस्ताक्षर के साथ संभव होगा, जैसा कि मानक के यूबी-के रूप में है।
इस साइट के लिए बहुत ही ईमानदार

15
@rustyx: चाहे आप फोन यह लपेटकर या बह निकला, बिंदु बनी हुई है कि ((uint32_t)-1 + (uint32_t)1) + (uint64_t)0में परिणाम 0है, जबकि (uint32_t)-1 + ((uint32_t)1 + (uint64_t)0)में परिणाम 0x100000000है, और इन दो मानों बराबर नहीं हैं। इसलिए यह महत्वपूर्ण है कि संकलक उस परिवर्तन को लागू कर सकता है या नहीं। लेकिन हाँ, मानक केवल हस्ताक्षर किए गए पूर्णांकों के लिए "अतिप्रवाह" शब्द का उपयोग करता है, अहस्ताक्षरित नहीं।
स्टीव जेसोप

जवाबों:


84

यदि ऑप्टिमाइज़र इस तरह के पुन: व्यवस्थित करता है, तो यह अभी भी सी विनिर्देश के लिए बाध्य है, इसलिए ऐसा पुन: क्रमबद्ध हो जाएगा:

uint64_t u64_z = (uint64_t)u32_x + (uint64_t)u32_y + u64_a;

दलील:

हम शुरुआत करते हैं

uint64_t u64_z = u32_x + u64_a + u32_y;

जोड़ बाएं से दाएं किया जाता है।

पूर्णांक पदोन्नति नियम बताता है कि मूल अभिव्यक्ति में पहले जोड़ में u32_xपदोन्नत किया जाए uint64_t। दूसरे जोड़ में u32_yभी पदोन्नत किया जाएगा uint64_t

इसलिए, सी विनिर्देश के अनुरूप होने के लिए, किसी भी आशावादी को बढ़ावा देना चाहिए u32_xऔर u32_y64 बिट अहस्ताक्षरित मूल्यों के लिए। यह कलाकारों को जोड़ने के बराबर है। (वास्तविक अनुकूलन C स्तर पर नहीं किया गया है, लेकिन मैं C संकेतन का उपयोग करता हूं क्योंकि यह एक संकेतन है जिसे हम समझते हैं।)


क्या यह वाम-सहयोगी नहीं है, इसलिए (u32_x + u32_t) + u64_a?
बेकार

12
@ यूलेस: क्लास ने 64 बिट के लिए सब कुछ डाला। अब इस आदेश से कोई फर्क नहीं पड़ता। संकलक को सहानुभूति का पालन करने की आवश्यकता नहीं है, इसे बस उसी परिणाम का उत्पादन करना है जैसे कि उसने किया था।
gnasher729

2
ऐसा लगता है कि ओपी कोड का मूल्यांकन उस तरह किया जाएगा, जो सच नहीं है।
बेकार

@Klas - यह समझाने की परवाह करें कि यह मामला क्यों है और आप अपने कोड नमूने पर वास्तव में कैसे पहुंचे?
रस्टेक्स

1
@rustyx इसे एक स्पष्टीकरण की आवश्यकता थी। मुझे एक जोड़ने के लिए धक्का देने के लिए धन्यवाद।
क्लस लिंडबेक

28

एक संकलक केवल तहत फिर से आदेश लेने की अनुमति है , जैसे कि नियम। यही है, यदि रिडरिंग हमेशा निर्दिष्ट आदेश के समान परिणाम देगा, तो इसकी अनुमति है। अन्यथा (आपके उदाहरण के अनुसार), नहीं।

उदाहरण के लिए, निम्नलिखित अभिव्यक्ति दी गई है

i32big1 - i32big2 + i32small

जिसका सावधानी से निर्माण उन दो मूल्यों को घटाने के लिए किया गया है, जो बड़े लेकिन समान रूप से जाने जाते हैं, और उसके बाद ही अन्य छोटे मूल्य (इस प्रकार किसी भी अतिप्रवाह से बचना) को जोड़ते हैं, संकलक में फिर से चुन सकते हैं:

(i32small - i32big2) + i32big1

और इस तथ्य पर भरोसा करते हैं कि लक्ष्य मंच समस्याओं को रोकने के लिए रैप-राउंड के साथ दो-पूरक अंकगणित का उपयोग कर रहा है। (यदि ऐसा कंपाइलर रजिस्टरों के लिए दबाया जाता है, और ऐसा हो सकता है, तो ऐसा पुनरावृत्ति समझदार हो सकता है i32small)


ओपी का उदाहरण अहस्ताक्षरित प्रकारों का उपयोग करता है। i32big1 - i32big2 + i32smallतात्पर्य हस्ताक्षरित प्रकार। अतिरिक्त चिंताएँ खेलने में आती हैं।
chux -

@chux बिल्कुल। मैं जिस बिंदु को बनाने की कोशिश कर रहा था वह यह है कि यद्यपि मैं लिख नहीं सकता था (i32small-i32big2) + i32big1, (क्योंकि यह यूबी का कारण हो सकता है), संकलक इसे प्रभावी ढंग से पुनर्व्यवस्थित कर सकता है क्योंकि संकलक आश्वस्त हो सकता है कि व्यवहार सही होगा।
मार्टिन बोनर

3
@chux: यूबी जैसी अतिरिक्त चिंताएं खेलने में नहीं आती हैं, क्योंकि हम एक नियम के तहत कंपाइलर के बारे में बात कर रहे हैं। एक विशेष संकलक अपने स्वयं के अतिप्रवाह व्यवहार को जानने का लाभ उठा सकता है।
MSalters

16

C, C ++ और Objective-C में "as if" रूल है। कंपाइलर जो चाहे उसे कर सकता है जब तक कोई अनुरूप प्रोग्राम अंतर नहीं बता सकता।

इन भाषाओं में, a + b + c को (+ b) + c के समान ही परिभाषित किया गया है। यदि आप इस और उदाहरण के लिए a (b + c) के बीच का अंतर बता सकते हैं तो संकलक आदेश को बदल नहीं सकता है। यदि आप अंतर नहीं बता सकते हैं, तो संकलक ऑर्डर बदलने के लिए स्वतंत्र है, लेकिन यह ठीक है, क्योंकि आप अंतर नहीं बता सकते।

आपके उदाहरण में, b = 64 बिट, a और c 32 बिट के साथ, संकलक को मूल्यांकन करने की अनुमति दी जाएगी (b + a) + c या सम (b + c) + a, क्योंकि आप अंतर नहीं बता सकते, लेकिन नहीं (a + c) + b क्योंकि आप अंतर बता सकते हैं।

दूसरे शब्दों में, संकलक को ऐसा कुछ भी करने की अनुमति नहीं है जो आपके कोड को उसके व्यवहार से अलग करे। यह आपको लगता है कि यह उत्पादन होता है, या आपको लगता है कि यह उत्पादन करना चाहिए कि कोड का उत्पादन करने की आवश्यकता नहीं है, लेकिन कोड होगा कि वास्तव में आप परिणाम यह होना चाहिए दे।


लेकिन एक बड़ी चेतावनी के साथ; संकलक कोई अपरिभाषित व्यवहार मानने के लिए स्वतंत्र है (इस मामले में अतिप्रवाह)। यह एक अतिप्रवाह जाँच कैसे if (a + 1 < a)अनुकूलित की जा सकती है, इसके समान है।
१२:५० बजे सीएसज

7
@csiz ... हस्ताक्षरित चर पर। निरस्त चर में अच्छी तरह से परिभाषित अतिप्रवाह शब्दार्थ (रैप-अराउंड) है।
गैविन एस। यान्सी

7

मानकों से उद्धरण :

[नोट: संचालकों को सामान्य गणितीय नियमों के अनुसार ही फिर से इकट्ठा किया जा सकता है, जहां ऑपरेटर वास्तव में साहचर्य या कम्यूटेटिव होते हैं। उदाहरण के लिए, निम्न अंश int, b में;

/∗ ... ∗/
a = a + 32760 + b + 5;

अभिव्यक्ति कथन बिल्कुल वैसा ही व्यवहार करता है

a = (((a + 32760) + b) + 5);

इन ऑपरेटरों की सहानुभूति और पूर्वता के कारण। इस प्रकार, योग का परिणाम (a + 32760) बी में जोड़ा जाता है, और उसके बाद परिणाम को 5 में जोड़ा जाता है, जिसके परिणामस्वरूप ए को सौंपा गया मूल्य होता है। एक मशीन पर जिसमें ओवरफ्लो एक अपवाद उत्पन्न करता है और जिसमें एक इंट द्वारा दर्शाए जाने वाले मानों की सीमा [-32768, + 32767] है, कार्यान्वयन इस अभिव्यक्ति को फिर से लिख नहीं सकता है

a = ((a + b) + 32765);

चूँकि यदि a और b के मान क्रमशः -32754 और -15 थे, तो a + b एक अपवाद उत्पन्न करेगा जबकि मूल अभिव्यक्ति नहीं होगी; न ही अभिव्यक्ति को फिर से लिखा जा सकता है

a = ((a + 32765) + b);

या

a = (a + (b + 32765));

चूँकि a और b के मान क्रमशः, 4 और -8 या -17 और 12. हो सकते हैं, हालांकि एक मशीन पर जिसमें ओवरफ्लो एक अपवाद उत्पन्न नहीं करता है और जिसमें ओवरफ्लो के परिणाम प्रतिवर्ती होते हैं, उपरोक्त अभिव्यक्ति कथन कर सकते हैं उपरोक्त तरीकों में से किसी भी तरीके से कार्यान्वयन द्वारा फिर से लिखा जा सकता है क्योंकि एक ही परिणाम होगा। - अंतिम नोट]


4

क्या कंपाइलरों को इस तरह की पुनरावृत्ति करने की अनुमति है या क्या हम परिणाम असंगति को नोटिस करने और अभिव्यक्ति के क्रम को बनाए रखने के लिए उन पर भरोसा कर सकते हैं?

कंपाइलर केवल तब ही पुन: व्यवस्थित हो सकता है जब वह समान परिणाम देता है - यहाँ, जैसा कि आपने देखा, यह नहीं है।


एक फ़ंक्शन टेम्प्लेट लिखना संभव है, यदि आप एक चाहते हैं, जो std::common_typeजोड़ने से पहले सभी तर्कों को बढ़ावा देता है - यह सुरक्षित होगा, और न ही तर्क क्रम या मैनुअल कास्टिंग पर भरोसा करना होगा, लेकिन यह बहुत क्लूनी है।


मुझे पता है कि स्पष्ट कास्टिंग का उपयोग किया जाना चाहिए, लेकिन मैं कंपाइलर के व्यवहार को जानना चाहता हूं जब ऐसी कास्टिंग गलती से छोड़ दी गई थी।
ताल

1
जैसा कि मैंने कहा, स्पष्ट कास्टिंग के बिना: बाएं जोड़ पहले किया जाता है, अभिन्न संवर्धन के बिना, और इसलिए लपेटने के अधीन है। परिणाम है कि इसके अलावा, संभवतः लिपटे, है तो करने के लिए प्रोत्साहित uint64_tराइट सबसे अधिक मूल्य के अलावा के लिए।
बेकार

जैसे-यदि नियम के बारे में आपका स्पष्टीकरण पूरी तरह से गलत है। उदाहरण के लिए सी भाषा निर्दिष्ट करती है कि एक अमूर्त मशीन पर क्या संचालन होना चाहिए। "जैसा-अगर" नियम इसे पूरी तरह से करने की अनुमति देता है जो भी चाहे जब तक कोई भी अंतर नहीं बता सकता है।
gnasher729

इसका मतलब है कि संकलक जो कुछ भी करना चाहता है वह तब तक कर सकता है जब तक कि परिणाम वही हो जो निर्धारित किए गए वाम-समरूपता और अंकगणितीय रूपांतरण नियमों द्वारा निर्धारित किया गया हो।
बेकार

1

यह थोड़ी चौड़ाई पर निर्भर करता है unsigned/int

नीचे 2 समान नहीं हैं (जब unsigned <= 32बिट्स)। u32_x + u32_y0 हो जाता है।

u64_a = 0; u32_x = 1; u32_y = 0xFFFFFFFF;
uint64_t u64_z = u32_x + u64_a + u32_y;
uint64_t u64_z = u32_x + u32_y + u64_a;  // u32_x + u32_y carry does not add to sum.

वे समान हैं (जब unsigned >= 34बिट्स)। पूर्णांक पदोन्नति u32_x + u32_y64-बिट गणित में होने के अलावा हुई । आदेश अप्रासंगिक है।

यह यूबी (जब unsigned == 33बिट्स) होता है। पूर्णांक पदोन्नति 33-बिट गणित और हस्ताक्षरित अतिप्रवाह पर होने के अलावा यूबी है।

क्या कंपाइलरों को इस तरह की पुनरावृत्ति करने की अनुमति है ...?

(32 बिट गणित): पुन: आदेश हाँ, लेकिन एक ही परिणाम उत्पन्न होना चाहिए, इसलिए ऐसा न हो कि ओपी को फिर से आदेश दिया जाए। नीचे समान हैं

// Same
u32_x + u64_a + u32_y;
u64_a + u32_x + u32_y;
u32_x + (uint64_t) u32_y + u64_a;
...

// Same as each other below, but not the same as the 3 above.
uint64_t u64_z = u32_x + u32_y + u64_a;
uint64_t u64_z = u64_a + (u32_x + u32_y);

... क्या हम परिणाम असंगति को नोटिस करने और अभिव्यक्ति के क्रम को बनाए रखने के लिए उन पर भरोसा कर सकते हैं?

ट्रस्ट हां, लेकिन ओपी का कोडिंग लक्ष्य स्पष्ट नहीं है। u32_x + u32_yयोगदान देना चाहिए ? यदि ओपी चाहता है कि योगदान, कोड होना चाहिए

uint64_t u64_z = u64_a + u32_x + u32_y;
uint64_t u64_z = u32_x + u64_a + u32_y;
uint64_t u64_z = u32_x + (u32_y + u64_a);

लेकिन नहीं

uint64_t u64_z = u32_x + u32_y + u64_a;
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.