2010-12-16 9 views
6

निम्नलिखित कोड में, यह किसी व्युत्पन्न ऑब्जेक्ट पर पॉइंटर के माध्यम से वर्चुअल फ़ंक्शन foo को कॉल करता है। क्या यह कॉल vtable के माध्यम से जाएगा या क्या यह सीधे B::foo पर कॉल करेगा?क्या व्युत्पन्न वस्तु पर सी ++ आभासी फ़ंक्शन कॉल Vtable के माध्यम से जाता है?

यदि यह एक vtable के माध्यम से जाता है, तो इसे B::foo पर कॉल करने का सी ++ बेवकूफ तरीका क्या होगा? मुझे पता है कि इस मामले में मैं हमेशा B पर इशारा करता हूं।

Class A 
{ 
    public: 
     virtual void foo() {} 
}; 

class B : public A 
{ 
    public: 
     virtual void foo() {} 
}; 


int main() 
{ 
    B* b = new B(); 
    b->foo(); 
} 
+1

क्या आप अनुकूलित करने की कोशिश कर रहे हैं (अपना समय कम्पाइलर नौकरी बर्बाद न करें)। या आप एक तकनीक चाहते हैं कि बस बी के संस्करण को foo() के कॉल करें? –

+3

आपको वास्तव में चिंता नहीं करनी चाहिए कि प्रेषण प्रत्यक्ष होगा या एक vtable के माध्यम से जाना होगा। अधिकांश परिदृश्यों में, वर्चुअल विधि तालिका प्रेषण का प्रदर्शन पर कभी भी महत्वपूर्ण प्रभाव नहीं पड़ता है। –

उत्तर

3

हां, यह vtable (केवल गैर वर्चुअल विधियों vtable को बाईपास) का उपयोग करेगा। पर b पर कॉल करने के लिए, b->B::foo() पर कॉल करें।

+3

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

+2

@ बेन वोगेट हां, यह बहुत संभव है। मुझे लगता है कि मूल पोस्टर जिस कोड को देख रहा है वह बहुत जटिल है, और यह मामला नहीं हो सकता है। – robert

+0

धन्यवाद, यह वाक्यविन्यास वह है जो मैं याद कर रहा था। मैं अनुकूलन आदि से संबंधित अन्य मुद्दों से अवगत हूं। – aaa

9

अधिकांश कंप्यूटर्स उस परिदृश्य में अप्रत्यक्ष कॉल को खत्म करने के लिए पर्याप्त स्मार्ट होंगे, अगर आपके पास ऑप्टिमाइज़ेशन सक्षम है। लेकिन केवल इसलिए कि आपने अभी ऑब्जेक्ट बनाया है और संकलक गतिशील प्रकार जानता है; ऐसी स्थिति हो सकती है जब आप गतिशील प्रकार और कंपाइलर नहीं जानते हैं।

+0

किस मामले में आप गतिशील प्रकार को ज्ञात करने के लिए 'static_cast' का उपयोग कर सकते हैं ..... –

+3

@ बिली: मुझे इतना यकीन नहीं है। 'static_cast' बस संकलक को बताता है कि गतिशील प्रकार 'बी' का एक (गैर-सख्त) उप-वर्ग है, न कि यह वास्तव में' बी' है, इसलिए अनुकूलन आईएमओ लागू नहीं करता है। –

+0

@ बिली: संकलक को आजमाएं और मूर्ख क्यों करें। संकलक पहले से ही कोड के बारे में एक इंसान से अधिक जानता है और इसे अनुकूलित कैसे करें। बस इसे अपना काम करने दें। –

6

सामान्य रूप से, इस प्रश्न का उत्तर "यदि यह आपके लिए महत्वपूर्ण है, तो उत्सर्जित कोड पर नज़र डालें"। जी ++ कोई ऑप्टिमाइज़ेशन के साथ उत्पन्न होता है:

18  b->foo(); 
0x401375 <main+49>: mov eax,DWORD PTR [esp+28] 
0x401379 <main+53>: mov eax,DWORD PTR [eax] 
0x40137b <main+55>: mov edx,DWORD PTR [eax] 
0x40137d <main+57>: mov eax,DWORD PTR [esp+28] 
0x401381 <main+61>: mov DWORD PTR [esp],eax 
0x401384 <main+64>: call edx 

जो vtable का उपयोग कर रहा है। एक सीधा कॉल, जैसे कोड द्वारा उत्पादित:

B b; 
b.foo(); 

इस तरह दिखता है:

0x401392 <main+78>: lea eax,[esp+24] 
0x401396 <main+82>: mov DWORD PTR [esp],eax 
0x401399 <main+85>: call 0x40b2d4 <_ZN1B3fooEv> 
+3

अब, अनुकूलन के साथ संकलित करें और कोड को फिर से देखें ... –

+0

@ डेविड वास्तव में। लेकिन कौन सा अनुकूलन? मेरा मुद्दा यह है कि ऑप्टिमाइज़ेशन का कुशलतापूर्वक उपयोग करने के लिए आपको कोड को देखने की आवश्यकता है। –

+1

@Unquiet: G ++, तो या तो -O2 या -O3 अनुकूलन। साथ ही, मैं कहूंगा कि मैंने "असेंबली को देखो" कहने वाले सभी लोगों से क्या कहा है - हम सभी को असेंबली नहीं पता है, और सभी को उच्च स्तर की भाषा का उपयोग करके मानना ​​है कि सी ++ जानता है कि यह एक तरह का अनुचित है। –

1

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

int main() {} 
+1

कंपाइलर को नए कॉल को हटाने की अनुमति नहीं है।इसका साइड इफेक्ट्स है कि कंपाइलर एनालेसे नहीं कर सकता क्योंकि यह स्मृति आवंटन के लिए अंतर्निहित लाइब्रेरी को कॉल करता है। –

4

इस के साथ जी से संकलित कोड ++ (4.5) है -O3

_ZN1B3fooEv: 
    rep 
    ret 

main: 
    subq $8, %rsp 
    movl $8, %edi 
    call _Znwm 
    movq $_ZTV1B+16, (%rax) 
    movq %rax, %rdi 
    call *_ZTV1B+16(%rip) 
    xorl %eax, %eax 
    addq $8, %rsp 
    ret 

_ZTV1B: 
    .quad 0 
    .quad _ZTI1B 
    .quad _ZN1B3fooEv 

यह एकमात्र अनुकूलन था कि यह पता था कि किस vtable का उपयोग करना है (बी ऑब्जेक्ट पर)। अन्यथा "कॉल * _ZTV1B + 16 (% रिप)" movq (% रैक्स),% रैक्स; कॉल * (% रैक्स) "होता। तो g ++ वर्चुअल फ़ंक्शन कॉल को अनुकूलित करने में वास्तव में काफी खराब है।

+0

जीसीसी 4.6 और बाद में '\t _ZN1B3fooEv' कॉल करें, इसलिए सफलतापूर्वक devirtualize और 'B :: foo()' सीधे (http://goo.gl/wxcSiw) –

+0

पर कॉल करें। मैं इसे भी देखता हूं। लेकिन यह फ़ंक्शन कॉल को दूर (इनलाइन) क्यों नहीं कर सकता है क्योंकि बी :: foo के विधि निकाय खाली है ... – Emil

+0

जीसीसी 4.7+ इनलाइन करता है, केवल 4.6 नहीं है (ध्यान दें कि मेरी पिछली टिप्पणी में लिंक सदस्य को परिभाषित नहीं किया, विशेष रूप से इसलिए इसे अनुकूलित नहीं किया जाएगा और कॉल दिखाया जाएगा) –

0

मैंने कोड को थोड़ा सा बदलने के लिए थोड़ा सा बदल दिया, और मेरे लिए ऐसा लगता है कि यह vtable को छोड़ रहा है, लेकिन मैं यह बताने के लिए पर्याप्त विशेषज्ञ नहीं हूं।

g++ -g -S -O0 -fverbose-asm virt.cpp 
as -alhnd virt.s > virt.base.asm 
g++ -g -S -O6 -fverbose-asm virt.cpp 
as -alhnd virt.s > virt.opt.asm 

और दिलचस्प बिट्स 'ऑप्ट' की तरह मेरे लिए देखने के संस्करण: मुझे यकीन है कि कुछ टिप्पणीकारों मुझे सेट हो जाएगा सही हालांकि :)

struct A { 
    virtual int foo() { return 1; } 
}; 

struct B : public A { 
    virtual int foo() { return 2; } 
}; 

int useIt(A* a) { 
    return a->foo(); 
} 

int main() 
{ 
    B* b = new B(); 
    return useIt(b); 
} 

मैं तो इस कोड को विधानसभा के लिए इस तरह परिवर्तित कर रहा हूँ vtable छोड़ रहा है। ऐसा लगता है कि यह vtable बनाने है, लेकिन इसे प्रयोग नहीं ..

ऑप्ट एएसएम में:

9:virt.cpp  **** int useIt(A* a) { 
89     .loc 1 9 0 
90     .cfi_startproc 
91    .LVL2: 
10:virt.cpp  ****  return a->foo(); 
92     .loc 1 10 0 
93 0000 488B07  movq (%rdi), %rax # a_1(D)->_vptr.A, a_1(D)->_vptr.A 
94 0003 488B00  movq (%rax), %rax # *D.2259_2, *D.2259_2 
95 0006 FFE0   jmp *%rax # *D.2259_2 
96    .LVL3: 
97     .cfi_endproc 

और आधार।उसी के एएसएम संस्करण:

9:virt.cpp  **** int useIt(A* a) { 
    88     .loc 1 9 0 
    89     .cfi_startproc 
    90 0000 55   pushq %rbp # 
    91    .LCFI6: 
    92     .cfi_def_cfa_offset 16 
    93     .cfi_offset 6, -16 
    94 0001 4889E5  movq %rsp, %rbp #, 
    95    .LCFI7: 
    96     .cfi_def_cfa_register 6 
    97 0004 4883EC10  subq $16, %rsp #, 
    98 0008 48897DF8  movq %rdi, -8(%rbp) # a, a 
    10:virt.cpp  ****  return a->foo(); 
    99     .loc 1 10 0 
100 000c 488B45F8  movq -8(%rbp), %rax # a, tmp64 
101 0010 488B00  movq (%rax), %rax # a_1(D)->_vptr.A, D.2263 
102 0013 488B00  movq (%rax), %rax # *D.2263_2, D.2264 
103 0016 488B55F8  movq -8(%rbp), %rdx # a, tmp65 
104 001a 4889D7  movq %rdx, %rdi # tmp65, 
105 001d FFD0   call *%rax # D.2264 
    11:virt.cpp  **** } 
106     .loc 1 11 0 
107 001f C9   leave 
108    .LCFI8: 
109     .cfi_def_cfa 7, 8 
110 0020 C3   ret 
111     .cfi_endproc 

लाइन 93 पर हम टिप्पणी में देखें: _vptr.A जो मैं यकीन है कि इसका मतलब यह वास्तविक मुख्य कार्य में एक vtable देखने कर रहा है, तथापि, कर रहा हूँ, यह करने में सक्षम हो रहा है जवाब का अनुमान है और यहां तक ​​कि कि useIt कोड फोन नहीं करता है:

16:virt.cpp  ****  return useIt(b); 
17:virt.cpp  **** } 
124     .loc 1 17 0 
125 0015 B8020000  movl $2, %eax #, 

जो मुझे लगता है कि सिर्फ कह रहा है, हम जानते हैं कि हम वापसी करने वाले 2, बस eax में रख देता है। (मैं कार्यक्रम को फिर से चलाने के लिए कह रहा हूं 200, और उस लाइन को अपडेट किया गया जैसा कि मैं उम्मीद करता हूं)।


अतिरिक्त बिट

तो मैं कार्यक्रम को जटिल थोड़ा अधिक:

:

struct A { 
    int valA; 
    A(int value) : valA(value) {} 
    virtual int foo() { return valA; } 
}; 

struct B : public A { 
    int valB; 
    B(int value) : valB(value), A(0) {} 
    virtual int foo() { return valB; } 
}; 

int useIt(A* a) { 
    return a->foo(); 
} 

int main() 
{ 
    A* a = new A(100); 
    B* b = new B(200); 
    int valA = useIt(a); 
    int valB = useIt(a); 
    return valA + valB; 
} 

इस संस्करण में, useIt कोड निश्चित रूप से अनुकूलित विधानसभा में vtable का उपयोग करता है

13:virt.cpp  **** int useIt(A* a) { 
    89     .loc 1 13 0 
    90     .cfi_startproc 
    91    .LVL2: 
    14:virt.cpp  ****  return a->foo(); 
    92     .loc 1 14 0 
    93 0000 488B07  movq (%rdi), %rax # a_1(D)->_vptr.A, a_1(D)->_vptr.A 
    94 0003 488B00  movq (%rax), %rax # *D.2274_2, *D.2274_2 
    95 0006 FFE0   jmp *%rax # *D.2274_2 
    96    .LVL3: 
    97     .cfi_endproc 

इस बार, मुख्य कार्य इनलाइन useIt की एक प्रति, लेकिन वास्तव में vtable लुकअप करता है।


के बारे में क्या C++ 11 और 'अंतिम' कीवर्ड?

तो मैं करने के लिए एक लाइन बदल:

virtual int foo() override final { return valB; } 

और संकलक लाइन के लिए:

g++ -std=c++11 -g -S -O6 -fverbose-asm virt.cpp 

में सोच रही थी कि संकलक इसे अंतिम ओवरराइड है कि कह रहा, इसे छोड़ करने की अनुमति होगी शायद vtable।

यह अभी भी vtable का उपयोग करता है।


तो मेरी सैद्धांतिक जवाब होगा:

  • मुझे नहीं लगता कि किसी भी स्पष्ट, "vtable का उपयोग नहीं करते" अनुकूलन कर रहे हैं। (मैंने vtable और virt के लिए g ++ manpage के माध्यम से खोज की और जैसा कुछ भी नहीं मिला)।
  • लेकिन जी ++ के साथ -6, एक सरल प्रोग्राम पर बहुत अनुकूलन कर सकता है जिसमें स्पष्ट स्थिरांक बिंदु के साथ जहां यह परिणाम की भविष्यवाणी कर सकता है और कॉल को पूरी तरह से छोड़ सकता है।
  • हालांकि, एक बार चीजें जटिल हो जाती हैं (वास्तविक पढ़ें) यह निश्चित रूप से vtable लुकअप कर रही है, हर बार जब आप वर्चुअल फ़ंक्शन को कॉल करते हैं।
संबंधित मुद्दे