बिटवाइज ऑपरेशन का परिणाम अप्रत्याशित परिवर्तनशील आकार में होता है


24

प्रसंग

हम सी कोड को पोर्ट कर रहे हैं जो मूल रूप से PIC माइक्रोकंट्रोलर के लिए 8-बिट सी कंपाइलर का उपयोग करके संकलित किया गया था। एक सामान्य मुहावरे का इस्तेमाल किया गया था, जो अहस्ताक्षरित वैश्विक चर (उदाहरण के लिए, त्रुटि काउंटर) को वापस शून्य पर लुढ़कने से रोकने के लिए इस्तेमाल किया गया है:

if(~counter) counter++;

यहाँ बिटवाइज़ ऑपरेटर सभी बिट्स को बदल देता है और यदि केवल counterअधिकतम मान से कम है तो कथन सही है। महत्वपूर्ण रूप से, यह चर आकार की परवाह किए बिना काम करता है।

मुसीबत

अब हम GCC का उपयोग करके 32-बिट ARM प्रोसेसर को लक्षित कर रहे हैं। हमने देखा है कि एक ही कोड अलग-अलग परिणाम देता है। अब तक हम बता सकते हैं, ऐसा लगता है कि बिटवाइज़ सप्लीमेंट ऑपरेशन एक ऐसा मूल्य देता है जो एक अलग आकार है जितना हम उम्मीद करेंगे। इसे पुन: प्रस्तुत करने के लिए, हम जीसीसी में संकलित करते हैं:

uint8_t i = 0;
int sz;

sz = sizeof(i);
printf("Size of variable: %d\n", sz); // Size of variable: 1

sz = sizeof(~i);
printf("Size of result: %d\n", sz); // Size of result: 4

आउटपुट की पहली पंक्ति में, हमें वह मिलता है जिसकी हम अपेक्षा करते हैं: i1 बाइट। हालांकि, बिटवाइज़ का पूरक iवास्तव में चार बाइट्स है जो एक समस्या का कारण बनता है क्योंकि इसके साथ तुलना अब अपेक्षित परिणाम नहीं देगी। उदाहरण के लिए, यदि कर रहा है (जहां iएक ठीक से शुरू किया गया है uint8_t):

if(~i) i++;

हम i0xFF से 0x00 तक "रैप अराउंड" देखेंगे । यह व्यवहार GCC के साथ तुलना में अलग है जब यह पिछले कंपाइलर और 8-बिट PIC माइक्रोकंट्रोलर में काम करने के लिए इस्तेमाल किया गया था।

हम जानते हैं कि हम इस तरह से कास्टिंग करके इसे हल कर सकते हैं:

if((uint8_t)~i) i++;

या द्वारा

if(i < 0xFF) i++;

हालाँकि इन दोनों वर्कअराउंड में, चर का आकार ज्ञात होना चाहिए और सॉफ़्टवेयर डेवलपर के लिए त्रुटि-प्रवण है। इस प्रकार की ऊपरी सीमा की जाँच पूरे कोडबेस में होती है। चर (जैसे।, uint16_tऔर unsigned charआदि) के कई आकार हैं और इन्हें अन्यथा काम करने वाले कोडबेस में बदलना ऐसी चीज नहीं है जिसे हम आगे देखना चाहते हैं।

सवाल

क्या समस्या के बारे में हमारी समझ सही है, और क्या इसको हल करने के लिए विकल्प उपलब्ध हैं जिन्हें प्रत्येक मामले में दोबारा जाने की आवश्यकता नहीं है जहाँ हमने इस मुहावरे का उपयोग किया है? क्या हमारी धारणा सही है, कि बिटवाइज़ सप्लीमेंट जैसे ऑपरेशन को एक परिणाम देना चाहिए जो कि ऑपरेंड के समान आकार का हो? ऐसा लगता है कि प्रोसेसर आर्किटेक्चर के आधार पर यह टूट जाएगा। मुझे लगता है कि मैं पागल गोलियां ले रहा हूं और सी को इससे थोड़ा अधिक पोर्टेबल होना चाहिए। फिर, इस बारे में हमारी समझ गलत हो सकती है।

सतह पर यह एक बहुत बड़ा मुद्दा नहीं लग सकता है, लेकिन पहले से काम कर रहे मुहावरे का इस्तेमाल सैकड़ों स्थानों पर किया जाता है और हम महंगे बदलावों के साथ आगे बढ़ने से पहले इसे समझने के लिए उत्सुक हैं।


नोट: यहां एक समान प्रतीत होता है, लेकिन सटीक डुप्लिकेट प्रश्न यहां नहीं है: चार्ट पर बिटवाइज़ ऑपरेशन 32 बिट परिणाम देता है

मुझे वहां चर्चा किए गए मुद्दे के वास्तविक क्रैक्स को नहीं देखा, अर्थात्, एक बिटवाइज़ पूरक का परिणाम आकार ऑपरेटर में पारित होने की तुलना में अलग है।


14
"क्या हमारी धारणा सही है, कि बिटवाइज़ सप्लीमेंट जैसे ऑपरेशन को एक परिणाम देना चाहिए जो कि ऑपरेंड के समान आकार का है?" नहीं, यह सही नहीं है, पूर्णांक पदोन्नति लागू होती है।
थॉमस जगर

2
हालांकि निश्चित रूप से प्रासंगिक, मैं आश्वस्त नहीं हूं कि वे इस विशेष प्रश्न के डुप्लिकेट हैं, क्योंकि वे समस्या का समाधान प्रदान नहीं करते हैं।
कोड़ी ग्रे

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

1
क्या मैं केवल यह सोच रहा हूं कि वास्तव में महत्वहीन काउंटरों के अलावा क्या तर्क है, इसे "वेतन वृद्धि के लिए ले सकते हैं अगर पर्याप्त जगह है, तो इसे भूल जाएं"? यदि आप पोर्ट कोडिंग कर रहे हैं, तो क्या आप uint_8 के बजाय int (4 बाइट्स) का उपयोग कर सकते हैं? यह आपकी समस्या को कई मामलों में रोकता है।
पक

1
@ ठीक है आप सही हैं, हम इसे 4 बाइट्स में बदल सकते हैं, लेकिन मौजूदा सिस्टम के साथ संचार करते समय यह संगतता को तोड़ देगा। आशय यह जानना है कि जब कोई त्रुटियां होती हैं और इसलिए 1-बाइट काउंटर मूल रूप से पर्याप्त था, और ऐसा ही रहता है।
चार्ली साल्ट्स

जवाबों:


26

आप जो देख रहे हैं वह पूर्णांक प्रचार का परिणाम है । ज्यादातर मामलों में जहां एक पूर्णांक मान का उपयोग एक अभिव्यक्ति में किया जाता है, यदि मूल्य का प्रकार intमूल्य को बढ़ावा देने से छोटा होता है int। यह सी मानक के 6.3.1.1p2 में प्रलेखित है :

निम्नलिखित का प्रयोग अभिव्यक्ति में कहीं भी किया जा सकता है intया unsigned intइसका उपयोग किया जा सकता है

  • एक वस्तु या अभिव्यक्ति एक पूर्णांक प्रकार के साथ (के अलावा अन्य intया unsigned int) जिसका पूर्णांक रूपांतरण रैंक से कम या के पद के लिए बराबर है intऔर unsigned int
  • बिट-प्रकार का क्षेत्र _Bool, int ,हस्ताक्षरित int , orअहस्ताक्षरित int`।

यदि कोई intमूल प्रकार के सभी मूल्यों का प्रतिनिधित्व कर सकता है (जैसा कि चौड़ाई द्वारा सीमित है, एक बिट-फ़ील्ड के लिए), तो मान को एक में बदल दिया जाता है int; अन्यथा, यह एक में बदल जाता है unsigned int। इन्हें पूर्णांक पदोन्नति कहा जाता है । अन्य सभी प्रकार पूर्णांक प्रचार द्वारा अपरिवर्तित हैं।

इसलिए यदि एक चर का प्रकार uint8_tऔर मान 255 है, तो कास्ट या असाइनमेंट के अलावा किसी भी ऑपरेटर का उपयोग करने intसे पहले ऑपरेशन करने से पहले इसे 255 मान के साथ टाइप करने के लिए बदल देगा । यही कारण है कि sizeof(~i)आपको 1 के बजाय 4 देता है।

खंड 6.5.3.3 बताता है कि पूर्णांक पदोन्नति ~ऑपरेटर पर लागू होती है :

~ऑपरेटर का परिणाम इसके (पदोन्नत) ऑपरेंड का बिटवाइज़ पूरक है (अर्थात, परिणाम में प्रत्येक बिट सेट किया गया है और केवल यदि परिवर्तित ऑपरेटर में संबंधित बिट सेट नहीं है)। पूर्णांक पदोन्नति को ऑपरेंड पर किया जाता है, और परिणाम में प्रचारित प्रकार होता है। यदि पदोन्नत प्रकार एक अहस्ताक्षरित प्रकार है, तो अभिव्यक्ति ~Eउस प्रकार के ऋण में अधिकतम मूल्य के बराबर है E

तो एक 32 बिट मान int, अगर counter8 बिट मूल्य 0xffयह 32 बिट मूल्य में बदल जाता है 0x000000ff, और ~इसे लागू करने से आपको मिलता है 0xffffff00

संभवतः इसे संभालने का सबसे सरल तरीका यह जानने के बिना है कि प्रकार को जांचना है कि क्या मूल्य वृद्धि के बाद 0 है, और यदि ऐसा है तो इसे घटाएं।

if (!++counter) counter--;

अहस्ताक्षरित पूर्णांकों का रैपराउंड दोनों दिशाओं में काम करता है, इसलिए 0 का मान घटाना आपको सबसे बड़ा सकारात्मक मूल्य देता है।


1
if (!++counter) --counter;अल्पविराम ऑपरेटर का उपयोग करने की तुलना में कुछ प्रोग्रामर के लिए कम अजीब हो सकता है।
एरिक पोस्टपिसिल

1
एक और विकल्प है ++counter; counter -= !counter;
एरिक पोस्टपिसिल

@ EricPostpischil वास्तव में, मुझे आपका पहला विकल्प बेहतर लगता है। संपादित।
dbush

15
यह बदसूरत और अपठनीय है कोई फर्क नहीं पड़ता कि आप इसे कैसे लिखते हैं। यदि आपको इस तरह से एक मुहावरे का उपयोग करना है , तो प्रत्येक रखरखाव प्रोग्रामर को एक एहसान करें और इसे एक इनलाइन फ़ंक्शन के रूप में लपेटें : कुछ ऐसा increment_unsigned_without_wraparoundया increment_with_saturation। व्यक्तिगत रूप से, मैं एक सामान्य त्रि-ऑपरेंड clampफ़ंक्शन का उपयोग करूँगा ।
कोड़ी ग्रे

5
इसके अलावा, आप इसे एक फ़ंक्शन नहीं बना सकते, क्योंकि इसमें विभिन्न तर्क प्रकारों के लिए अलग-अलग व्यवहार करना पड़ता है। आपको एक प्रकार-जेनेरिक मैक्रो का उपयोग करना होगा ।
user2357112

7

में sizeof (i); आप चर i के आकार का अनुरोध करते हैं , इसलिए 1

में sizeof (~ i); आप अभिव्यक्ति के प्रकार है, जो एक है के आकार का अनुरोध पूर्णांक , अपने मामले में 4


काम में लाना

अगर (~ i)

यह जानने के लिए कि क्या मैं 255 (uint8_t के साथ आपके मामले में) मूल्य नहीं रखता है, बहुत पठनीय नहीं है, बस

if (i != 255)

और आपके पास एक पोर्टेबल और पठनीय कोड होगा


चर के कई आकार हैं (उदाहरण के लिए, uint16_t और अहस्ताक्षरित चार आदि)

अहस्ताक्षरित किसी भी आकार का प्रबंधन करने के लिए:

if (i != (((uintmax_t) 2 << (sizeof(i)*CHAR_BIT-1)) - 1))

अभिव्यक्ति स्थिर है, इसलिए संकलित समय पर गणना की जाती है।

# शामिल <limits.h> के लिए CHAR_BIT और # शामिल <stdint.h> के लिए uintmax_t


3
प्रश्न स्पष्ट रूप से बताता है कि उनके पास निपटने के लिए कई आकार हैं, इसलिए != 255अपर्याप्त है।
एरिक पोस्टपिसिल

@EricPostpischil आह हाँ, मैं यह भूल जाता हूँ, इसलिए "if (i! = (1u << sizeof (i) * 8) - 1)" "हमेशा अहस्ताक्षरित?
ब्रूनो

1
यह unsignedऑब्जेक्ट के लिए अपरिभाषित होगा क्योंकि पूर्ण ऑब्जेक्ट चौड़ाई की पाली सी मानक द्वारा परिभाषित नहीं है, लेकिन इसके साथ तय किया जा सकता है (2u << sizeof(i)*CHAR_BIT-1) - 1
एरिक पोस्टपिसिल

ओह हां ऑफ सी सी, CHAR_BIT, मेरा बुरा
ब्रूनो

2
व्यापक प्रकार की सुरक्षा के लिए, कोई भी उपयोग कर सकता है ((uintmax_t) 2 << sizeof(i)*CHAR_BIT-1) - 1
एरिक पोस्टपिसिल

5

यहाँ "एक जोड़ें, xलेकिन अधिकतम प्रतिनिधित्व मूल्य पर दबाना " लागू करने के लिए कई विकल्प दिए गए हैं, जो कि xकुछ अहस्ताक्षरित पूर्णांक प्रकार हैं:

  1. एक जोड़ें और यदि केवल xइसके प्रकार में अधिकतम मूल्य से कम हो तो:

    x += x < Maximum(x);

    की परिभाषा के लिए निम्नलिखित आइटम देखें Maximum। यह विधि संकलक द्वारा कुशल निर्देशों जैसे कि तुलना, सशर्त सेट या चाल, और एक ऐड के रूप में अनुकूलित किए जाने का एक अच्छा मौका है।

  2. प्रकार के सबसे बड़े मूल्य की तुलना करें:

    if (x < ((uintmax_t) 2u << sizeof x * CHAR_BIT - 1) - 1) ++x

    (यह 2 एन की गणना करता है , जहां एन में बिट्स की संख्या है x, 2 द्वारा एन bits1 बिट्स को स्थानांतरित करके । हम इसे 1 शिफ्टिंग के बजाय करते हैं। एन बिट्स क्योंकि एक प्रकार में बिट्स की संख्या से एक पारी सी से परिभाषित नहीं है मानक। CHAR_BITमैक्रो कुछ के लिए अपरिचित हो सकता है, यह एक बाइट में बिट्स की संख्या है, इसलिए sizeof x * CHAR_BITप्रकार में बिट्स की संख्या है x।)

    इसे सौंदर्यशास्त्र और स्पष्टता के लिए एक मैक्रो में लपेटा जा सकता है:

    #define Maximum(x) (((uintmax_t) 2u << sizeof (x) * CHAR_BIT - 1) - 1)
    if (x < Maximum(x)) ++x;
  3. वेतन वृद्धि x और सही अगर यह शून्य से लपेटता है, तो एक का उपयोग कर if:

    if (!++x) --x; // !++x is true if ++x wraps to zero.
  4. वेतन वृद्धि x और सही अगर यह एक अभिव्यक्ति का उपयोग करते हुए शून्य से लपेटता है:

    ++x; x -= !x;

    यह नाममात्र शाखा रहित (कभी-कभी प्रदर्शन के लिए फायदेमंद) है, लेकिन एक संकलक इसे ऊपर के रूप में लागू कर सकता है, यदि आवश्यक हो तो एक शाखा का उपयोग करके लेकिन संभवतः बिना शर्त निर्देशों के साथ अगर लक्ष्य वास्तुकला में उपयुक्त निर्देश हैं।

  5. उपरोक्त मैक्रो का उपयोग करके एक शाखा रहित विकल्प है:

    x += 1 - x/Maximum(x);

    यदि xइसका प्रकार अधिकतम है, तो यह इसका मूल्यांकन करता है x += 1-1। नहीं तो है x += 1-0। हालांकि, कई आर्किटेक्चर पर विभाजन कुछ धीमा है। संकलक और लक्ष्य वास्तुकला के आधार पर एक संकलक इसे बिना विभाजन के निर्देशों के अनुसार अनुकूलित कर सकता है।


1
मैं सिर्फ एक जवाब का उपयोग करने के लिए खुद को नहीं ला सकता हूं जो मैक्रो का उपयोग करने की सिफारिश करता है। C में इनलाइन फ़ंक्शन हैं। आप उस मैक्रो परिभाषा के अंदर कुछ भी नहीं कर रहे हैं जो आसानी से एक इनलाइन फ़ंक्शन के अंदर नहीं किया जा सकता है। और यदि आप एक मैक्रो का उपयोग करने जा रहे हैं, तो सुनिश्चित करें कि आप स्पष्टता के लिए रणनीतिक रूप से संक्षिप्त विवरण देते हैं: ऑपरेटर << में बहुत कम वरीयता है। क्लैंग इस बारे में चेतावनी देता है -Wshift-op-parentheses। अच्छी खबर यह है कि एक ऑप्टिमाइज़िंग कंपाइलर यहाँ एक डिवीजन उत्पन्न करने वाला नहीं है, इसलिए आपको इसके धीमे होने की चिंता करने की कोई आवश्यकता नहीं है।
कोड़ी ग्रे

1
@ कोडीग्रे, अगर आपको लगता है कि आप एक फ़ंक्शन के साथ ऐसा कर सकते हैं, तो उत्तर लिखें।
कार्स्टन एस

2
@ कोडीअरे: sizeof xC फ़ंक्शन के अंदर लागू नहीं किया जा सकता क्योंकि xकुछ निश्चित प्रकार के साथ एक पैरामीटर (या अन्य अभिव्यक्ति) होना चाहिए। यह कॉलर द्वारा उपयोग किए जाने वाले किसी भी तर्क के आकार का उत्पादन नहीं कर सकता है। एक मैक्रो कर सकते हैं।
एरिक पोस्टपिसिल

2

Stdint.h से पहले वेरिएबल साइज कंपाइलर से लेकर कंपाइलर तक अलग-अलग हो सकते हैं और C में वास्तविक वेरिएबल टाइप अभी भी int, long आदि हैं और कंपाइलर ऑथर द्वारा उनके साइज के अनुसार अभी भी डिफाइन किए जाते हैं। कुछ मानक नहीं और न ही विशिष्ट मान्यताओं को लक्षित। लेखक (ओं) को फिर दो दुनियाओं के मानचित्र के लिए stdint.h बनाने की आवश्यकता है, जो कि stdint.h का उद्देश्य है कि uint_this को मानचित्रित करना है, ताकि लंबे, छोटे।

यदि आप किसी अन्य कंपाइलर से पोर्ट पोर्ट कर रहे हैं और यह चार, शॉर्ट, इंट, लॉन्ग का उपयोग करता है, तो आपको प्रत्येक प्रकार से गुजरना होगा और पोर्ट को खुद करना होगा, इसके आसपास कोई रास्ता नहीं है। और या तो आप चर के लिए सही आकार के साथ समाप्त होते हैं, घोषणा में परिवर्तन होता है लेकिन लिखित कार्यों के रूप में कोड ...।

if(~counter) counter++;

या ... सीधे मास्क या टाइपकास्ट की आपूर्ति करें

if((~counter)&0xFF) counter++;
if((uint_8)(~counter)) counter++;

दिन के अंत में यदि आप चाहते हैं कि यह कोड काम करे तो आपको इसे नए प्लेटफॉर्म पर पोर्ट करना होगा। आपकी पसंद कैसे। हां, आपको प्रत्येक मामले को हिट करने के लिए समय देना होगा और इसे सही करना होगा, अन्यथा आप इस कोड पर वापस आते रहेंगे जो कि और भी महंगा है।

यदि आप पोर्ट करने से पहले कोड पर चर प्रकारों को अलग-अलग करते हैं और चर प्रकार किस आकार के होते हैं, तो ऐसा करने वाले चर को अलग करें (streint के लिए आसान होना चाहिए) और stdint.h परिभाषाओं का उपयोग करके अपनी घोषणाओं को बदल दें, जो भविष्य में उम्मीद को बदलते हैं। और आपको आश्चर्य होगा लेकिन गलत हेडर का उपयोग कभी-कभी किया जाता है, यहां तक ​​कि चेक भी लगाए जाते हैं ताकि आप रात में बेहतर सो सकें

if(sizeof(uint_8)!=1) return(FAIL);

और जब कोडिंग की शैली काम करती है (यदि (~ काउंटर) काउंटर ++;), तो पोर्टेबिलिटी इच्छाओं के लिए अभी और भविष्य में यह विशेष रूप से आकार को सीमित करने के लिए मास्क का उपयोग करने के लिए सबसे अच्छा है (और घोषणा पर भरोसा नहीं), जब ऐसा करें। कोड पहली जगह में लिखा गया है या सिर्फ पोर्ट खत्म कर रहा है और फिर आपको किसी और दिन फिर से पोर्ट करना होगा। या कोड को अधिक पठनीय बनाने के लिए यदि x <0xFF तब या x! = 0xFF या ऐसा कुछ किया जाए, तो कंपाइलर इसे उसी कोड में ऑप्टिमाइज़ कर सकता है, जो इनमें से किसी भी समाधान के लिए होगा, बस इसे और अधिक पठनीय और कम उपयोगी बनाता है ...

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


0
६.५.३.३ अपरिपक्व अंकगणितीय संचालक
...
संचालक का परिणाम ~इसके (पदोन्नत) ऑपरेंड का बिटवाइज पूरक है (अर्थात, परिणाम में प्रत्येक बिट सेट है और केवल यदि परिवर्तित ऑपरेटर में संबंधित बिट सेट नहीं है )। पूर्णांक पदोन्नति को ऑपरेंड पर किया जाता है, और परिणाम में प्रचारित प्रकार होता है । यदि पदोन्नत प्रकार एक अहस्ताक्षरित प्रकार है, तो अभिव्यक्ति ~Eउस प्रकार के ऋण में अधिकतम मूल्य के बराबर है E

सी 2011 ऑनलाइन ड्राफ्ट

मुद्दा यह है कि के संचालन को ~बढ़ावा दिया जा रहा हैint ऑपरेटर के लागू पहले के है।

दुर्भाग्य से, मुझे नहीं लगता कि इसमें से एक आसान तरीका है। लिख रहे हैं

if ( counter + 1 ) counter++;

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

#define MAX_COUNTER 255
...
if ( counter < MAX_COUNTER-1 ) counter++;

मैं पूर्णांक पदोन्नति के बारे में बात की सराहना करता हूं - ऐसा लगता है कि यह वह मुद्दा है जिसमें हम भाग रहे हैं। हालांकि, इस बात की ओर इशारा करते हुए कि आपके दूसरे कोड के नमूने में -1जरूरत नहीं है, क्योंकि इससे काउंटर 254 (0xFE) पर बस जाएगा। किसी भी स्थिति में, यह दृष्टिकोण, जैसा कि मेरे प्रश्न में उल्लेख किया गया है, कोडबस में विभिन्न चर आकारों के कारण आदर्श नहीं है जो इस मुहावरे में भाग लेते हैं।
चार्ली साल्ट्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.