आपका सवाल शायद नहीं था, "ये निर्माण सी में अपरिभाषित व्यवहार क्यों हैं?"। आपका प्रश्न संभवतः था, "इस कोड ( ++
मुझे उपयोग करके ) ने मुझे वह मूल्य क्यों नहीं दिया जिसकी मुझे उम्मीद थी?", और किसी ने आपके प्रश्न को एक डुप्लिकेट के रूप में चिह्नित किया, और आपको यहां भेजा।
यह उत्तर उस प्रश्न का उत्तर देने की कोशिश करता है: आपके कोड ने आपको वह उत्तर क्यों नहीं दिया जिसकी आप अपेक्षा करते थे, और आप उन अभिव्यक्तियों को कैसे पहचान सकते हैं (और बच सकते हैं) जो अपेक्षा के अनुरूप काम नहीं करेंगी।
मुझे लगता है कि आपने अब तक सी ++
और --
ऑपरेटरों की मूल परिभाषा सुनी है , और उपसर्ग फॉर्म ++x
पोस्टफिक्स फॉर्म से अलग है x++
। लेकिन इन ऑपरेटरों के बारे में सोचना मुश्किल है, इसलिए यह सुनिश्चित करने के लिए कि आप समझ गए हैं, शायद आपने एक छोटा सा परीक्षण कार्यक्रम लिखा है जिसमें कुछ ऐसा है
int x = 5;
printf("%d %d %d\n", x, ++x, x++);
लेकिन, आपके आश्चर्य के लिए, इस कार्यक्रम ने आपको समझने में मदद नहीं की - इसने कुछ अजीब, अप्रत्याशित, अकथनीय उत्पादन को मुद्रित किया, यह सुझाव देते हुए कि शायद ++
कुछ पूरी तरह से अलग है, जो आपने सोचा था कि वह बिल्कुल भी नहीं।
या, शायद आप एक हार्ड-टू-समझने की अभिव्यक्ति की तरह देख रहे हैं
int x = 5;
x = x++ + ++x;
printf("%d\n", x);
शायद किसी ने आपको पहेली के रूप में वह कोड दिया था। यह कोड भी कोई मतलब नहीं है, खासकर यदि आप इसे चलाते हैं - और यदि आप इसे दो अलग-अलग संकलकों के तहत संकलित करते हैं और चलाते हैं, तो आपको दो अलग-अलग उत्तर मिलने की संभावना है! उसके साथ क्या है? कौन सा उत्तर सही है? (और जवाब है कि वे दोनों हैं, या दोनों में से कोई भी नहीं है।)
जैसा कि आपने अब तक सुना है, ये सभी अभिव्यक्तियाँ अपरिभाषित हैं , जिसका अर्थ है कि सी भाषा इस बारे में कोई गारंटी नहीं देती है कि वे क्या करेंगे। यह एक अजीब और आश्चर्यजनक परिणाम है, क्योंकि आपने शायद सोचा था कि कोई भी कार्यक्रम जिसे आप लिख सकते हैं, जब तक यह संकलित और चलाया जाता है, एक अद्वितीय, अच्छी तरह से परिभाषित आउटपुट उत्पन्न करेगा। लेकिन अपरिभाषित व्यवहार के मामले में, ऐसा नहीं है।
क्या एक अभिव्यक्ति अपरिभाषित बनाता है? क्या अभिव्यक्ति शामिल है ++
और --
हमेशा अपरिभाषित है? बेशक नहीं: ये उपयोगी ऑपरेटर हैं, और यदि आप उन्हें ठीक से उपयोग करते हैं, तो वे पूरी तरह से परिभाषित हैं।
जिन अभिव्यक्तियों के बारे में हम बात कर रहे हैं, जो उन्हें अपरिभाषित बनाती हैं, वह यह है कि जब एक ही बार में बहुत अधिक चल रहा हो, जब हमें यकीन नहीं होता कि चीजें किस क्रम में होंगी, लेकिन जब हम परिणाम प्राप्त करते हैं तो यह क्रम मायने रखता है।
आइए इस उत्तर में जिन दो उदाहरणों का मैंने उपयोग किया है, उन पर वापस जाएं। जब मैंने लिखा
printf("%d %d %d\n", x, ++x, x++);
सवाल यह है कि कॉल printf
करने से x
पहले, कंपाइलर पहले, या x++
, या शायद के मूल्य की गणना करता है ++x
? लेकिन यह पता चला है कि हम नहीं जानते । C में कोई नियम नहीं है जो कहता है कि किसी फ़ंक्शन के तर्कों का मूल्यांकन बाएं से दाएं, या दाएं-बाएं, या किसी अन्य क्रम में किया जाता है। इसलिए हम यह नहीं कह सकते कि संकलक x
पहले करेगा , फिर ++x
, फिर x++
, या x++
फिर ++x
तब x
, या कुछ अन्य क्रम। लेकिन आदेश स्पष्ट रूप से मायने रखता है, क्योंकि संकलक किस क्रम का उपयोग करता है, इसके आधार पर, हम स्पष्ट रूप से अलग-अलग परिणाम मुद्रित करेंगे printf
।
इस पागल अभिव्यक्ति के बारे में क्या?
x = x++ + ++x;
इस अभिव्यक्ति के साथ समस्या यह है कि इसमें x के मान को संशोधित करने के तीन अलग-अलग प्रयास हैं: (1) x++
भाग 1 से x को जोड़ने, नए मान को संग्रहीत करने x
और पुराने मान को वापस करने का प्रयास करता है x
; (2) ++x
भाग 1 से x जोड़ने का प्रयास करता है, नए मान को संग्रहीत करता है और नए मान को x
लौटाता है x
; और (3) x =
भाग अन्य दो का योग असाइन करने का प्रयास करता है। उन तीन में से कौन सा प्रयास "जीत" होगा? वास्तव में तीन मूल्यों में से x
किसे सौंपा जाएगा ? फिर, और शायद आश्चर्यजनक रूप से, हमें बताने के लिए सी में कोई नियम नहीं है।
आप सोच सकते हैं कि पूर्वता या सहानुभूति या बाएं से दाएं मूल्यांकन आपको बताते हैं कि चीजें किस क्रम में होती हैं, लेकिन वे नहीं करते हैं। आप मुझ पर विश्वास नहीं कर सकते हैं, लेकिन कृपया इसके लिए मेरा शब्द लें, और मैं इसे फिर से कहूंगा: पूर्वता और सहानुभूति किसी अभिव्यक्ति के मूल्यांकन क्रम के हर पहलू को निर्धारित नहीं करते हैं। विशेष रूप से, यदि एक अभिव्यक्ति के भीतर कई हैं अलग-अलग स्पॉट जहां हम कुछ नया मान देने की कोशिश करते हैं जैसे x
, पूर्वता और सहानुभूति हमें यह नहीं बताती है कि उन प्रयासों में से कौन सा प्रयास पहले, या अंतिम या कुछ भी होता है।
तो उस सभी पृष्ठभूमि और रास्ते से बाहर परिचय के साथ, यदि आप यह सुनिश्चित करना चाहते हैं कि आपके सभी कार्यक्रम अच्छी तरह से परिभाषित हैं, तो आप कौन से भाव लिख सकते हैं और आप कौन से नहीं लिख सकते हैं?
ये भाव सभी ठीक हैं:
y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;
ये भाव सभी अपरिभाषित हैं:
x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);
और आखिरी सवाल यह है कि आप कैसे बता सकते हैं कि कौन से भाव अच्छी तरह से परिभाषित हैं, और कौन से भाव अपरिभाषित हैं?
जैसा कि मैंने पहले कहा था, अपरिभाषित भाव वे हैं जहाँ एक बार बहुत अधिक हो रहा है, जहाँ आप यह सुनिश्चित नहीं कर सकते कि चीजें किस क्रम में होती हैं, और जहाँ आदेश मायने रखता है:
- यदि दो या अधिक भिन्न स्थानों में एक चर है जिसे संशोधित (असाइन) किया जा रहा है, तो आप कैसे जानते हैं कि कौन सा संशोधन पहले होता है?
- यदि एक चर है जो एक स्थान पर संशोधित हो रहा है, और उसका मूल्य दूसरी जगह उपयोग किया जा रहा है, तो आप कैसे जानते हैं कि यह पुराने मूल्य का उपयोग करता है या नए मूल्य का?
# 1 के उदाहरण के रूप में, अभिव्यक्ति में
x = x++ + ++x;
`x को संशोधित करने के तीन प्रयास हैं।
# 2 के उदाहरण के रूप में, अभिव्यक्ति में
y = x + x++;
हम दोनों के मूल्य का उपयोग करते हैं x
, और इसे संशोधित करते हैं।
तो इसका उत्तर है: सुनिश्चित करें कि आप जो भी अभिव्यक्ति लिखते हैं, प्रत्येक चर को एक बार में संशोधित किया जाता है, और यदि एक चर को संशोधित किया जाता है, तो आप उस चर के मूल्य का उपयोग कहीं और करने का प्रयास नहीं करते हैं।