मुझे 8-बिट पूर्णांक से आकार में 8 बिट्स से बड़ा मान कैसे मिला?


118

मैंने इस छोटे से मणि के पीछे छिपे एक बेहद गंदे बग को ट्रैक किया। मुझे पता है कि C ++ कल्पना के अनुसार, हस्ताक्षरित ओवरफ्लो अपरिभाषित व्यवहार हैं, लेकिन केवल जब अतिप्रवाह तब होता है जब मूल्य को बिट-चौड़ाई तक बढ़ाया जाता है sizeof(int)। जैसा कि मैंने इसे समझा है, charजब तक कभी भी अपरिभाषित व्यवहार नहीं करना चाहिए , वृद्धि नहीं होनी चाहिए sizeof(char) < sizeof(int)। लेकिन यह नहीं समझाता है कि cएक असंभव मूल्य कैसे मिल रहा है । 8-बिट पूर्णांक के रूप में, cइसकी बिट-चौड़ाई से अधिक मान कैसे हो सकता है ?

कोड

// Compiled with gcc-4.7.2
#include <cstdio>
#include <stdint.h>
#include <climits>

int main()
{
   int8_t c = 0;
   printf("SCHAR_MIN: %i\n", SCHAR_MIN);
   printf("SCHAR_MAX: %i\n", SCHAR_MAX);

   for (int32_t i = 0; i <= 300; i++)
      printf("c: %i\n", c--);

   printf("c: %i\n", c);

   return 0;
}

उत्पादन

SCHAR_MIN: -128
SCHAR_MAX: 127
c: 0
c: -1
c: -2
c: -3
...
c: -127
c: -128  // <= The next value should still be an 8-bit value.
c: -129  // <= What? That's more than 8 bits!
c: -130  // <= Uh...
c: -131
...
c: -297
c: -298  // <= Getting ridiculous now.
c: -299
c: -300
c: -45   // <= ..........

इसे आइडोन पर देखें।


61
"मुझे पता है कि C ++ के अनुसार, हस्ताक्षरित ओवरफ्लो अपरिभाषित हैं।" -- सही। सटीक होने के लिए, न केवल मूल्य अपरिभाषित है, व्यवहार है। शारीरिक रूप से असंभव परिणाम प्राप्त करने के लिए प्रकट होना एक वैध परिणाम है।

@hvd मुझे यकीन है कि किसी के पास एक स्पष्टीकरण है कि सामान्य C ++ कार्यान्वयन इस व्यवहार का कारण कैसे बनते हैं। शायद यह संरेखण के साथ करना है या printf()रूपांतरण कैसे करता है?
rliu

अन्य लोगों ने मुख्य मुद्दे को संबोधित किया है। मेरी टिप्पणी अधिक सामान्य है और नैदानिक ​​दृष्टिकोण से संबंधित है। मुझे विश्वास है कि आपने ऐसा क्यों पाया, यह एक ऐसी अटूट धारणा है, जो असंभव थी। जाहिर है, यह असंभव नहीं है, इसलिए उसे स्वीकार करें और फिर से देखें
टिम एक्स

@ टिमएक्स - मैंने व्यवहार का अवलोकन किया और स्पष्ट रूप से निष्कर्ष निकाला कि यह उस अर्थ में असंभव नहीं था। 9-बिट मान रखने वाले 8-बिट पूर्णांक को संदर्भित शब्द का मेरा उपयोग, जो परिभाषा द्वारा एक असंभव है। तथ्य यह है कि यह हुआ कि यह 8-बिट मान के रूप में नहीं माना जा रहा है। जैसा कि दूसरों ने संबोधित किया है, यह एक कंपाइलर बग के कारण है। यहां केवल प्रतीत होने वाली असंभवता 8-बिट स्थान में 9-बिट मान है, और इस स्पष्ट असंभवता को अंतरिक्ष द्वारा वास्तव में रिपोर्ट किए गए "बड़ा" द्वारा समझाया गया है।
अप्रकाशित

मैंने अभी-अभी अपनी माइनस पर इसका परीक्षण किया है, और इसका परिणाम यह है कि यह क्या होना चाहिए। c: -120 c: -121 c: -122 c: -123 c: -124 c: -125 c: -126 c: -127 c: -1287: c -128 c: 127 c: 126 c: 125 c: 124 c: 123 c: 122 c: 121 c: 120 c: 119 c: 118 c: 117 और मेरा पर्यावरण है: Ubuntu-12.10 gcc-4.7.2
VELVETDETH

जवाबों:


111

यह एक कंपाइलर बग है।

हालांकि अपरिभाषित व्यवहार के लिए असंभव परिणाम प्राप्त करना एक वैध परिणाम है, वास्तव में आपके कोड में कोई अपरिभाषित व्यवहार नहीं है। क्या हो रहा है कि संकलक सोचता है कि व्यवहार अपरिभाषित है, और तदनुसार अनुकूलन करता है।

के cरूप में परिभाषित किया गया है int8_t, और int8_tकरने के लिए बढ़ावा देता है int, तो अंकगणित में c--घटाव प्रदर्शन करने और परिणाम वापस करने के लिए परिवर्तित करने के लिए माना जाता है । में घटाव अतिप्रवाह नहीं, और बाहर-सीमा की परिवर्तित अभिन्न मान के लिए एक और अभिन्न प्रकार मान्य है नहीं करता है। यदि गंतव्य प्रकार पर हस्ताक्षर किए गए हैं, तो परिणाम कार्यान्वयन-परिभाषित है, लेकिन यह गंतव्य प्रकार के लिए एक मान्य मूल्य होना चाहिए। (और यदि गंतव्य प्रकार अहस्ताक्षरित है, तो परिणाम अच्छी तरह से परिभाषित है, लेकिन यह यहां लागू नहीं होता है।)c - 1intint8_tint


मैं इसे "बग" के रूप में वर्णित नहीं करूंगा। चूंकि हस्ताक्षरित अतिप्रवाह अपरिभाषित व्यवहार का कारण बनता है, इसलिए संकलक पूरी तरह से यह मानने का हकदार नहीं है कि ऐसा नहीं होगा, और cएक व्यापक प्रकार में मध्यवर्ती मूल्यों को बनाए रखने के लिए लूप का अनुकूलन करें । शायद, यही यहाँ हो रहा है।
सितंबर को माइक सेमुर

4
@MikeSeymour: यहाँ केवल ओवरफ़्लो (अंतर्निहित) रूपांतरण है। हस्ताक्षरित रूपांतरण पर अतिप्रवाह में अपरिभाषित व्यवहार नहीं होता है; यह केवल एक कार्यान्वयन-परिभाषित परिणाम देता है (या कार्यान्वयन-परिभाषित संकेत उठाता है, लेकिन यहां ऐसा नहीं लगता है)। अंकगणितीय संचालन और रूपांतरणों के बीच परिभाषितता में अंतर विषम है, लेकिन यही भाषा मानक इसे परिभाषित करता है।
कीथ थॉम्पसन

2
@KeithThompson यह C और C ++ के बीच अंतर करने वाली चीज़ है: C एक कार्यान्वयन-परिभाषित सिग्नल की अनुमति देता है, C ++ नहीं करता है। C ++ केवल यह कहता है कि "यदि गंतव्य प्रकार पर हस्ताक्षर किए गए हैं, तो गंतव्य स्थान (और बिट-फ़ील्ड चौड़ाई) में प्रतिनिधित्व किया जा सकता है, तो मूल्य अपरिवर्तित है; अन्यथा, मूल्य कार्यान्वयन-परिभाषित है।"

जैसा कि होता है, मैं जी ++ 4.8.0 पर अजीब व्यवहार को पुन: पेश नहीं कर सकता।
डैनियल लैंडौ

2
@DanielLandau उस बग में टिप्पणी 38 देखें: "4.8.0 के लिए निश्चित।" :)

15

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

इस मामले में, यह एक अनुरूप बग प्रतीत होता है। अभिव्यक्ति c--को cएक तरह से हेरफेर करना चाहिए c = c - 1। यहां, cदाईं ओर के मान को टाइप करने के लिए प्रचारित किया जाता है int, और फिर घटाव होता है। चूंकि cकी सीमा में है int8_t, यह घटाव अतिप्रवाह नहीं होगा, लेकिन यह एक मूल्य पैदा कर सकता है जो की सीमा से बाहर है int8_t। जब यह मान असाइन किया जाता है, तो रूपांतरण वापस टाइप होता है, int8_tताकि परिणाम वापस फिट हो जाए c। आउट-ऑफ-द-रेंज मामले में, रूपांतरण का कार्यान्वयन-परिभाषित मूल्य होता है। लेकिन की सीमा से बाहर एक मूल्य int8_tएक मान्य कार्यान्वयन-परिभाषित मूल्य नहीं है। एक कार्यान्वयन "परिभाषित" नहीं कर सकता है कि एक 8 बिट प्रकार अचानक 9 या अधिक बिट रखता है। मूल्य के लिए कार्यान्वयन-परिभाषित होने का मतलब है कि int8_tउत्पादन की सीमा में कुछ है, और कार्यक्रम जारी है। सी मानक जिससे संतृप्ति अंकगणित (डीएसपी पर आम) या रैप-अराउंड (मुख्यधारा आर्किटेक्चर) जैसे व्यवहारों के लिए अनुमति देता है।

कंपाइलर एक व्यापक अंतर्निहित मशीन प्रकार का उपयोग कर रहा है जब छोटे पूर्णांक प्रकारों के मानों को जोड़ तोड़ int8_tया char। जब अंकगणित किया जाता है, तो छोटे पूर्णांक प्रकार की सीमा से बाहर होने वाले परिणामों को इस व्यापक प्रकार में मज़बूती से पकड़ा जा सकता है। बाहरी रूप से दिखाई देने वाले व्यवहार को संरक्षित करने के लिए कि चर 8 बिट प्रकार है, व्यापक परिणाम को 8% सीमा में काट दिया जाना है। स्पष्ट कोड यह करने के लिए आवश्यक है कि चूंकि मशीन भंडारण स्थान (रजिस्टर) 8 बिट्स से अधिक व्यापक हैं और बड़े मूल्यों से खुश हैं। यहाँ, संकलक ने मान को सामान्य करने के लिए उपेक्षित किया और बस इसे उसी printfरूप में पारित कर दिया । रूपांतरण निर्दिष्ट करने वाले %iको printfयह पता नहीं है कि तर्क मूल रूप से int8_tगणना से आया है ; यह सिर्फ एक के साथ काम कर रहा हैint बहस।


यह एक स्पष्ट व्याख्या है।
डेविड हीली

कंपाइलर ऑप्टिमाइज़र के साथ अच्छा कोड तैयार करता है। इसलिए, "नियम" और "परिभाषा" का उपयोग करते हुए स्पष्टीकरण लागू नहीं होते हैं। यह अनुकूलक में एक बग है।

14

मैं इसे एक टिप्पणी में फिट नहीं कर सकता, इसलिए मैं इसे उत्तर के रूप में पोस्ट कर रहा हूं।

किसी बहुत ही अजीब कारण के लिए, --ऑपरेटर अपराधी होता है।

मैंने Ideone पर पोस्ट किए गए कोड का परीक्षण किया और उन्हें प्रतिस्थापित c--किया c = c - 1और मान सीमा [-128 ... 127] के भीतर बने रहे:

c: -123
c: -124
c: -125
c: -126
c: -127
c: -128 // about to overflow
c: 127  // woop
c: 126
c: 125
c: 124
c: 123
c: 122

अजीब आँख? मुझे इस बारे में ज्यादा जानकारी नहीं है कि कंपाइलर भावों की तरह क्या करता है i++या है i--। यह संभावना है कि वापसी मूल्य को बढ़ावा देने intऔर इसे पारित करने के लिए। यह एकमात्र तार्किक निष्कर्ष है जिसके साथ मैं आ सकता हूं क्योंकि आप वास्तव में ऐसे मूल्य प्राप्त कर रहे हैं जो 8-बिट्स में फिट नहीं हो सकते।


4
अभिन्न प्रचार के कारण, c = c - 1साधन c = (int8_t) ((int)c - 1। आउट-ऑफ-द-रेंज intको int8_tपरिभाषित करने के लिए परिभाषित व्यवहार है लेकिन एक कार्यान्वयन-परिभाषित परिणाम है। वास्तव में, c--उन्हीं रूपांतरणों को भी करना चाहिए?

12

मुझे लगता है कि अंतर्निहित हार्डवेयर अभी भी उस int8_t को पकड़ने के लिए 32-बिट रजिस्टर का उपयोग कर रहा है। चूंकि विनिर्देश अतिप्रवाह के लिए एक व्यवहार नहीं करता है, इसलिए कार्यान्वयन अतिप्रवाह के लिए जांच नहीं करता है और बड़े मूल्यों को भी संग्रहीत करने की अनुमति देता है।


यदि आप स्थानीय चर को चिह्नित करते हैं, तो आप volatileइसके लिए मेमोरी का उपयोग करने के लिए मजबूर कर रहे हैं और परिणामस्वरूप सीमा के भीतर अपेक्षित मान प्राप्त करते हैं।


1
ओह वाह। मैं भूल गया कि संकलित विधानसभा स्थानीय चर को रजिस्टरों में संग्रहित करेगी यदि ऐसा हो सकता है। यह प्रारूप मानों के printfबारे में परवाह नहीं करने के साथ सबसे अधिक संभावित उत्तर की तरह लगता है sizeof
rliu

3
@roliu रन g ++ -O2 -S code.cpp, और आप असेंबली देखेंगे। इसके अलावा, printf () एक चर तर्क फ़ंक्शन है, इसलिए तर्क जिनकी रैंक एक इंट से कम है उन्हें एक इंट को बढ़ावा दिया जाएगा।
ओपन स्कूल

@ मैं ऐसा करना चाहूंगा। मैं अपनी मशीन पर आर्चलिनक्स प्राप्त करने के लिए UEFI बूट लोडर (विशेष रूप से rEFInd) स्थापित करने में सक्षम नहीं हुआ हूं, इसलिए मैंने वास्तव में लंबे समय में GNU टूल के साथ कोड नहीं किया है। मुझे मिल जाएगा ... आखिरकार। अभी के लिए यह सिर्फ वी # में सी # है और सी को याद करने की कोशिश कर रहा है / कुछ सी ++ सीखने के लिए :)
rliu

@rollu इसे वर्चुअल मशीन में चलाएं, जैसे VirtualBox
nos

@nos विषय को पटरी से नहीं उतारना चाहते, लेकिन हाँ, मैं कर सकता था। मैं भी बस एक BIOS बूटलोडर के साथ लिनक्स स्थापित कर सकता है। मैं सिर्फ जिद्दी हूं और अगर मैं इसे यूईएफआई बूटलोडर के साथ काम नहीं करवा पा रहा हूं तो मैं शायद इसे काम नहीं कर पाऊंगा: पी।
रिल्लू

11

कोड कोड से समस्या का पता चलता है:

:loop
mov esi, ebx
xor eax, eax
mov edi, OFFSET FLAT:.LC2   ;"c: %i\n"
sub ebx, 1
call    printf
cmp ebx, -301
jne loop

mov esi, -45
mov edi, OFFSET FLAT:.LC2   ;"c: %i\n"
xor eax, eax
call    printf

ईबीएक्स को एफएफ पद में वृद्धि के साथ होना चाहिए, या ईबीएक्स के शेष के साथ केवल बीएल का उपयोग किया जाना चाहिए। जिज्ञासु कि यह डिक के बजाय उप का उपयोग करता है। -45 फ्लैट-आउट रहस्यमय है। यह 300 और 255 = 44. -45 = ~ 44 का बिटवाइस उलटा है। कहीं कनेक्शन है।

यह c = c - 1 का उपयोग करके बहुत अधिक कार्य से गुजरता है:

mov eax, ebx
mov edi, OFFSET FLAT:.LC2   ;"c: %i\n"
add ebx, 1
not eax
movsx   ebp, al                 ;uses only the lower 8 bits
xor eax, eax
mov esi, ebp

यह तब RAX के केवल निचले हिस्से का उपयोग करता है, इसलिए यह -128 थ्रू 127 तक सीमित है। कंपाइलर विकल्प "-g -O2"।

अनुकूलन के साथ, यह सही कोड का उत्पादन करता है:

movzx   eax, BYTE PTR [rbp-1]
sub eax, 1
mov BYTE PTR [rbp-1], al
movsx   edx, BYTE PTR [rbp-1]
mov eax, OFFSET FLAT:.LC2   ;"c: %i\n"
mov esi, edx

तो यह अनुकूलक में एक बग है।


4

के %hhdबजाय का उपयोग करें %i! अपनी समस्या का समाधान करना चाहिए।

आप जो देखते हैं, वह एक 32 बिट नंबर प्रिंट करने के लिए संकलक के साथ मिलकर संकलक अनुकूलन का परिणाम है और फिर स्टैक पर एक (माना जाता है कि 8 बिट) संख्या को धक्का देना है, जो वास्तव में सूचक आकार है, क्योंकि यह xx के काम में पुश ओपकोड है।


1
मैं अपने सिस्टम का उपयोग करके मूल व्यवहार को पुन: पेश करने में सक्षम हूं g++ -O3। कुछ भी बदलने के %iलिए %hhdनहीं बदल रहा है।
कीथ थॉम्पसन

3

मुझे लगता है कि यह कोड के अनुकूलन द्वारा कर रहा है:

for (int32_t i = 0; i <= 300; i++)
      printf("c: %i\n", c--);

संकलक int32_t iचर का उपयोग करता है iऔर दोनों के लिए c। ऑप्टिमाइज़ेशन बंद करें या डायरेक्ट कास्ट करें printf("c: %i\n", (int8_t)c--);


फिर अनुकूलन बंद करें। या ऐसा कुछ करें:(int8_t)(c & 0x0000ffff)--
Vsevolod

1

cहै खुद के रूप में परिभाषित int8_tहै, लेकिन जब ऑपरेटिंग ++या --अधिक int8_tयह परोक्ष करने के लिए पहले बदल जाती है intऔर आपरेशन के परिणाम के बजाय ग के आंतरिक मूल्य printf जो होने वाला के साथ मुद्रित किया जाता है int

पूरे लूप के बाद का वास्तविक मूल्य देखें c, विशेष रूप से अंतिम गिरावट के बाद

-301 + 256 = -45 (since it revolved entire 8 bit range once)

इसका सही मूल्य जो व्यवहार से मिलता जुलता है -128 + 1 = 127

cintआकार की मेमोरी का उपयोग करना शुरू कर देता है, लेकिन int8_tजब वह केवल प्रिंट का उपयोग करता है 8 bits32 bitsजब उपयोग किया जाता है तो सभी का उपयोग करता हैint

[संकलक बग]


0

मुझे लगता है कि ऐसा इसलिए हुआ क्योंकि आपका लूप तब तक चलेगा जब तक कि इंट 300 हो जाएगा और सी -300 हो जाएगा। और अंतिम मूल्य है क्योंकि

printf("c: %i\n", c);

'c' एक 8 बिट वैल्यू है, इसलिए कभी-कभी किसी नंबर को -300 जितना बड़ा रखना असंभव है।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.