क्या इनलाइन ऑपरेटरों के लिए "k + = c + = k + = c?" का स्पष्टीकरण है?


89

निम्नलिखित ऑपरेशन से परिणाम के लिए स्पष्टीकरण क्या है?

k += c += k += c;

मैं निम्नलिखित कोड से आउटपुट परिणाम को समझने की कोशिश कर रहा था:

int k = 10;
int c = 30;
k += c += k += c;
//k=80 instead of 110
//c=70

और वर्तमान में मैं यह समझने में संघर्ष कर रहा हूं कि "k" का परिणाम 80 क्यों है। क्यों k = 40 असाइन नहीं किया जा रहा है (वास्तव में Visual Studio मुझे बताता है कि उस मूल्य का कहीं और उपयोग नहीं किया जा रहा है)?

K 80 और 110 क्यों नहीं है?

यदि मैं ऑपरेशन को निम्न में विभाजित करता हूं:

k+=c;
c+=k;
k+=c;

परिणाम k = 110 है।

मैं CIL के माध्यम से देखने की कोशिश कर रहा था , लेकिन CIL की व्याख्या करने में मैं इतना गहरा नहीं हूँ और कुछ विवरण प्राप्त नहीं कर सकता हूँ:

 // [11 13 - 11 24]
IL_0001: ldc.i4.s     10
IL_0003: stloc.0      // k

// [12 13 - 12 24]
IL_0004: ldc.i4.s     30
IL_0006: stloc.1      // c

// [13 13 - 13 30]
IL_0007: ldloc.0      // k expect to be 10
IL_0008: ldloc.1      // c
IL_0009: ldloc.0      // k why do we need the second load?
IL_000a: ldloc.1      // c
IL_000b: add          // I expect it to be 40
IL_000c: dup          // What for?
IL_000d: stloc.0      // k - expected to be 40
IL_000e: add
IL_000f: dup          // I presume the "magic" happens here
IL_0010: stloc.1      // c = 70
IL_0011: add
IL_0012: stloc.0      // k = 80??????

3
आपको भिन्न परिणाम मिले क्योंकि आपने फ़ंक्शन को विभाजित किया है, k + = c + = k + = c = 80 क्योंकि k और c का मान सभी राशियों में समान है, इसलिए k + = c + = k + = c बराबर है 10 + 30 + 10 + 30 तक
जोओ पाउलो अमोरिम

78
दिलचस्प अभ्यास, लेकिन, व्यवहार में, कभी भी उस तरह से कोडिंग नहीं लिखना चाहिए जब तक कि आप अपने सहकर्मियों को आपसे नफरत नहीं करना चाहते। :)
UnhandledExcepSean

3
@AndriiKotliarov क्योंकि k + = c + = + k + = c 10 + 30 + 10 + 30 है, इसलिए, K को सभी मान प्राप्त हैं, और C को केवल अंतिम 3 तर्क 30 + 10 + 30 = 70
जोय पॉलो अमोरिम

6
इसके अलावा लायक पढ़ने - एरिक Lippert के जवाब के लिए क्या मैं के बीच का अंतर ++ और ++ मैं है?
वाई हा ली

34
"डॉक्टर, डॉक्टर, ऐसा करने पर मुझे दर्द होता है!" "तो ऐसा मत करो।"
डेविड कॉनरेड

जवाबों:


104

जैसा एक ऑपरेशन के a op= b;बराबर है a = a op b;। एक असाइनमेंट को स्टेटमेंट या एक्सप्रेशन के रूप में इस्तेमाल किया जा सकता है, जबकि एक्सप्रेशन के रूप में यह असाइन किए गए मान को प्राप्त करता है। तुम्हारा बयान ...

k += c += k += c;

..., चूंकि असाइनमेंट ऑपरेटर सही-सहयोगी है, इसलिए इसे भी लिखा जाना चाहिए

k += (c += (k += c));

या (विस्तारित)

k =  k +  (c = c +  (k = k  + c));
     10301030   // operand evaluation order is from left to right
      |         |        ↓    ↓
      |         ↓   4010 + 30   // operator evaluation7030 + 40
8010 + 70

जहां पूरे मूल्यांकन के दौरान शामिल चर के पुराने मूल्यों का उपयोग किया जाता है। इस के मूल्य के लिए विशेष रूप से सच है k(नीचे आईएल की मेरी समीक्षा देखें और वाई हा ली द्वारा प्रदान की गई लिंक )। इसलिए, आपको 70 + 40 (नया मूल्य k) = 110 नहीं मिल रहा है , बल्कि 70 + 10 (पुराना मूल्य k) = 80 है।

मुद्दा यह है कि (सी # कल्पना के अनुसार ) "एक अभिव्यक्ति में संचालन का मूल्यांकन बाएं से दाएं किया जाता है" (ऑपरेंड चर हैं cऔर kहमारे मामले में)। यह ऑपरेटर की पूर्ववर्तीता और सहानुभूति से स्वतंत्र है जो इस मामले में एक निष्पादन आदेश को दाएं से बाएं ओर निर्देशित करता है। ( इस पेज पर एरिक लिपर्ट के जवाब के लिए टिप्पणियां देखें )।


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

IL_0007: ldloc.0      // k (is 10)
IL_0008: ldloc.1      // c (is 30)
IL_0009: ldloc.0      // k (is 10)
IL_000a: ldloc.1      // c (is 30)

स्टैक अब इस तरह दिखता है (बाएं से दाएं; स्टैक के ऊपर दाएं)

१० ३० १० ३०

IL_000b: add          // pops the 2 top (right) positions, adds them and pushes the sum back

१० ३० ४०

IL_000c: dup

१० ३० ४० ४०

IL_000d: stloc.0      // k <-- 40

१० ३० ४०

IL_000e: add

१० 70०

IL_000f: dup

१० 70० 70०

IL_0010: stloc.1      // c <-- 70

१० 70०

IL_0011: add

80

IL_0012: stloc.0      // k <-- 80

ध्यान दें कि IL_000c: dup, IL_000d: stloc.0अर्थात करने के लिए पहला काम k , दूर अनुकूलित किया जा सकता है। आईएल को मशीन कोड में परिवर्तित करते समय संभवतः यह घबराहट के लिए चर के लिए किया जाता है।

यह भी ध्यान दें कि गणना द्वारा आवश्यक सभी मान या तो असाइनमेंट किए जाने से पहले स्टैक पर धकेल दिए जाते हैं या इन मानों से गणना की जाती है। stlocइस मूल्यांकन के दौरान असाइन किए गए मान (द्वारा ) का फिर से उपयोग नहीं किया जाता है। stlocढेर के ऊपर चबूतरे।


निम्नलिखित कंसोल परीक्षण का आउटपुट है ( Releaseपर अनुकूलन के साथ मोड)

k (10) का
मूल्यांकन करना c (30) का
मूल्यांकन करना k (10) का
मूल्यांकन करना c (30) का मूल्यांकन c (30)
40 को सौंपा गया k को
70 को
सौंपा गया k को 80 को सौंपा गया

private static int _k = 10;
public static int k
{
    get { Console.WriteLine($"evaluating k ({_k})"); return _k; }
    set { Console.WriteLine($"{value} assigned to k"); _k = value; }
}

private static int _c = 30;
public static int c
{
    get { Console.WriteLine($"evaluating c ({_c})"); return _c; }
    set { Console.WriteLine($"{value} assigned to c"); _c = value; }
}

public static void Test()
{
    k += c += k += c;
}

आप फॉर्मूले में संख्याओं के साथ अंतिम परिणाम को और अधिक पूर्ण के लिए जोड़ सकते हैं: अंतिम है k = 10 + (30 + (10 + 30)) = 80और cपहले कोष्ठक में अंतिम मान निर्धारित किया गया है c = 30 + (10 + 30) = 70
फ्रेंक

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

रिलीज़ मोड में एक कंसोल परीक्षण वास्तव में दिखाता है कि kयह संपत्ति होने पर दो बार असाइन किया गया है।
ओलिवियर जैकोट-डेस्कोब्स

26

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

int k = 10;
int c = 30;
k += c += k += c;

और फिर आप गलत तरीके से निष्कर्ष निकालते हैं कि इस बयान के सेट के समान परिणाम देना चाहिए:

int k = 10;
int c = 30;
k += c;
c += k;
k += c;

यह देखना जानकारीपूर्ण है कि आपको वह गलत कैसे मिला, और इसे सही कैसे करना है। इसे तोड़ने का सही तरीका इस तरह है।

सबसे पहले, सबसे बाहरी = लिखो

k = k + (c += k += c);

दूसरा, सबसे बाहरी + को फिर से लिखना। मुझे आशा है कि आप सहमत होंगे कि x = y + z को हमेशा "एक अस्थायी के लिए मूल्यांकन y, एक अस्थायी के लिए z का मूल्यांकन, अस्थायी का योग, x का योग असाइन करें" के समान होना चाहिए । तो चलिए इसे बहुत स्पष्ट करते हैं:

int t1 = k;
int t2 = (c += k += c);
k = t1 + t2;

सुनिश्चित करें कि स्पष्ट है, क्योंकि यह वह कदम है जो आपको गलत लगा । जटिल ऑपरेशन को सरल ऑपरेशन में तोड़ते समय, आपको यह सुनिश्चित करना होगा कि आप धीरे-धीरे और सावधानी से करें और कदमों को छोड़ें नहीं । कदम उठाना वह है जहाँ हम गलतियाँ करते हैं।

ठीक है, अब धीरे-धीरे और ध्यान से, t2 को असाइनमेंट को तोड़ दें।

int t1 = k;
int t2 = (c = c + (k += c));
k = t1 + t2;

असाइनमेंट t2 को वैसा ही मान देगा जैसा कि c को असाइन किया गया है, तो चलिए बताते हैं कि:

int t1 = k;
int t2 = c + (k += c);
c = t2;
k = t1 + t2;

महान। अब दूसरी पंक्ति को तोड़ें:

int t1 = k;
int t3 = c;
int t4 = (k += c);
int t2 = t3 + t4;
c = t2;
k = t1 + t2;

महान, हम प्रगति कर रहे हैं। T4 को असाइनमेंट तोड़ें:

int t1 = k;
int t3 = c;
int t4 = (k = k + c);
int t2 = t3 + t4;
c = t2;
k = t1 + t2;

अब तीसरी पंक्ति को तोड़ें:

int t1 = k;
int t3 = c;
int t4 = k + c;
k = t4;
int t2 = t3 + t4;
c = t2;
k = t1 + t2;

और अब हम पूरी बात देख सकते हैं:

int k = 10;  // 10
int c = 30;  // 30
int t1 = k;  // 10
int t3 = c;  // 30
int t4 = k + c; // 40
k = t4;         // 40
int t2 = t3 + t4; // 70
c = t2;           // 70
k = t1 + t2;      // 80

तो जब हम किया जाता है, k 80 है और c 70 है।

अब आइए देखें कि यह आईएल में कैसे लागू किया जाता है:

int t1 = k;
int t3 = c;  
  is implemented as
ldloc.0      // stack slot 1 is t1
ldloc.1      // stack slot 2 is t3

अब यह थोड़ा मुश्किल है:

int t4 = k + c; 
k = t4;         
  is implemented as
ldloc.0      // load k
ldloc.1      // load c
add          // sum them to stack slot 3
dup          // t4 is stack slot 3, and is now equal to the sum
stloc.0      // k is now also equal to the sum

हम उपरोक्त को लागू कर सकते थे

ldloc.0      // load k
ldloc.1      // load c
add          // sum them
stloc.0      // k is now equal to the sum
ldloc.0      // t4 is now equal to k

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

अब हमें सी करने के लिए एक ही चाल करनी है:

int t2 = t3 + t4; // 70
c = t2;           // 70
  is implemented as:
add          // t3 and t4 are the top of the stack.
dup          
stloc.1      // again, we do the dup trick to get the sum in 
             // both c and t2, which is stack slot 2.

और अंत में:

k = t1 + t2;
  is implemented as
add          // stack slots 1 and 2 are t1 and t2.
stloc.0      // Store the sum to k.

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

कहानी का नैतिक है: जब आप एक जटिल कार्यक्रम को समझने की कोशिश कर रहे हैं, तो हमेशा एक समय में एक ऑपरेशन को तोड़ दें । छोटे कटौती मत करो; वे तुम्हें भटका देंगे।


3
@ ओलिवियरजैकॉट-डेस्कोम्स: युक्ति की प्रासंगिक पंक्ति "ऑपरेटर्स" में है और कहते हैं "अभिव्यक्ति में ऑपरेशंस का मूल्यांकन बाएं से दाएं किया जाता है। उदाहरण के लिए F(i) + G(i++) * H(i), विधि F को i के पुराने मान का उपयोग करके कहा जाता है, फिर विधि। जी को i के पुराने मूल्य के साथ कहा जाता है, और, अंत में, विधि H को i के नए मूल्य के साथ कहा जाता है । यह अलग है और असंबंधित से ऑपरेटर पूर्वता है। " (जोर देकर कहा।) तो मुझे लगता है कि मैं गलत था जब मैंने कहा कि कहीं नहीं है कि "पुराने मूल्य का उपयोग किया जाता है" तब होता है! यह एक उदाहरण में होता है। लेकिन मानक बिट "बाएं से दाएं" है।
एरिक लिपर्ट

1
यह गायब कड़ी थी। कथन यह है कि हमें ऑपरेंड मूल्यांकन आदेश और ऑपरेटर पूर्वता के बीच अंतर करना चाहिए । ऑपरेंड मूल्यांकन बाएं से दाएं और ओपी के मामले में ऑपरेटर निष्पादन दाएं से बाएं ओर जाता है।
ओलिवियर जैकोट-डेसकोम्बेस

4
@ ओलिवियरजैकॉट-डेसकॉम्ब: यह बिल्कुल सही है। वरीयता और सहकारिता का उस स्थिति से कोई लेना-देना नहीं है, जिसमें उप-अनुक्रमों का मूल्यांकन किया जाता है, इस तथ्य से इतर कि पूर्वता और सहानुभूति यह निर्धारित करती है कि उप-संप्रत्यय सीमाएँ कहाँ हैं । Subexpressions का मूल्यांकन बाएं से दाएं किया जाता है।
एरिक लिपर्ट

1
Ooops ऐसा लगता है कि आप असाइनमेंट ऑपरेटरों को अधिभार नहीं दे सकते: /
जॉनी 5

1
@ johnny5: यह सही है। लेकिन आप ओवरलोड कर सकते हैं +, और फिर आपको +=मुफ्त में मिलेगा क्योंकि केवल एक बार मूल्यांकन किए जाने के अलावा x += yपरिभाषित किया गया है। यह परवाह किए बिना सच है कि क्या उपयोगकर्ता द्वारा परिभाषित या बनाया गया है। तो: एक संदर्भ प्रकार पर ओवरलोडिंग का प्रयास करें और देखें कि क्या होता है। x = x + yx++
एरिक लिपर्ट

14

यह उबलता है: क्या पहले +=मूल kया उस मूल्य पर लागू किया गया है जो सही पर अधिक गणना की गई थी?

इसका उत्तर यह है कि यद्यपि असाइनमेंट दाएं से बाएं ओर बंधे हैं, फिर भी ऑपरेशन बाएं से दाएं आगे बढ़ते हैं।

इसलिए सबसे बाईं ओर अमल +=है 10 += 70


1
यह इसे अखरोट के खोल में अच्छी तरह से डालता है।
अगंजू

इसके वास्तव में ऑपरेंड जिनका मूल्यांकन बाएं से दाएं किया जाता है।
ओलिवियर जैकोट-डेसकॉम्ब्स

0

मैंने gcc और pgcc के साथ उदाहरण की कोशिश की और 110 प्राप्त किए। मैंने IR की जाँच की जो उन्होंने उत्पन्न की, और कंपाइलर ने एक्सप का विस्तार किया:

k = 10;
c = 30;
k = c+k;
c = c+k;
k = c+k;

जो मुझे उचित लगता है।


-1

इस तरह के चेन असाइनमेंट के लिए, आपको सबसे दाईं ओर शुरू करने से मान असाइन करना होगा। आपको इसे बाईं ओर असाइन करना और उसकी गणना करना है, और अंतिम (बायीं ओर असाइनमेंट) के लिए इस रास्ते पर जाना है, निश्चित रूप से इसकी गणना k = 80 के रूप में की जाती है।


कृपया उन उत्तरों को पोस्ट न करें जो बस फिर से राज्य करते हैं जो पहले से ही कई अन्य उत्तर देते हैं।
एरिक लिपर्ट

-1

सरल उत्तर: मानों के साथ var को बदलें und जो आपको मिल गया है:

int k = 10;
int c = 30;
k += c += k += c;
10 += 30 += 10 += 30
= 10 + 30 + 10 + 30
= 80 !!!

यह उत्तर गलत है। यद्यपि यह तकनीक इस विशिष्ट मामले में काम करती है, लेकिन एल्गोरिथ्म सामान्य रूप से काम नहीं करता है। उदाहरण के लिए, k = 10; m = (k += k) + k;मतलब नहीं है m = (10 + 10) + 10उत्परिवर्तन अभिव्यक्तियों के साथ भाषाओं का विश्लेषण नहीं किया जा सकता है, क्योंकि उनके पास उत्सुक मूल्य प्रतिस्थापन है । मूल्य प्रतिस्थापन एक विशेष क्रम में उत्परिवर्तन के संबंध में होता है और आपको इसे ध्यान में रखना चाहिए।
एरिक लिपर्ट

-1

आप इसे गिनकर हल कर सकते हैं।

a = k += c += k += c

दो cs और दो ks तो हैं

a = 2c + 2k

और, भाषा के संचालकों के परिणामस्वरूप, k यह भी बराबर है2c + 2k

यह श्रृंखला की इस शैली में चर के किसी भी संयोजन के लिए काम करेगा:

a = r += r += r += m += n += m

इसलिए

a = 2m + n + 3r

तथा r बराबर होगा।

आप केवल उनके सबसे बाएं असाइनमेंट तक की गणना करके अन्य नंबरों के मूल्यों का पता लगा सकते हैं। तो mबराबरी 2m + nऔर nबराबरी n + m

यह दर्शाता है कि k += c += k += c;अलग है k += c; c += k; k += c;और इसलिए आपको अलग-अलग उत्तर मिलते हैं।

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


इस सवाल का जवाब नहीं है
जॉनी 5

@ johnny5 यह बताता है कि आपको परिणाम क्यों मिलता है, अर्थात क्योंकि गणित कैसे काम करता है।
मैट एलेन

2
गणित और संचालन के आदेश जो एक संकलक एक बयान को विकसित करता है, दो अलग-अलग चीजें हैं। आपके तर्क के तहत k + = c; सी + = के; k + = c को उसी परिणाम का मूल्यांकन करना चाहिए।
जॉनी 5

नहीं, जॉनी 5, इसका मतलब यह नहीं है। गणितीय रूप से वे अलग-अलग चीजें हैं। तीन अलग-अलग ऑपरेशन 3c + 2k का मूल्यांकन करते हैं।
मैट एलेन

2
दुर्भाग्य से आपका "बीजगणितीय" समाधान केवल संयोग से सही है। आपकी तकनीक सामान्य रूप से काम नहीं करती है । गौर कीजिए x = 1;और y = (x += x) + x;क्या यह आपका तर्क है कि "तीन x हैं और इसलिए y बराबर है 3 * x"? क्योंकि इस मामले में yबराबर है 4। अब इस बारे y = x + (x += x);में आपका क्या कहना है कि बीजीय कानून "a + b = b + a" पूरा हुआ है और यह भी 4 है? क्योंकि यह 3. है। दुर्भाग्य से, सी # हाई स्कूल के बीजगणित के नियमों का पालन नहीं करता है अगर अभिव्यक्ति में साइड इफेक्ट होते हैं । C # बीजगणित को प्रभावित करने वाले एक पक्ष के नियमों का पालन करता है।
एरिक लिपर्ट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.