ये निर्माण पूर्व और पश्चात वृद्धि अपरिभाषित व्यवहार का उपयोग क्यों कर रहे हैं?


814
#include <stdio.h>

int main(void)
{
   int i = 0;
   i = i++ + ++i;
   printf("%d\n", i); // 3

   i = 1;
   i = (i++);
   printf("%d\n", i); // 2 Should be 1, no ?

   volatile int u = 0;
   u = u++ + ++u;
   printf("%d\n", u); // 1

   u = 1;
   u = (u++);
   printf("%d\n", u); // 2 Should also be one, no ?

   register int v = 0;
   v = v++ + ++v;
   printf("%d\n", v); // 3 (Should be the same as u ?)

   int w = 0;
   printf("%d %d\n", ++w, w); // shouldn't this print 1 1

   int x[2] = { 5, 8 }, y = 0;
   x[y] = y ++;
   printf("%d %d\n", x[0], x[1]); // shouldn't this print 0 8? or 5 0?
}

12
@Jarett, nope, बस "अनुक्रम बिंदु" के लिए कुछ संकेत की आवश्यकता है। काम करते हुए मुझे i = i ++ के साथ कोड का एक टुकड़ा मिला, I Yougth "यह i के मूल्य को संशोधित नहीं कर रहा है"। मैंने परीक्षण किया और मैंने सोचा कि क्यों। चूंकि, मैंने इस प्रतिमा को हटा दिया है और इसे i ++ द्वारा प्रतिस्थापित कर दिया है;
पीआईएक्स

198
मुझे लगता है कि यह दिलचस्प है कि हर कोई मानता है कि इस तरह के प्रश्न पूछे जाते हैं क्योंकि प्रश्नकर्ता निर्माण को प्रश्न में उपयोग करना चाहता है। मेरी पहली धारणा यह थी कि PiX जानता है कि ये बुरे हैं, लेकिन जिज्ञासु हैं कि वे व्यवहार क्यों करते हैं जिस तरह से वेवर कंपाइलर s / he का उपयोग कर रहे थे ... और हाँ, क्या unWind ने कहा ... यह अपरिभाषित है, यह कुछ भी कर सकता है। ..
जेसीएफ

32
मैं जिज्ञासु हूं: कंस्ट्रक्टरों को "u = u ++ + ++ u;" जैसे निर्माणों पर चेतावनी क्यों नहीं लगती है; यदि परिणाम अपरिभाषित है?
OpenGL ES

5
(i++)कोष्ठक की परवाह किए बिना 1 का मूल्यांकन अभी भी करता है
ड्रू मैकगोवेन

2
जो कुछ i = (i++);भी करने का इरादा था, निश्चित रूप से इसे लिखने का एक स्पष्ट तरीका है। यह सच भी होगा अगर इसे अच्छी तरह से परिभाषित किया गया हो। जावा में भी, जो के व्यवहार को परिभाषित करता है i = (i++);, यह अभी भी बुरा कोड है। सिर्फ लिखेंi++;
कीथ थॉम्पसन

जवाबों:


566

C के पास अपरिभाषित व्यवहार की अवधारणा है, अर्थात कुछ भाषा निर्माण वाक्यविन्यास रूप से मान्य हैं, लेकिन जब कोड चलाया जाता है तो आप व्यवहार की भविष्यवाणी नहीं कर सकते।

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

तो, इस बात को ध्यान में रखते हुए, ये "मुद्दे" क्यों हैं? भाषा स्पष्ट रूप से कहती है कि कुछ चीजें अपरिभाषित व्यवहार की ओर ले जाती हैं । कोई समस्या नहीं है, इसमें कोई "नहीं" शामिल होना चाहिए। यदि अपरिभाषित व्यवहार में परिवर्तन होता है, जब शामिल चर में से एक घोषित किया जाता है volatile, तो यह साबित नहीं होता है या कुछ भी नहीं बदलता है। यह अपरिभाषित है ; आप व्यवहार के बारे में कारण नहीं बता सकते।

आपका सबसे दिलचस्प उदाहरण, एक साथ

u = (u++);

अपरिभाषित व्यवहार का एक पाठ्य-पुस्तक उदाहरण है ( अनुक्रम बिंदुओं पर विकिपीडिया की प्रविष्टि देखें )।


8
@PiX: कई संभावित कारणों से चीजें अपरिभाषित हैं। इनमें शामिल हैं: कोई स्पष्ट "सही परिणाम" नहीं है, अलग-अलग मशीन आर्किटेक्चर अलग-अलग परिणामों का दृढ़ता से पक्ष लेंगे, मौजूदा अभ्यास अनुरूप नहीं है, या मानक के दायरे से परे है (जैसे कि फ़ाइल नाम मान्य हैं)।
रिचर्ड

बस सभी को भ्रमित करने के लिए, कुछ ऐसे उदाहरण अब C11 में अच्छी तरह से परिभाषित हैं, जैसे i = ++i + 1;
MM

2
मानक और प्रकाशित तर्क पढ़ना, यह स्पष्ट है कि यूबी की अवधारणा क्यों मौजूद है। मानक को कभी भी पूरी तरह से वर्णित नहीं किया गया था कि एक सी कार्यान्वयन को किसी विशेष उद्देश्य के लिए उपयुक्त होना चाहिए ("वन प्रोग्राम" नियम की चर्चा देखें), लेकिन इसके बजाय कार्यान्वयनकर्ताओं के फैसले पर निर्भर करता है और उपयोगी गुणवत्ता कार्यान्वयन का उत्पादन करने की इच्छा रखता है। निम्न-स्तरीय सिस्टम प्रोग्रामिंग के लिए उपयुक्त एक गुणवत्ता कार्यान्वयन उन कार्यों के व्यवहार को परिभाषित करने की आवश्यकता होगी जो उच्च-अंत संख्या crunching.applications में आवश्यक नहीं होंगे। मानक को जटिल करने की कोशिश करने के बजाय ...
सुपरकैट

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

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

78

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

यह वही है जो मुझे मेरी मशीन पर मिलता है, साथ में मुझे लगता है कि क्या चल रहा है:

$ cat evil.c
void evil(){
  int i = 0;
  i+= i++ + ++i;
}
$ gcc evil.c -c -o evil.bin
$ gdb evil.bin
(gdb) disassemble evil
Dump of assembler code for function evil:
   0x00000000 <+0>:   push   %ebp
   0x00000001 <+1>:   mov    %esp,%ebp
   0x00000003 <+3>:   sub    $0x10,%esp
   0x00000006 <+6>:   movl   $0x0,-0x4(%ebp)  // i = 0   i = 0
   0x0000000d <+13>:  addl   $0x1,-0x4(%ebp)  // i++     i = 1
   0x00000011 <+17>:  mov    -0x4(%ebp),%eax  // j = i   i = 1  j = 1
   0x00000014 <+20>:  add    %eax,%eax        // j += j  i = 1  j = 2
   0x00000016 <+22>:  add    %eax,-0x4(%ebp)  // i += j  i = 3
   0x00000019 <+25>:  addl   $0x1,-0x4(%ebp)  // i++     i = 4
   0x0000001d <+29>:  leave  
   0x0000001e <+30>:  ret
End of assembler dump.

(I ... मान लें कि 0x00000014 निर्देश किसी प्रकार का संकलक अनुकूलन था?)


मैं मशीन कोड कैसे प्राप्त करूं? मैं देव सी ++ का उपयोग करता हूं, और मैंने कंपाइलर सेटिंग्स में 'कोड जेनरेशन' विकल्प के साथ खेला है, लेकिन कोई अतिरिक्त फ़ाइल आउटपुट या कोई कंसोल आउटपुट नहीं जाना है
bad_keypoint

5
@ronnieaka gcc evil.c -c -o evil.binऔर gdb evil.bindisassemble evil, या जो भी विंडोज समतुल्य हैं वे हैं :)
badp

21
यह उत्तर वास्तव में के प्रश्न को संबोधित नहीं करता है Why are these constructs undefined behavior?
शाफिक याघमोर

9
एक तरफ, असेंबली (साथ gcc -S evil.c) को संकलित करना आसान होगा , जो कि यहां आवश्यक है। फिर इसे असेंबल करना इसे करने का सिर्फ एक गोल चक्कर है।
कैट

50
रिकॉर्ड के लिए, यदि किसी भी कारण से आप सोच रहे हैं कि एक दिया गया निर्माण क्या करता है - और विशेष रूप से अगर इसमें कोई संदेह है कि यह अपरिभाषित व्यवहार हो सकता है - "बस अपने कंपाइलर के साथ प्रयास करें और देखें" की उम्र-पुरानी सलाह है संभावित रूप से काफी खतरनाक। आप सीखेंगे, सबसे अच्छा, यह आपके संकलक के इस संस्करण के तहत क्या करता है , इन परिस्थितियों में, आज । यदि आप इसे करने की गारंटी देते हैं, तो आप कुछ भी नहीं सीखेंगे। सामान्य तौर पर, "बस अपने कंपाइलर के साथ इसे आज़माएं" नॉनपोर्टेबल प्रोग्राम की ओर जाता है जो केवल आपके कंपाइलर के साथ काम करते हैं।
स्टीव समिट

64

मुझे लगता है कि C99 मानक के प्रासंगिक भाग 6.5 एक्सप्रेशंस, of2 हैं

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

और 6.5.16 असाइनमेंट ऑपरेटर, .14:

ऑपरेंड के मूल्यांकन का क्रम अनिर्दिष्ट है। यदि असाइनमेंट ऑपरेटर के परिणाम को संशोधित करने या अगले अनुक्रम बिंदु के बाद इसे एक्सेस करने का प्रयास किया जाता है, तो व्यवहार अपरिभाषित है।


2
क्या उपरोक्त इसका अर्थ यह होगा कि 'i = i = 5? "अपरिभाषित व्यवहार होगा?
सुपरकैट

1
@ सुपरकार्ट जहां तक ​​मुझे पता है i=i=5वह अपरिभाषित व्यवहार भी है
dhein

2
@ ज़ैबिस: ज्यादातर नियमों के लिए उपयोग किया जाने वाला औचित्य सिद्धांत में लागू होता है कि सिद्धांत में एक म्यूटली-प्रोसेसर प्लेटफ़ॉर्म A=B=5;"राइट-लॉक ए; राइट-लॉक बी; स्टोर 5 से ए; स्टोर 5 से बी; स्टोर अनलॉक" जैसे कुछ को लागू कर सकता है ; ; अनकॉक ए? ", और C=A+B;" रीड-लॉक ए; रीड-लॉक बी; गणना ए + बी; अनलॉक ए और बी; लिखें-लॉक सी; स्टोर परिणाम; अनलॉक सी; " जैसे कथन । यह सुनिश्चित करेगा कि यदि एक धागा A=B=5;दूसरे ने किया C=A+B;तो बाद वाला धागा या तो दोनों लिखेगा या नहीं। संभावित रूप से एक उपयोगी गारंटी। अगर एक धागा I=I=5;, हालांकि, ...
सुपरकैट

1
... और संकलक ने ध्यान नहीं दिया कि दोनों लिखते एक ही स्थान पर थे (यदि एक या दोनों अंतराल बिंदुओं को शामिल करते हैं, तो यह निर्धारित करना कठिन हो सकता है), उत्पन्न कोड गतिरोध हो सकता है। मुझे नहीं लगता कि कोई भी वास्तविक दुनिया कार्यान्वयन ऐसे लॉकिंग को उनके सामान्य व्यवहार के हिस्से के रूप में लागू करता है, लेकिन यह मानक के तहत स्वीकार्य होगा, और यदि हार्डवेयर ऐसे व्यवहारों को सस्ते में लागू कर सकता है तो यह उपयोगी हो सकता है। आज के हार्डवेयर पर ऐसा व्यवहार डिफ़ॉल्ट के रूप में लागू करने के लिए बहुत महंगा होगा, लेकिन इसका मतलब यह नहीं है कि यह हमेशा रहेगा।
सुपरकट

1
@supercat लेकिन c99 के सीक्वेंस प्वाइंट एक्सेस रूल अकेले नहीं होंगे जो इसे अपरिभाषित व्यवहार के रूप में घोषित करने के लिए पर्याप्त हों? तो यह कोई फर्क नहीं पड़ता कि तकनीकी रूप से हार्डवेयर क्या लागू कर सकता है?
dhein

55

सी मानक के हवाले से यहां दिए गए ज्यादातर जवाबों ने जोर दिया कि इन निर्माणों का व्यवहार अपरिभाषित है। यह समझने के लिए कि इन निर्माणों का व्यवहार अपरिभाषित क्यों है , आइए इन शर्तों को पहले C11 मानक के प्रकाश में समझते हैं:

अनुक्रमित: (5.1.2.3)

किसी भी दो मूल्यांकन को देखते हुए Aऔर B, अगर Aपहले से अनुक्रमित है B, तो निष्पादन के Aपहले के निष्पादन से पहले होगा B

Unsequenced:

तो Aपहले या बाद में अनुक्रम है नहीं B, तो Aऔर Bunsequenced हैं।

मूल्यांकन दो चीजों में से एक हो सकता है:

  • मूल्य अभिकलन , जो एक अभिव्यक्ति के परिणाम का काम करते हैं; तथा
  • साइड इफेक्ट , जो वस्तुओं के संशोधन हैं।

अनुक्रम बिंदु:

भाव के मूल्यांकन के बीच एक दृश्य बिंदु की उपस्थिति Aऔर Bसंकेत मिलता है कि हर मूल्य गणना और पक्ष प्रभाव के साथ जुड़े Aहर से पहले अनुक्रम है मूल्य गणना और पक्ष प्रभाव के साथ जुड़े B

अब सवाल आ रहा है, जैसे भावों के लिए

int i = 1;
i = i++;

मानक कहते हैं कि:

6.5 अभिव्यक्तियाँ:

एक अदिश वस्तु पर एक पक्ष प्रभाव के लिए unsequenced रिश्तेदार है या तो एक ही अदिश वस्तु पर एक अलग पक्ष प्रभाव या एक मूल्य गणना एक ही अदिश वस्तु के मूल्य का उपयोग कर, व्यवहार अपरिभाषित है । [...]

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

iअसाइनमेंट के बाईं ओर का नाम बदलें ilऔर असाइनमेंट के दाईं ओर (एक्सप्रेशन में i++) हो ir, फिर एक्सप्रेशन इस तरह हो

il = ir++     // Note that suffix l and r are used for the sake of clarity.
              // Both il and ir represents the same object.  

पोस्टफिक्स ++ऑपरेटर के बारे में एक महत्वपूर्ण बात यह है कि:

सिर्फ इसलिए कि ++चर के बाद आता है इसका मतलब यह नहीं है कि वेतन वृद्धि देर से होती है । वेतन वृद्धि तब तक हो सकती है जब तक कंपाइलर पसंद करता है जब तक कंपाइलर यह सुनिश्चित करता है कि मूल मूल्य का उपयोग किया जाता है

इसका मतलब है कि अभिव्यक्ति il = ir++का मूल्यांकन या तो किया जा सकता है

temp = ir;      // i = 1
ir = ir + 1;    // i = 2   side effect by ++ before assignment
il = temp;      // i = 1   result is 1  

या

temp = ir;      // i = 1
il = temp;      // i = 1   side effect by assignment before ++
ir = ir + 1;    // i = 2   result is 2  

दो अलग-अलग परिणामों के परिणामस्वरूप 1और 2जो असाइनमेंट द्वारा साइड इफेक्ट्स के अनुक्रम पर निर्भर करता है ++और इसलिए यूबी को आमंत्रित करता है।


52

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

अतः अनिर्दिष्ट व्यवहार की ओर बढ़ते हुए , ड्राफ्ट में c99 मानक खंड 6.5पैरा 3 कहता है ( जोर मेरा ):

ऑपरेटरों और ऑपरेंडों के समूह को सिंटैक्स.74 द्वारा दर्शाया गया है। बाद में निर्दिष्ट के अलावा (फ़ंक्शन-कॉल के लिए) (, &&, ||, ;: ;: और अल्पविराम ऑपरेटर), उप-संदर्भों के मूल्यांकन का क्रम और क्रम में कौन से दुष्प्रभाव होते हैं दोनों अनिर्दिष्ट हैं।

तो जब हमारे पास इस तरह की एक पंक्ति है:

i = i++ + ++i;

हम नहीं जानते कि पहले मूल्यांकन किया जाएगा i++या नहीं ++i। यह मुख्य रूप से संकलक को अनुकूलन के लिए बेहतर विकल्प देने के लिए है

हम यह भी है अपरिभाषित व्यवहार के बाद से कार्यक्रम चर को संशोधित किया जाता है (यहाँ भी i, uके बीच एक बार से, आदि ..) अधिक अनुक्रम अंक । मसौदा मानक खंड 6.5पैरा 2 ( जोर मेरा ) से:

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

यह अपरिभाषित होने के रूप में निम्नलिखित कोड उदाहरणों का हवाला देता है:

i = ++i + 1;
a[i++] = i; 

इन सभी उदाहरणों में कोड एक ही अनुक्रम बिंदु में एक से अधिक बार एक वस्तु को संशोधित करने का प्रयास कर रहा है, जो ;इन मामलों में से प्रत्येक में समाप्त होगा :

i = i++ + ++i;
^   ^       ^

i = (i++);
^    ^

u = u++ + ++u;
^   ^       ^

u = (u++);
^    ^

v = v++ + ++v;
^   ^       ^

अनिर्दिष्ट व्यवहार को अनुभाग में प्रारूप c99 मानक में परिभाषित किया गया है 3.4.4:

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

और अपरिभाषित व्यवहार को अनुभाग में परिभाषित किया 3.4.3गया है:

एक गैर-जिम्मेदार या गलत कार्यक्रम के निर्माण या गलत डेटा के उपयोग पर, जिसके लिए यह अंतर्राष्ट्रीय मानक कोई आवश्यकता नहीं रखता है

और ध्यान दें कि:

संभावित अपरिभाषित व्यवहार, अप्रत्याशित परिणामों के साथ स्थिति को पूरी तरह से अनदेखा करने से लेकर, अनुवाद या कार्यक्रम के निष्पादन के दौरान व्यवहार करने के लिए पर्यावरण के एक दस्तावेज तरीके से विशेषता (नैदानिक ​​संदेश जारी किए बिना या बिना), अनुवाद या निष्पादन को समाप्त करने के साथ होता है। नैदानिक ​​संदेश का)।


33

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

पहले टुकड़े के बारे में पूछा, i = i++ + ++iमेरी किताब में बहुत स्पष्ट रूप से पागल है। कोई भी कभी भी इसे एक वास्तविक कार्यक्रम में नहीं लिखेगा, यह स्पष्ट नहीं है कि यह क्या करता है, कोई भी कल्पित एल्गोरिथ्म नहीं है जिसे कोई व्यक्ति कोड करने की कोशिश कर रहा हो, जिसके परिणामस्वरूप यह विशेष रूप से संचालित अनुक्रम से जुड़ा होगा। और चूंकि यह आपके और मेरे लिए स्पष्ट नहीं है कि यह क्या करना चाहिए, यह मेरी पुस्तक में ठीक है यदि कंपाइलर यह पता नहीं लगा सकता है कि यह क्या करना है, या तो।

दूसरा टुकड़ा, i = i++समझने में थोड़ा आसान है। कोई व्यक्ति स्पष्ट रूप से मुझे वेतन वृद्धि की कोशिश कर रहा है, और परिणाम को वापस i को सौंप देगा। लेकिन सी में ऐसा करने के कुछ तरीके हैं। 1 से 1 को जोड़ने का सबसे बुनियादी तरीका है, और परिणाम को वापस I में असाइन करना, लगभग किसी भी प्रोग्रामिंग भाषा में समान है:

i = i + 1

सी, ज़ाहिर है, एक आसान शॉर्टकट है:

i++

इसका मतलब है, "1 को i में जोड़ें, और परिणाम को वापस i पर असाइन करें"। इसलिए अगर हम लिखकर दोनों के एक हॉज का निर्माण करें

i = i++

जो हम वास्तव में कह रहे हैं वह है "1 को i में जोड़ें, और परिणाम को वापस i में असाइन करें, और परिणाम को वापस i में असाइन करें"। हम उलझन में हैं, इसलिए यह मुझे बहुत परेशान नहीं करता है यदि कंपाइलर भ्रमित हो जाता है, तो भी।

वास्तविक रूप से, इन पागल अभिव्यक्तियों को लिखने का एकमात्र समय तब होता है जब लोग उन्हें कृत्रिम उदाहरण के रूप में उपयोग कर रहे हैं कि ++ कैसे काम करना चाहिए। और निश्चित रूप से यह समझना महत्वपूर्ण है कि ++ कैसे काम करता है। लेकिन ++ का उपयोग करने के लिए एक व्यावहारिक नियम है, "अगर यह स्पष्ट नहीं है कि ++ का उपयोग करने का एक अभिव्यक्ति क्या है, तो इसे न लिखें।"

हम comp.lang.c पर अनगिनत घंटे बिताते थे जो इन जैसे भावों पर चर्चा करते थे और वे अपरिभाषित क्यों होते थे। मेरे दो लंबे उत्तर, जो वास्तव में समझाने की कोशिश करते हैं कि, वेब पर संग्रहीत क्यों हैं:

C अकसर पूछे जाने वाले प्रश्न सूची की धारा 3 में प्रश्न 3.8 और शेष प्रश्न भी देखें ।


1
अपरिभाषित व्यवहार के संबंध में एक नहीं बल्कि बुरा पकड़ लिया है कि जब तक यह है इस्तेमाल किया compilers की 99.9% का उपयोग करने पर सुरक्षित रहने के लिए *p=(*q)++;मतलब है if (p!=q) *p=(*q)++; else *p= __ARBITRARY_VALUE;कि अब यह मामला नहीं है। हाइपर-मॉडर्न C को बाद के फॉर्मूलेशन की तरह कुछ लिखने की आवश्यकता होगी (हालांकि कोड का संकेत देने का कोई मानक तरीका नहीं है कि क्या ध्यान रखें *p) पूर्व के साथ प्रदान करने के लिए उपयोग किए जाने वाले दक्षता कंपाइलरों के स्तर को प्राप्त करने के लिए ( elseक्लॉज आवश्यक है ताकि जाने दिया जा सके कंपाइलर ऑप्टिमाइज़ करता है ifजिसमें कुछ नए कंपाइलरों की आवश्यकता होगी)।
सुपरकैट

@supercat अब मेरा मानना ​​है कि कोई भी कंपाइलर जो "स्मार्ट" होता है, उस तरह के ऑप्टिमाइज़ेशन को करने के लिए भी assertस्टेटमेंट पर नज़र रखने के लिए पर्याप्त स्मार्ट होना चाहिए , ताकि प्रोग्रामर सरल तरीके से सवाल के साथ लाइन को पूर्ववर्ती कर सके assert(p != q)। (बेशक, उस कोर्स को लेने के लिए भी <assert.h>गैर-डीबग संस्करणों में एकरूपता को हटाने के लिए पुनर्लेखन की आवश्यकता नहीं होगी , बल्कि, उन्हें कुछ इस तरह मोड़ें __builtin_assert_disabled()कि कंपाइलर उचित देख सकें, और उसके लिए कोड का उत्सर्जन न करें।)
स्टीव समिट

25

अक्सर इस प्रश्न को कोड से संबंधित प्रश्नों की नकल के रूप में जोड़ा जाता है

printf("%d %d\n", i, i++);

या

printf("%d %d\n", ++i, i++);

या इसी तरह के रूप।

हालांकि यह भी अपरिभाषित व्यवहार है जैसा कि पहले ही कहा जा चुका है, जब printf()बयान की तुलना करते समय इसमें अंतर होता है जैसे कि:

x = i++ + i++;

निम्नलिखित कथन में:

printf("%d %d\n", ++i, i++);

मूल्यांकन के क्रम में तर्कों की printf()है अनिर्दिष्ट । इसका मतलब है, अभिव्यक्ति i++और ++iकिसी भी क्रम में मूल्यांकन किया जा सकता है। C11 मानक में इस पर कुछ प्रासंगिक विवरण हैं:

अनुलग्नक जे, अनिर्दिष्ट व्यवहार

जिस क्रम में फ़ंक्शन डिज़ाइनर, आर्ग्युमेंट्स, और तर्कों के भीतर उप-फ़ंक्शन का मूल्यांकन फ़ंक्शन कॉल (6.5.2.2) में किया जाता है।

3.4.4, अनिर्दिष्ट व्यवहार

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

उदाहरण अनिर्दिष्ट व्यवहार का एक उदाहरण वह क्रम है जिसमें किसी फ़ंक्शन के तर्कों का मूल्यांकन किया जाता है।

अनिर्दिष्ट व्यवहार ही कोई मुद्दा नहीं है। इस उदाहरण पर विचार करें:

printf("%d %d\n", ++x, y++);

यह भी है अनिर्दिष्ट व्यवहार की वजह से मूल्यांकन के आदेश ++xऔर y++अनिर्दिष्ट है। लेकिन यह पूरी तरह से कानूनी और वैध कथन है। नहीं है कोई इस बयान में अपरिभाषित व्यवहार। क्योंकि संशोधन ( ++xऔर y++) अलग-अलग वस्तुओं के लिए किए जाते हैं।

निम्नलिखित कथन का प्रतिपादन करता है

printf("%d %d\n", ++i, i++);

जैसा कि अपरिभाषित व्यवहार यह तथ्य है कि ये दो अभिव्यक्तियाँ एक ही वस्तु iको एक हस्तक्षेप अनुक्रम बिंदु के बिना संशोधित करती हैं ।


एक और विस्तार यह है कि प्रिंटफ () कॉल में शामिल अल्पविराम एक विभाजक है , न कि अल्पविराम ऑपरेटर

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

int i = 5;
int j;

j = (++i, i++);  // No undefined behaviour here because the comma operator 
                 // introduces a sequence point between '++i' and 'i++'

printf("i=%d j=%d\n",i, j); // prints: i=7 j=6

अल्पविराम ऑपरेटर अपने ऑपरेंडों को बाएं-से-दाएं का मूल्यांकन करता है और अंतिम ऑपरेंड के केवल मूल्य देता है। तो में j = (++i, i++);, ++iवेतन वृद्धि iकरने के लिए 6और i++पैदावार के पुराने मूल्य i( 6), जो करने के लिए असाइन किया गया है j। फिर पोस्ट-इन्क्रीमेंट के कारण iबन जाता है 7

तो अगर अल्पविराम समारोह कॉल में एक अल्पविराम ऑपरेटर तो होना करने के लिए थे

printf("%d %d\n", ++i, i++);

कोई समस्या नहीं होगी। लेकिन यह अपरिभाषित व्यवहार को आमंत्रित करता है क्योंकि यहाँ अल्पविराम एक विभाजक है


उन लोगों के लिए जो अपरिभाषित व्यवहार के लिए नए हैं, उन्हें पढ़ने से लाभ होगा कि सी में हर सी प्रोग्रामर को अपरिभाषित व्यवहार के बारे में पता होना चाहिए और सी में अपरिभाषित व्यवहार के कई अन्य रूपों को समझना चाहिए

यह पद: अनिर्धारित, अनिर्दिष्ट और कार्यान्वयन-परिभाषित व्यवहार भी प्रासंगिक है।


यह अनुक्रम int a = 10, b = 20, c = 30; printf("a=%d b=%d c=%d\n", (a = a + b + c), (b = b + b), (c = c + c));स्थिर व्यवहार (gcc v7.3.0 में दाएँ से बाएँ तर्क मूल्यांकन देता है; परिणाम "a = 110 b = 40 c = 60")। क्या इसलिए कि असाइनमेंट को 'पूर्ण-कथन' के रूप में माना जाता है और इस प्रकार एक अनुक्रम बिंदु पेश किया जाता है? क्या यह परिणाम बाएं से दाएं तर्क / कथन मूल्यांकन में नहीं होना चाहिए? या, क्या यह केवल अपरिभाषित व्यवहार की अभिव्यक्ति है?
कावडिय़ा

@kavadias कि printf स्टेटमेंट में अपरिभाषित व्यवहार शामिल है, इसी कारण से ऊपर बताया गया है। आप क्रमशः तीसरे और चौथे तर्क में लिख रहे हैं bऔर c2 तर्क में पढ़ रहे हैं। लेकिन इन भावों के बीच कोई क्रम नहीं है (2nd, 3rd, & 4th args)। gcc / clang में एक विकल्प है -Wsequence-pointजो इनको खोजने में मदद कर सकता है।
पीपी

23

हालांकि यह संभावना नहीं है कि कोई भी कंपाइलर और प्रोसेसर वास्तव में ऐसा करेंगे, यह सी मानक के तहत, कंपाइलर के लिए "i ++" को सीक्वेंस के साथ लागू करने के लिए कानूनी होगा:

In a single operation, read `i` and lock it to prevent access until further notice
Compute (1+read_value)
In a single operation, unlock `i` and store the computed value

हालांकि मुझे नहीं लगता कि कोई भी प्रोसेसर इस तरह के काम को कुशलतापूर्वक करने की अनुमति देने के लिए हार्डवेयर का समर्थन करता है, कोई भी ऐसी परिस्थितियों की आसानी से कल्पना कर सकता है जहां इस तरह का व्यवहार बहु-थ्रेडेड कोड को आसान बना देगा (जैसे यह गारंटी देता है कि यदि दो धागे ऊपर प्रदर्शन करने की कोशिश करते हैं। अनुक्रम एक साथ, iदो से वृद्धि होगी) और यह पूरी तरह से समझ में नहीं आता है कि कुछ भविष्य के प्रोसेसर इस तरह की सुविधा प्रदान कर सकते हैं।

यदि कंपाइलर को i++ऊपर बताए अनुसार लिखना था (मानक के तहत कानूनी) और समग्र अभिव्यक्ति के मूल्यांकन के दौरान उपरोक्त निर्देशों को विच्छेदित करना था (कानूनी भी), और यदि ऐसा नहीं हुआ तो ध्यान दें कि अन्य निर्देशों में से एक हुआ एक्सेस करने के iलिए, संकलक के लिए यह संभव (और कानूनी) होगा कि निर्देशों का एक क्रम उत्पन्न होगा जो गतिरोध उत्पन्न करेगा। सुनिश्चित करने के लिए, एक कंपाइलर लगभग निश्चित रूप से उस स्थिति में समस्या का पता लगाएगा जहां iदोनों स्थानों में एक ही चर का उपयोग किया जाता है, लेकिन अगर एक रूटीन दो पॉइंटर्स pऔर q, और उपयोग करता है (*p)और (*q)उपरोक्त अभिव्यक्ति (उपयोग करने के बजाय) को स्वीकार करता हैiदो बार) संकलक पहचान या गतिरोध तो होने वाले अगर एक ही वस्तु का पता दोनों के लिए पारित किए गए से बचने के लिए आवश्यकता नहीं होगी pऔर q


16

जबकि वाक्य रचना भाव की तरह a = a++या a++ + a++कानूनी है, व्यवहार इन निर्माणों की है अपरिभाषित वजह से एक होगा सी मानक में आज्ञा का पालन नहीं कर रहा है। C99 6.5p2 :

  1. पिछले और अगले अनुक्रम बिंदु के बीच किसी वस्तु का एक अभिव्यक्ति के मूल्यांकन के द्वारा एक बार में उसका संग्रहित मूल्य संशोधित होगा। [The२] इसके अलावा, मूल्य को संग्रहीत करने के लिए केवल निर्धारित करने के लिए पूर्व मूल्य पढ़ा जाएगा [the३]

फुटनोट 73 के साथ आगे स्पष्ट किया गया है

  1. यह पैराग्राफ अपरिभाषित कथन अभिव्यक्तियों को प्रस्तुत करता है जैसे कि

    i = ++i + 1;
    a[i++] = i;

    अनुमति देते समय

    i = i + 1;
    a[i] = i;

विभिन्न अनुक्रम बिंदु C11 (और C99 ) के अनुलग्नक C में सूचीबद्ध हैं :

  1. निम्न क्रमांक 5.1.2.3 में वर्णित हैं:

    • एक फ़ंक्शन कॉल और वास्तविक कॉल में फ़ंक्शन डिज़ाइनर और वास्तविक तर्कों के मूल्यांकन के बीच। (6.5.2.2)।
    • निम्नलिखित ऑपरेटरों के पहले और दूसरे संचालन के मूल्यांकन के बीच: तार्किक और & (6.5.13); तार्किक या || (6.5.14); अल्पविराम, (6.5.17)।
    • सशर्त के पहले ऑपरेंड के मूल्यांकन के बीच? : दूसरे और तीसरे ऑपरेटर के ऑपरेटर और जो भी मूल्यांकन किया जाता है (6.5.15)।
    • एक पूर्ण घोषणाकर्ता का अंत: घोषणाकर्ता (6.7.6);
    • एक पूर्ण अभिव्यक्ति के मूल्यांकन के बीच और अगली पूर्ण अभिव्यक्ति का मूल्यांकन किया जाना है। निम्नलिखित पूर्ण अभिव्यक्तियाँ हैं: एक इनिशलाइज़र जो एक यौगिक शाब्दिक का हिस्सा नहीं है (6.7.9); एक अभिव्यक्ति बयान में अभिव्यक्ति (6.8.3); चयन कथन (यदि या स्विच) (6.8.4) की नियंत्रित अभिव्यक्ति; थोड़ी देर की अभिव्यक्ति की अभिव्यक्ति या कथन (6.8.5); कथन के लिए (वैकल्पिक) अभिव्यक्ति के प्रत्येक (6.8.5.3); , एक वापसी बयान (6.8.6.4) में (वैकल्पिक) अभिव्यक्ति।
    • लाइब्रेरी फंक्शन लौटने से ठीक पहले (7.1.4)।
    • प्रत्येक स्वरूपित इनपुट / आउटपुट फ़ंक्शन रूपांतरण विनिर्देशक (7.21.6, 7.29.2) से जुड़े कार्यों के बाद।
    • तुलनात्मक फ़ंक्शन के लिए प्रत्येक कॉल के तुरंत पहले और तुरंत बाद, और किसी भी कॉल के बीच तुलना फ़ंक्शन के लिए और ऑब्जेक्ट के किसी भी आंदोलन को उस कॉल के तर्क के रूप में पारित किया (7.22.5)।

C11 में उसी पैराग्राफ का शब्दांकन है:

  1. यदि एक स्केलर ऑब्जेक्ट पर एक साइड इफेक्ट एक समान स्केलर ऑब्जेक्ट पर एक अलग साइड इफेक्ट या एक ही स्केलर ऑब्जेक्ट के मूल्य का उपयोग करके एक मूल्य संगणना के सापेक्ष अप्रयुक्त है, तो व्यवहार अपरिभाषित है। यदि किसी अभिव्यक्ति के सबएक्सप्रेस के कई स्वीकार्य आदेश हैं, तो व्यवहार अपरिभाषित है यदि इस तरह के किसी भी क्रम में साइड इफेक्ट होता है।

आप के साथ जीसीसी के नवीनतम संस्करण का उपयोग उदाहरण के लिए एक कार्यक्रम में ऐसी त्रुटियों का पता लगा सकते -Wallहैं और -Werror, और फिर जीसीसी एकमुश्त अपने कार्यक्रम को संकलित करने के लिए मना कर दिया जाएगा। निम्नलिखित gcc का आउटपुट है (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005:

% gcc plusplus.c -Wall -Werror -pedantic
plusplus.c: In function main’:
plusplus.c:6:6: error: operation on i may be undefined [-Werror=sequence-point]
    i = i++ + ++i;
    ~~^~~~~~~~~~~
plusplus.c:6:6: error: operation on i may be undefined [-Werror=sequence-point]
plusplus.c:10:6: error: operation on i may be undefined [-Werror=sequence-point]
    i = (i++);
    ~~^~~~~~~
plusplus.c:14:6: error: operation on u may be undefined [-Werror=sequence-point]
    u = u++ + ++u;
    ~~^~~~~~~~~~~
plusplus.c:14:6: error: operation on u may be undefined [-Werror=sequence-point]
plusplus.c:18:6: error: operation on u may be undefined [-Werror=sequence-point]
    u = (u++);
    ~~^~~~~~~
plusplus.c:22:6: error: operation on v may be undefined [-Werror=sequence-point]
    v = v++ + ++v;
    ~~^~~~~~~~~~~
plusplus.c:22:6: error: operation on v may be undefined [-Werror=sequence-point]
cc1: all warnings being treated as errors

महत्वपूर्ण हिस्सा यह जानना है कि अनुक्रम बिंदु क्या है - और अनुक्रम बिंदु क्या है और क्या नहीं है । उदाहरण के लिए अल्पविराम ऑपरेटर एक अनुक्रम बिंदु है, इसलिए

j = (i ++, ++ i);

अच्छी तरह से परिभाषित किया गया है, और एक से बढ़ेगा i, पुराने मूल्य की उपज, उस मूल्य को त्याग देगा; फिर अल्पविराम ऑपरेटर में, साइड इफेक्ट्स का निपटान करें; और फिर iएक के बाद वृद्धि , और परिणामी मूल्य अभिव्यक्ति का मूल्य बन जाता है - यानी यह लिखने के लिए सिर्फ एक विवादित तरीका है j = (i += 2)जो फिर से लिखने के लिए एक "चतुर" तरीका है

i += 2;
j = i;

हालाँकि, ,फ़ंक्शन तर्क सूचियों में अल्पविराम ऑपरेटर नहीं है, और अलग-अलग तर्कों के मूल्यांकन के बीच कोई अनुक्रम बिंदु नहीं है; इसके बजाय उनके मूल्यांकन एक दूसरे के संबंध में आरोपित हैं; इसलिए फ़ंक्शन कॉल करें

int i = 0;
printf("%d %d\n", i++, ++i, i);

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


14

सी मानक का कहना है कि एक चर को केवल दो अनुक्रम बिंदुओं के बीच में एक बार सौंपा जाना चाहिए। उदाहरण के लिए एक अर्ध-उपनिवेश एक अनुक्रम बिंदु है।
तो फार्म का हर बयान:

i = i++;
i = i++ + ++i;

और इसलिए उस नियम का उल्लंघन करते हैं। मानक यह भी कहता है कि व्यवहार अनिर्धारित है और अनिर्दिष्ट नहीं है। कुछ संकलक इनका पता लगाते हैं और कुछ परिणाम उत्पन्न करते हैं लेकिन यह प्रति मानक नहीं है।

हालांकि, दो अनुक्रम बिंदुओं के बीच दो अलग-अलग चर बढ़े जा सकते हैं।

while(*src++ = *dst++);

स्ट्रिंग की नकल / विश्लेषण करते समय उपरोक्त एक सामान्य कोडिंग अभ्यास है।


बेशक यह एक अभिव्यक्ति के भीतर विभिन्न चर पर लागू नहीं होता है। यदि यह किया जाता है तो यह कुल डिज़ाइन विफलता होगी! आप सभी को दूसरे उदाहरण में जरूरत है कि दोनों को समाप्त होने वाले बयान और अगले एक शुरुआत के बीच वृद्धि के लिए है, और यह गारंटी है, ठीक यही सब के केंद्र में अनुक्रम बिंदुओं की अवधारणा के कारण है।
अंडरस्कोर_ड

11

में /programming/29505280/incrementing-array-index-in-c किसी की तरह एक बयान के बारे में पूछा:

int k[] = {0,1,2,3,4,5,6,7,8,9,10};
int i = 0;
int num;
num = k[++i+k[++i]] + k[++i];
printf("%d", num);

जो 7 प्रिंट करता है ... ओपी को उम्मीद है कि वह 6 प्रिंट करेगा।

++iवेतन वृद्धि की गणना के बाकी से पहले सभी पूरा करने की गारंटी नहीं कर रहे हैं। वास्तव में, विभिन्न कंपाइलरों को यहां अलग-अलग परिणाम मिलेंगे। आपके द्वारा प्रदान किए गए उदाहरण में, पहले 2 को ++iनिष्पादित किया गया, फिर मूल्यों k[]को पढ़ा गया, फिर अंतिम ++iफिर k[]

num = k[i+1]+k[i+2] + k[i+3];
i += 3

आधुनिक संकलक इसे बहुत अच्छी तरह से अनुकूलित करेंगे। वास्तव में, संभवतः आपके द्वारा लिखे गए कोड से बेहतर (यह मानते हुए कि आपने जिस तरह से आशा की थी, वह काम किया होगा)।


5

इस तरह की संगणना में क्या होता है, इस बारे में एक अच्छी व्याख्या ISO W14 साइट से दस्तावेज़ n1188 में प्रदान की गई है ।

मैं विचारों की व्याख्या करता हूं।

इस स्थिति में लागू होने वाले मानक आईएसओ 9899 से मुख्य नियम 6.5p2 है।

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

i=i++पहले i=और बाद की तरह एक अभिव्यक्ति में अनुक्रम अंक i++

पेपर में जो मैंने ऊपर उद्धृत किया है, उसे समझाया जाता है कि आप प्रोग्राम को छोटे बक्से द्वारा गठित किए जाने के रूप में समझ सकते हैं, प्रत्येक बॉक्स में 2 लगातार अनुक्रम बिंदुओं के बीच निर्देश होते हैं। अनुक्रम अंक मानक के एनेक्स सी में परिभाषित किए गए i=i++हैं, 2 अनुक्रम बिंदु हैं जो एक पूर्ण-अभिव्यक्ति का परिसीमन करते हैं। ऐसी अभिव्यक्ति वाक्य-रचना के समान है expression-statement, जो व्याकरण के बैकस-नौर रूप में एक प्रविष्टि है (मानक के अनुलग्नक A में एक व्याकरण प्रदान किया गया है)।

तो एक बॉक्स के अंदर निर्देशों के आदेश का कोई स्पष्ट आदेश नहीं है।

i=i++

के रूप में व्याख्या की जा सकती है

tmp = i
i=i+1
i = tmp

या के रूप में

tmp = i
i = tmp
i=i+1

क्योंकि कोड की व्याख्या करने के लिए ये सभी रूप i=i++मान्य हैं और क्योंकि दोनों अलग-अलग उत्तर देते हैं, इसलिए व्यवहार अपरिभाषित है।

तो एक अनुक्रम बिंदु को प्रत्येक बॉक्स की शुरुआत और अंत तक देखा जा सकता है जो प्रोग्राम को बनाता है [बक्से सी में परमाणु इकाइयाँ हैं] और एक बॉक्स के अंदर निर्देशों का क्रम सभी मामलों में परिभाषित नहीं है। उस आदेश को बदलने से कभी-कभी परिणाम बदल सकता है।

संपादित करें:

इस तरह की अस्पष्टताओं को समझाने के लिए अन्य अच्छा स्रोत हैं c-faq साइट (यह भी एक पुस्तक के रूप में प्रकाशित ) से प्रविष्टियां हैं , अर्थात् यहां और यहां और यहां


इस उत्तर ने मौजूदा उत्तरों में नया कैसे जोड़ा? इसके लिए स्पष्टीकरण i=i++भी इस उत्तर के समान है ।
हॉक

@ झटके मैंने अन्य उत्तरों को नहीं पढ़ा। मैं अपनी स्वयं की भाषा में यह बताना चाहता था कि मैंने आईएसओ 9899 की आधिकारिक साइट से क्या-क्या उल्लेख किया है, यह खुला
std.org/jtc1/sc22/wg14/www/docs/n1188.pdf

5

आपका सवाल शायद नहीं था, "ये निर्माण सी में अपरिभाषित व्यवहार क्यों हैं?"। आपका प्रश्न संभवतः था, "इस कोड ( ++मुझे उपयोग करके ) ने मुझे वह मूल्य क्यों नहीं दिया जिसकी मुझे उम्मीद थी?", और किसी ने आपके प्रश्न को एक डुप्लिकेट के रूप में चिह्नित किया, और आपको यहां भेजा।

यह उत्तर उस प्रश्न का उत्तर देने की कोशिश करता है: आपके कोड ने आपको वह उत्तर क्यों नहीं दिया जिसकी आप अपेक्षा करते थे, और आप उन अभिव्यक्तियों को कैसे पहचान सकते हैं (और बच सकते हैं) जो अपेक्षा के अनुरूप काम नहीं करेंगी।

मुझे लगता है कि आपने अब तक सी ++और --ऑपरेटरों की मूल परिभाषा सुनी है , और उपसर्ग फॉर्म ++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. यदि दो या अधिक भिन्न स्थानों में एक चर है जिसे संशोधित (असाइन) किया जा रहा है, तो आप कैसे जानते हैं कि कौन सा संशोधन पहले होता है?
  2. यदि एक चर है जो एक स्थान पर संशोधित हो रहा है, और उसका मूल्य दूसरी जगह उपयोग किया जा रहा है, तो आप कैसे जानते हैं कि यह पुराने मूल्य का उपयोग करता है या नए मूल्य का?

# 1 के उदाहरण के रूप में, अभिव्यक्ति में

x = x++ + ++x;

`x को संशोधित करने के तीन प्रयास हैं।

# 2 के उदाहरण के रूप में, अभिव्यक्ति में

y = x + x++;

हम दोनों के मूल्य का उपयोग करते हैं x, और इसे संशोधित करते हैं।

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


3

कारण यह है कि कार्यक्रम अपरिभाषित व्यवहार चला रहा है। समस्या मूल्यांकन क्रम में है, क्योंकि C ++ 98 मानक के अनुसार कोई अनुक्रम बिंदु आवश्यक नहीं है (कोई भी संचालन C ++ 11 शब्दावली के अनुसार एक या पहले या बाद में अनुक्रमित नहीं है)।

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

  • तो पहले GCC: Nuwen MinGW 15 GCC 7.1 का उपयोग करने पर आपको मिलेगा:

    #include<stdio.h>
    int main(int argc, char ** argv)
    {
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 2
    
    i = 1;
    i = (i++);
    printf("%d\n", i); //1
    
    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 2
    
    u = 1;
    u = (u++);
    printf("%d\n", u); //1
    
    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); //2

    }

जीसीसी कैसे काम करता है? यह दाएं हाथ की ओर (आरएचएस) के लिए बाएं से दाएं क्रम में उप अभिव्यक्तियों का मूल्यांकन करता है, फिर बाएं हाथ की तरफ (एलएचएस) के लिए मान प्रदान करता है। यह ठीक उसी तरह है जैसे जावा और सी # अपने मानकों को व्यवहार और परिभाषित करते हैं। (हां, जावा और C # में समतुल्य सॉफ्टवेयर ने परिभाषित व्यवहार किया है)। यह बाएं से दाएं क्रम में आरएचएस स्टेटमेंट में एक-एक करके प्रत्येक उप अभिव्यक्ति का मूल्यांकन करता है; प्रत्येक उप अभिव्यक्ति के लिए: ++ c (प्री-इन्क्रीमेंट) का मूल्यांकन पहले किया जाता है फिर ऑपरेशन के लिए वैल्यू c का उपयोग किया जाता है, फिर पोस्ट इंक्रीमेंट c ++)।

GCC C ++ के अनुसार : ऑपरेटर्स

जीसीसी सी ++ में, ऑपरेटरों की पूर्वता उस क्रम को नियंत्रित करती है जिसमें व्यक्तिगत ऑपरेटरों का मूल्यांकन किया जाता है

परिभाषित व्यवहार C ++ में समान कोड GCC समझता है:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    //i = i++ + ++i;
    int r;
    r=i;
    i++;
    ++i;
    r+=i;
    i=r;
    printf("%d\n", i); // 2

    i = 1;
    //i = (i++);
    r=i;
    i++;
    i=r;
    printf("%d\n", i); // 1

    volatile int u = 0;
    //u = u++ + ++u;
    r=u;
    u++;
    ++u;
    r+=u;
    u=r;
    printf("%d\n", u); // 2

    u = 1;
    //u = (u++);
    r=u;
    u++;
    u=r;
    printf("%d\n", u); // 1

    register int v = 0;
    //v = v++ + ++v;
    r=v;
    v++;
    ++v;
    r+=v;
    v=r;
    printf("%d\n", v); //2
}

फिर हम Visual Studio पर जाते हैं । दृश्य स्टूडियो 2015, आपको मिलता है:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 3

    i = 1;
    i = (i++);
    printf("%d\n", i); // 2 

    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 3

    u = 1;
    u = (u++);
    printf("%d\n", u); // 2 

    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); // 3 
}

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

तो परिभाषित C ++ में विज़ुअल C ++ के समान व्यवहार समझ में आता है:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int r;
    int i = 0;
    //i = i++ + ++i;
    ++i;
    r = i + i;
    i = r;
    i++;
    printf("%d\n", i); // 3

    i = 1;
    //i = (i++);
    r = i;
    i = r;
    i++;
    printf("%d\n", i); // 2 

    volatile int u = 0;
    //u = u++ + ++u;
    ++u;
    r = u + u;
    u = r;
    u++;
    printf("%d\n", u); // 3

    u = 1;
    //u = (u++);
    r = u;
    u = r;
    u++;
    printf("%d\n", u); // 2 

    register int v = 0;
    //v = v++ + ++v;
    ++v;
    r = v + v;
    v = r;
    v++;
    printf("%d\n", v); // 3 
}

विजुअल स्टूडियो प्रलेखन के रूप में मूल्यांकन और मूल्यांकन के आदेश में कहा गया है :

जहां कई ऑपरेटर एक साथ दिखाई देते हैं, उनके पास समान समानता होती है और उनका संघटन के अनुसार मूल्यांकन किया जाता है। तालिका में ऑपरेटरों को पोस्टफ़िक्स ऑपरेटर्स के साथ शुरू होने वाले अनुभागों में वर्णित किया गया है।


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

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