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




cil compound-assignment (6)

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

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.

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

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

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

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??????

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


यह उबलता है: क्या मूल k या पहले मूल्य पर गणना की गई बहुत पहले += लागू होती है?

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

तो सबसे बाईं ओर += 10 += 70 को निष्पादित कर रहा है।


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

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

दो c s और दो k s हैं

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; लिए अलग है k += c; c += k; k += c; k += c; c += k; k += c; और इसलिए आपको अलग-अलग उत्तर मिलते हैं।

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


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

k += c += k += c;

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

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

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

k =  k +  (c = c +  (k = k  + c));
     10       30       10  30   // operand evaluation order is from left to right
      |         |            
      |            40  10 + 30   // operator evaluation
         70  30 + 40
80  10 + 70

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

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

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

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)
मूल्यांकन सी (30)
मूल्यांकन k (10)
मूल्यांकन सी (30)
40 को के
70 को सौंपा
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;
}

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

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

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







compound-assignment