2008-10-11 10 views

उत्तर

42

call गैर वर्चुअल, स्थैतिक, या सुपरक्लास विधियों को कॉल करने के लिए है, यानी, कॉल का लक्ष्य ओवरराइडिंग के अधीन नहीं है। callvirt वर्चुअल विधियों को कॉल करने के लिए है (ताकि this एक उप-वर्ग है जो विधि को ओवरराइड करता है, इसके बजाय उप-वर्ग संस्करण को कॉल किया जाता है)।

+25

अगर मुझे सही ढंग से याद है कि 'कॉल' कॉल करने से पहले शून्य के खिलाफ पॉइंटर की जांच नहीं करता है, तो कुछ 'कॉलवर्ट' की स्पष्ट रूप से आवश्यकता होती है। यही कारण है कि गैर-आभासी तरीकों को कॉल करने के बावजूद 'कॉलवर्ट' कभी-कभी संकलक द्वारा उत्सर्जित होता है। – dalle

+1

आह। यह इंगित करने के लिए धन्यवाद कि (मैं नहीं हूं। नेट व्यक्ति)। मेरे द्वारा उपयोग किए जाने वाले समरूपता jVM बाइटकोड में कॉल => invokespecial, और callvirt => invokevirtual हैं। JVM के मामले में, दोनों निर्देश शून्यता के लिए "यह" जांचते हैं (मैंने अभी जांच करने के लिए एक परीक्षण कार्यक्रम लिखा है)। –

+2

आप अपने उत्तर में प्रदर्शन अंतर का उल्लेख करना चाह सकते हैं, जो कि 'कॉल' निर्देश रखने का कारण है। –

45

जब रनटाइम call निर्देश निष्पादित करता है तो यह कोड (विधि) के सटीक टुकड़े पर कॉल कर रहा है। यह कहां मौजूद है इसके बारे में कोई सवाल नहीं है। एक बार आईएल को जिट किया गया है, तो कॉल साइट पर परिणामी मशीन कोड एक बिना शर्त jmp निर्देश है।

इसके विपरीत, callvirt निर्देश का उपयोग पॉलीमोर्फिक तरीके से वर्चुअल विधियों को कॉल करने के लिए किया जाता है। विधि के कोड का सटीक स्थान प्रत्येक आमंत्रण के लिए रनटाइम पर निर्धारित किया जाना चाहिए। परिणामस्वरूप JITted कोड में vtable संरचनाओं के माध्यम से कुछ संकेत शामिल हैं। इसलिए कॉल निष्पादित करने के लिए धीमा है, लेकिन यह अधिक लचीला है कि यह polymorphic कॉल के लिए अनुमति देता है।

ध्यान दें कि संकलक वर्चुअल विधियों के लिए call निर्देशों को उत्सर्जित कर सकता है। उदाहरण के लिए:

SealedObject a = // ... 
object b = // ... 

bool equal = a.Equals(b); 

जबकि System.Object.Equals(object) एक आभासी तरीका है, इस प्रयोग में वहाँ Equals विधि की एक अधिभार के अस्तित्व के लिए कोई रास्ता नहीं है:

sealed class SealedObject : object 
{ 
    public override bool Equals(object o) 
    { 
     // ... 
    } 
} 

बुला कोड पर विचार करें। SealedObject एक सीलबंद वर्ग है और इसमें उप-वर्ग नहीं हो सकते हैं।

इस कारण से, .NET के sealed कक्षाओं में उनके गैर-मुहरबंद समकक्षों की तुलना में बेहतर तरीके से प्रेषण प्रदर्शन हो सकता है।

संपादित करें: बाहर निकलता है कि मैं गलत था। सी # कंपाइलर विधि के स्थान पर बिना शर्त कूद नहीं सकता है क्योंकि ऑब्जेक्ट का संदर्भ (विधि के भीतर this का मान) शून्य हो सकता है। इसके बजाय यह callvirt उत्सर्जित करता है जो शून्य की जांच करता है और यदि आवश्यक हो तो फेंकता है।

यह वास्तव में कुछ विचित्र कोड मैं नेट ढांचे में पाया परावर्तक का उपयोग कर बताते हैं:

if (this==null) // ... 

यह एक संकलक निरीक्षण कोड this सूचक के लिए एक शून्य मान है कि उत्सर्जन करने के लिए संभव है (local0), केवल सीएससी ऐसा नहीं करता है।

तो मुझे लगता है कि call केवल कक्षा स्थैतिक तरीकों और structs के लिए उपयोग किया जाता है।

इस जानकारी को देखते हुए अब मुझे लगता है कि sealed केवल एपीआई सुरक्षा के लिए उपयोगी है। मुझे another question मिला जो ऐसा लगता है कि आपकी कक्षाओं को सील करने के लिए कोई प्रदर्शन लाभ नहीं है।

संपादित करें 2: ऐसा लगता है कि इससे कहीं अधिक है।

new SealedObject().Equals("Rubber ducky"); 
जाहिर है इस तरह के एक मामले में

कोई संभावना वस्तु दृष्टान्त अशक्त हो सकता है कि यह है: उदाहरण के लिए निम्नलिखित कोड एक call अनुदेश उत्सर्जन करता है।

दिलचस्प है, एक डीबग बिल्ड में, निम्न कोड का उत्सर्जन करता है callvirt:

var o = new SealedObject(); 
o.Equals("Rubber ducky"); 

इसका कारण यह है कि आप दूसरी पंक्ति पर एक ब्रेकपाइंट सेट और o का मूल्य को संशोधित कर सकता है। रिलीज में मुझे लगता है कि कॉल callvirt की बजाय call होगा।

दुर्भाग्य से मेरा पीसी वर्तमान में कार्रवाई से बाहर है, लेकिन मैं इसे फिर से बार-बार प्रयोग कर दूंगा।

+2

प्रतिबिंब के माध्यम से उन्हें ढूंढते समय मुहरबंद विशेषताओं निश्चित रूप से तेज़ होते हैं, लेकिन इसके अलावा, मैं आपने जिन अन्य लाभों का उल्लेख नहीं किया है, उन्हें न जानें। – TraumaPony

11

इसी कारण से, .NET के मुहरबंद वर्गों में उनके गैर-मुहरबंद समकक्षों की तुलना में प्रदर्शन को बेहतर तरीके से प्रेषित किया जा सकता है।

दुर्भाग्यवश यह मामला नहीं है। Callvirt एक और चीज करता है जो इसे उपयोगी बनाता है। जब किसी ऑब्जेक्ट पर कॉल करने की विधि होती है तो कॉलवर्ट यह जांच करेगा कि ऑब्जेक्ट मौजूद है या नहीं, और यदि NullReferenceException फेंकता नहीं है। ऑब्जेक्ट संदर्भ वहां नहीं है, और उस स्थान पर बाइट निष्पादित करने का प्रयास करने पर भी कॉल मेमोरी लोकेशन पर जायेगा।

इसका क्या अर्थ है कि कॉलवर्ट हमेशा कक्षाओं के लिए सी # कंपाइलर (वीबी के बारे में निश्चित नहीं) द्वारा उपयोग किया जाता है, और कॉल हमेशा structs के लिए उपयोग किया जाता है (क्योंकि वे कभी भी शून्य या उपclassed नहीं हो सकता है)।

संपादित ड्रयू Noakes टिप्पणी के जवाब में:

public class SampleClass 
{ 
    public override bool Equals(object obj) 
    { 
     if (obj.ToString().Equals("Rubber Ducky", StringComparison.InvariantCultureIgnoreCase)) 
      return true; 

     return base.Equals(obj); 
    } 

    public void SomeOtherMethod() 
    { 
    } 

    static void Main(string[] args) 
    { 
     // This will emit a callvirt to System.Object.Equals 
     bool test1 = new SampleClass().Equals("Rubber Ducky"); 

     // This will emit a call to SampleClass.SomeOtherMethod 
     new SampleClass().SomeOtherMethod(); 

     // This will emit a callvirt to System.Object.Equals 
     SampleClass temp = new SampleClass(); 
     bool test2 = temp.Equals("Rubber Ducky"); 

     // This will emit a callvirt to SampleClass.SomeOtherMethod 
     temp.SomeOtherMethod(); 
    } 
} 

नोट: हाँ यह आप किसी भी वर्ग के लिए एक कॉल फेंकना संकलक प्राप्त कर सकते हैं, लेकिन केवल निम्नलिखित बहुत विशिष्ट मामले में लगता है काम करने के लिए वर्ग को सील नहीं करना पड़ता है।

तो ऐसा लगता है कि अगर इन सब बातों सत्य हैं की तरह संकलक एक फोन फेंकना होगा:

  • विधि कॉल ऑब्जेक्ट निर्माण
  • विधि एक आधार वर्ग में लागू नहीं किया गया है के बाद तुरंत
+0

यह समझ में आता है। मैंने सीआईएल का अध्ययन किया था और संकलक के व्यवहार के बारे में एक धारणा बनाई थी। इसे साफ़ करने के लिए धन्यवाद। मैं अपना जवाब अपडेट करूंगा। –

+1

वास्तव में मुझे विश्वास नहीं है कि आप यहां 100% सही हैं। मैंने अपनी पोस्ट अपडेट की है (2 संपादित करें)। –

+0

हाय कैमरून। क्या आप स्पष्ट कर सकते हैं कि इस कोड का विश्लेषण रिलीज बिल्ड पर किया गया था या नहीं? –

5

MSDN के अनुसार:

Call:

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

CallVirt:

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

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

कॉल: चर ->चर के प्रकार वस्तु -> विधि

CallVirt: चर -> वस्तु दृष्टान्त ->वस्तु की प्रकार वस्तु -> विधि

2

एक बात शायद पिछले जवाब करने के लिए जोड़ने के लायक है, वहाँ केवल एक ही है कि "आईएल कॉल" के लिए चेहरे हो रहा है वास्तव में कार्यान्वित करता है, +०१२३५१६४१०६१और "आईएल कॉलवर्ट" निष्पादित करने के लिए दो चेहरे।

इस नमूना सेटअप को ले लो।

public class Test { 
     public int Val; 
     public Test(int val) 
      { Val = val; } 
     public string FInst() // note: this==null throws before this point 
      { return this == null ? "NO VALUE" : "ACTUAL VALUE " + Val; } 
     public virtual string FVirt() 
      { return "ALWAYS AN ACTUAL VALUE " + Val; } 
    } 
    public static class TestExt { 
     public static string FExt (this Test pObj) // note: pObj==null passes 
      { return pObj == null ? "NO VALUE" : "VALUE " + pObj.Val; } 
    } 

पहले, FInst() और FEXT() की कोल इंडिया शरीर, 100% समान है opcode करने वाली opcode (सिवाय इसके कि एक "उदाहरण" घोषित कर दिया है और अन्य "स्थिर") - हालांकि, FInst() को "कॉल" के साथ "कॉलवर्ट" और FExt() के साथ बुलाया जाएगा।

दूसरा, FInst() और FVirt() दोनों "callvirt" साथ बुलाया जाएगा - भले ही एक आभासी है, लेकिन अन्य नहीं है - लेकिन यह "एक ही callvirt" है कि वास्तव में मिल जाएगा नहीं है निष्पादन हेतु।

यहाँ मोटे तौर पर JITting के बाद क्या होता है:

pObj.FExt(); // IL:call 
    mov   rcx, <pObj> 
    call  (direct-ptr-to) <TestExt.FExt> 

    pObj.FInst(); // IL:callvirt[instance] 
    mov   rax, <pObj> 
    cmp   byte ptr [rax],0 
    mov   rcx, <pObj> 
    call  (direct-ptr-to) <Test.FInst> 

    pObj.FVirt(); // IL:callvirt[virtual] 
    mov   rax, <pObj> 
    mov   rax, qword ptr [rax] 
    mov   rax, qword ptr [rax + NNN] 
    mov   rcx, <pObj> 
    call  qword ptr [rax + MMM] 

"कहते हैं" और "callvirt [उदाहरण]" के बीच फर्क सिर्फ इतना है कि "callvirt [उदाहरण]" जानबूझकर से पहले * pObj से एक बाइट का उपयोग करने की कोशिश करता है यह इंस्टेंस फ़ंक्शन के प्रत्यक्ष सूचक को कॉल करता है (संभवतः एक अपवाद फेंकने के लिए "वहां और फिर")।

इस प्रकार, आप बार आप

var d = GetDForABC (a, b, c); 
var e = d != null ? d.GetE() : ClassD.SOME_DEFAULT_E; 

की "जाँच हिस्सा" आप पुश नहीं कर सकता लिखने के लिए है की संख्या से नाराज कर रहे हैं "अगर (इस == नल) वापसी SOME_DEFAULT_E," ClassD.GetE() में नीचे (जैसा कि "आईएल कॉलवर्ट [इंस्टेंस]" सेमेटिक्स आपको ऐसा करने के लिए प्रतिबंधित करता है) लेकिन यदि आप स्थानांतरित करते हैं तो आप इसे GetE() में स्थानांतरित करने के लिए स्वतंत्र हैं। समारोह कहीं ("आईएल कॉल" के रूप में अर्थ यह अनुमति देता है - लेकिन अफसोस, निजी सदस्यों आदि पर पहुंच खो देना)

जिसके अनुसार, की "callvirt [उदाहरण]" निष्पादन "कहते हैं" के साथ आम में अधिक है "कॉलवर्ट [वर्चुअल]" के मुकाबले, क्योंकि बाद वाले को आपके फ़ंक्शन का पता ढूंढने के लिए एक तिहाई संकेतक निष्पादित करना पड़ सकता है। (typedef आधार को अविवेक, तो आधार-vtab या कुछ इंटरफ़ेस है, तो वास्तविक स्लॉट के लिए)

आशा इस मदद करता है, बोरिस

1

बस ऊपर जवाब में जोड़ने से, मुझे लगता है कि बदल गया है लंबे समय से ऐसा किया गया है कि सभी उदाहरण विधियों के लिए कॉलवर्ट आईएल निर्देश उत्पन्न होगा और कॉल आईएल निर्देश स्थिर तरीकों के लिए उत्पन्न होगा।

संदर्भ:

Pluralsight पाठ्यक्रम "सी # भाषा Internals - भाग 1 बार्ट De Smet द्वारा (वीडियो - कॉल निर्देश और संक्षेप में CLR आईएल में ढेर कहते हैं)

और भी https://blogs.msdn.microsoft.com/ericgu/2008/07/02/why-does-c-always-use-callvirt/

+0

'कॉल' भी बेस कॉल के लिए उपयोग किया जाता है। साथ ही, मेरा मानना ​​है कि रोसलीन कंपाइलर सीलबंद/गैर-वर्चुअल विधियों के लिए 'कॉल' निर्देश उत्पन्न करेगा यदि यह मामूली रूप से निर्धारित कर सकता है कि ऑब्जेक्ट कभी शून्य नहीं होगा (उदाहरण के लिए [शून्य सशर्त ऑपरेटर से उत्पन्न कॉल] (http: // stackoverflow.com/questions/34535000/call-instead-of-callvirt-in-case-of-the-new-c-sharp-6-null-check))। –

संबंधित मुद्दे