2010-06-10 27 views
14

मैं प्रोग्रामिंग सी पर सिगविन विंडोज़ पर हूं। थोड़ा सी प्रोग्रामिंग करने और भाषा के साथ सहज महसूस करने के बाद, मैं हुड के नीचे देखना चाहता था और देखता हूं कि संकलक जो कोड लिखता है उसके लिए क्या कर रहा है।स्विच केस असेंबली स्तर कोड

तो मैं स्विच मामले में बयान से युक्त एक कोड ब्लॉक नीचे लिखा और उन्हें प्रयोग विधानसभा में परिवर्तित कर दिया:

switch(i) 
{ 
    case 1: 
    { 
     printf("Case 1\n"); 
     break; 
    } 
    case 2: 
    {   printf("Case 2\n"); 
     break; 
    } 
    case 3: 
    { 
     printf("Case 3\n"); 
     break; 
    } 
    case 4: 
    { 
     printf("Case 4\n"); 
     break; 
    } 
    case 5: 
    { 
     printf("Case 5\n"); 
     break; 
    } 
    case 6: 
    { 
     printf("Case 6\n"); 
     break; 
    } 
    case 7: 
    { 
     printf("Case 7\n"); 
     break; 
    } 
    case 8: 
    { 
     printf("Case 8\n"); 
     break; 
    } 
    case 9: 
    { 
     printf("Case 9\n"); 
     break; 
    } 
    case 10: 
    { 
     printf("Case 10\n"); 
     break; 
    } 
    default: 
    { 
     printf("Nothing\n"); 
     break; 
    } 
} 

अब एक ही है के लिए परिणामी विधानसभा:

gcc -S foo.c 

यहाँ सी स्रोत है :

movl $5, -4(%ebp) 
cmpl $10, -4(%ebp) 
ja L13 
movl -4(%ebp), %eax 
sall $2, %eax 
movl L14(%eax), %eax 
jmp *%eax 
.section .rdata,"dr" 
.align 4 
L14: 
.long L13 
.long L3 
.long L4 
.long L5 
.long L6 
.long L7 
.long L8 
.long L9 
.long L10 
.long L11 
.long L12 
.text 
L3: 
movl $LC0, (%esp) 
call _printf 
jmp L2 
L4: 
movl $LC1, (%esp) 
call _printf 
jmp L2 
L5: 
movl $LC2, (%esp) 
call _printf 
jmp L2 
L6: 
movl $LC3, (%esp) 
call _printf 
jmp L2 
L7: 
movl $LC4, (%esp) 
call _printf 
jmp L2 
L8: 
movl $LC5, (%esp) 
call _printf 
jmp L2 
L9: 
movl $LC6, (%esp) 
call _printf 
jmp L2 
L10: 
movl $LC7, (%esp) 
call _printf 
jmp L2 
L11: 
movl $LC8, (%esp) 
call _printf 
jmp L2 
L12: 
movl $LC9, (%esp) 
call _printf 
jmp L2 
L13: 
movl $LC10, (%esp) 
call _printf 
L2: 

अब, असेंबली में, कोड पहले अंतिम मामले की जांच कर रहा है (यानी मामला 10) पहले। यह बहुत अजीब है। और फिर यह 'i' को 'eax' में कॉपी कर रहा है और मेरे बाहर की चीजें कर रहा है।

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

अग्रिम धन्यवाद।

+0

कि दुर्भाग्यपूर्ण है कि यह स्ट्रिंग सूचक की एक टेबल देखने के लिए और एक 'कॉल _printf' को अनुकूलित नहीं करता। जीसीसी/क्लैंग/आईसीसी में से कोई भी '-O3' पर भी नहीं करता है। https://godbolt.org/g/JrSwU3 (वे करते अनुकूलन '' puts' को printf', हालांकि, और एक 'एक' call'/'ret' के बजाय jmp' को पूंछ-कॉल का अनुकूलन) –

उत्तर

21

पहला कोड I से 10 की तुलना कर रहा है और जब मान अधिक है तो 10 (cmpl $10, -4(%ebp)ja L13) के बाद डिफ़ॉल्ट स्थिति में कूद रहा है।

कोड के अगले बिट चार जो एक कूद तालिका में ऑफसेट (क्योंकि तालिका में प्रत्येक प्रविष्टि 4 बाइट लंबा है) उत्पन्न करता है के द्वारा दो (sall $2, %eax) द्वारा छोड़ा है जो कई रूप में ही है के लिए इनपुट स्थानांतरण

फिर यह कूद तालिका (movl L14(%eax), %eax) से एक पता लोड करता है और इसे कूदता है (jmp *%eax)।

कूद तालिका बस पतों की एक सूची (लेबल द्वारा विधानसभा कोड में प्रतिनिधित्व) है: सूचना के लिए

L14: 
.long L13 
.long L3 
.long L4 
... 

एक बात है कि L13 डिफ़ॉल्ट मामले का प्रतिनिधित्व करता है। यह कूद तालिका में पहली प्रविष्टि है (जब मैं 0 है) और शुरुआत में विशेष रूप से संभाला जाता है (जब मैं> 10)।

+0

मैं देख रहा हूँ ... यह जानकारीपूर्ण है। लेकिन फिर कम मामलों (जैसे 2 या 3) के मामले में संकलक एक कूद तालिका क्यों उत्पन्न नहीं करता है? – puffadder

+0

@puffadder: सबसे आधुनिक compilers निर्धारित करने के लिए heuristics का उपयोग जब यह एक कूद तालिका बनाम शाखाओं उपयोग करने के लिए और अधिक कुशल है। जैसे यदि आपके केस स्तर 1, 100 और 1000 कह रहे थे तो आप शाखाओं का उपयोग करने की उम्मीद कर सकते हैं। –

+0

मुझे समझ में नहीं आता कि स्विच के बाहर हर कदम डिफ़ॉल्ट मामले में क्यों जाता है। क्या कोई इसे समझा सकता है? – mharris7190

2

[1..10] के लिए संकलक एक टेबल उत्पन्न करेगा ताकि उसे कहीं भी जाने के लिए मूल्य की तुलना करने की आवश्यकता न हो, यह सीधे एक: goto table[i] करता है। इस तरह यह तेज़ है।

लेकिन i > 10 के मामले में यह आपके डिफ़ॉल्ट कथन पर कूदता है। इसे अन्यथा कूदने से पहले पहले जांचना है, कार्यक्रम दुर्भाग्य से दुर्घटनाग्रस्त हो जाएगा।

यदि आपके पास स्पैस मूल्य (जैसे, 23, 9233, 9 1238, और 1, 2, 3 ...) नहीं थे, तो कंपाइलर ऐसी तालिका उत्पन्न नहीं करेगा, और प्रत्येक मान की तुलना करेगा।

0

हाँ, पहले eax स्विच मूल्य (गुणा के रूप में sall पारी) करके की जाती है (लेबल L14: निम्नलिखित)

jmp *%eax अपने मामले के लेबल करने के लिए एक के पास कूद है कूद तालिका से पता मिलता है। (eax के पास JMP)

अन्य लेबल निम्नलिखित कोड सिर्फ मुद्रण और अन्य मामलों को छोड़ देता है।

3

हाँ, यह एक कूद तालिका है। पहली जांच यह जांचना है कि क्या मूल्य मामलों में है और यदि यह नहीं है तो डिफ़ॉल्ट पर कूदें। यह मत भूलना कि ऐसी तालिका में, यदि% eax 0 है, तो L14 (% eax) तालिका के पहले तत्व (L13) को इंगित करता है। तो तालिका में case 10: 9, 10 के साथ अनुक्रमित नहीं है।

स्विच किया जा सकता है जिस तरह से case में आपके मानों पर निर्भर करता है; इस मामले में वे "अनुक्रम" में हैं, इसलिए सरल कूद तालिका संभव है।

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