2012-09-12 12 views
10

जीसीसी आप (void *addr = &&label के रूप में) एक लेबल के पते ले जा रहा है और फिर इसे (jump *addr) करने के लिए कूद कर एक अभिकलन गोटो का उपयोग कर सकते हैं। GCC manual कहता है कि आप इस पते पर किसी भी ­ से कूद सकते हैं जहां फ़ंक्शन में, यह केवल एक अन्य फ़ंक्शन से इसे कूदने के लिए अनिर्धारित है।जीसीसी गणना की गोटो और ढेर सूचक का मूल्य

जब आप कोड पर कूदते हैं तो यह रजिस्टरों के मूल्यों के बारे में कुछ भी नहीं मान सकता है, इसलिए संभवतः यह उन्हें स्मृति से पुनः लोड करता है। हालांकि स्टैक पॉइंटर का मूल्य भी आवश्यक रूप से परिभाषित नहीं किया गया है, उदाहरण के लिए आप एक नेस्टेड स्कोप से कूद सकते हैं जो अतिरिक्त चर घोषित करता है।

सवाल यह है कि जीसीसी सही मूल्य पर स्टैक पॉइंटर के मूल्य पर कैसे सेट करता है (यह बहुत अधिक या बहुत कम हो सकता है)? और यह (यदि ऐसा करता है) के साथ कैसे सहभागिता करता है?

अंत में, अतिरिक्त बिंदुओं के लिए, आप कहां से किसी लेबल पर जा सकते हैं के बारे में असली बाधाएं हैं? पूर्व ­ पूर्वाह्न ­ से अधिक के लिए, आप शायद इसे एक इंटरप्ट हैंडलर से कर सकते हैं।

उत्तर

11

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

लेबल पर कूदने पर एकमात्र बाधा वह है जिसे आपने नोट किया - आप इसे केवल उस फ़ंक्शन के भीतर ही कर सकते हैं जिसमें लेबल शामिल हैं। किसी भी अन्य समारोह के किसी भी अन्य ढेर फ्रेम या हस्तक्षेप हैंडलर या कुछ भी नहीं।

संपादित

आप एक ढेर फ्रेम से दूसरे के लिए कूद करने में सक्षम होना चाहते हैं, तो आप setjmp/longjmp या स्टैक करने के लिए कुछ इसी तरह का उपयोग करने की जरूरत है। आपको लगता है कि जोड़ सकता एक अप्रत्यक्ष गोटो के साथ - कुछ की तरह:

if (target = (void *)setjmp(jmpbuf)) goto *target; 

कि जिस तरह से आप किसी भी बुलाया समारोह से longjmp(jmpbuf, label_address); कह सकते हैं स्टैक करने के लिए और उसके बाद लेबल के लिए कूद। जब तक setjmp/longjmp एक इंटरप्ट हैंडलर से काम करता है, यह एक इंटरप्ट हैंडलर से भी काम करेगा। यह भी sizeof(int) == sizeof(void *) पर निर्भर करता है, जो हमेशा मामला नहीं है।

+1

+1, अच्छा स्पष्टीकरण – rkosegi

+0

हस्तक्षेप करने वाले हैंडलरों के बारे में दिलचस्प बात यह है कि ढेर को बेकार किया जाएगा और आप सभी रजिस्टरों (एसपी सहित) को पुनर्स्थापित कर सकते हैं। इस मामले में प्रदान किया गया है कि जीसीसी जो आपने वर्णन किया है वह करता है तो आप वास्तव में एक इंटरप्ट हैंडलर से एक लेबल पर कूदने में सक्षम होंगे। एकमात्र मामला यह काम नहीं करेगा अगर जीसीसी गणना किए गए गोटो के आसपास विशेष प्रति-फ़ंक्शन स्टैक हैंडलिंग कोड डालता है। – jleahy

+0

@jleahy: जो यह निश्चित रूप से कर सकता है (यह नहीं जानता कि यह करता है)। न्यूनतम आवश्यकता यह है कि फ़ंक्शन में एक परिभाषित "साफ" राज्य होता है: सभी लेबल जिनके पते को इस स्वच्छ राज्य में शुरू किया जाता है, और सभी गणना किए गए कूद कूदने से पहले स्वच्छ स्थिति स्थापित करते हैं। असल में यह उससे भी कम हो सकता है: "लेबल का पता" कुछ कोड का पता हो सकता है जो स्वच्छ राज्य से लेबल पर अपेक्षित राज्य तक जाता है, फिर 'असली "लेबल मिल जाता है। यह काम करेगा, लेकिन यह कुछ और विरोधाभास हो सकता है कि जीसीसी लेबल के पते के बारे में कहता है। –

0

फ़ंक्शन प्रस्तावना में स्टैक की वर्तमान स्थिति को कैली सहेजे गए रजिस्टर में सहेजा जाता है -फॉमिट-फ्रेम-पॉइंटर के साथ भी।

नीचे दिए गए उदाहरण में sp + 4 r7 में संग्रहीत है और फिर epilogue (LBB0_3) में पुनर्स्थापित किया गया है (r7 + 4 -> r4; r4 -> sp)। इस वजह से आप फ़ंक्शन के भीतर कहीं भी कूद सकते हैं, फ़ंक्शन में किसी भी बिंदु पर स्टैक बढ़ा सकते हैं और स्टैक को पेंच नहीं कर सकते हैं। यदि आप फ़ंक्शन से बाहर कूदते हैं (कूद * एडीआर के माध्यम से) तो आप इस उपन्यास को छोड़ देंगे और रॉयली को ढेर कर देंगे।

लघु उदाहरण जो भी alloca जो गतिशील रूप से ढेर पर स्मृति आवंटित करता है का उपयोग करता है:

बजना -arch ARMv7 -fomit फ्रेम-सूचक -c एस -O0 -ओ - ढेर।ग

#include <alloca.h> 

int foo(int sz, int jmp) { 
    char *buf = alloca(sz); 
    int rval = 0; 

    if(jmp) { 
     rval = 1; 
     goto done; 
    } 

    volatile int s = 2; 

    rval = s * 5; 

done: 

    return rval; 
} 

और disassembly:

_foo: 
@ BB#0: 
    push {r4, r7, lr} 
    add r7, sp, #4 
    sub sp, #20 
    movs r2, #0 
    movt r2, #0 
    str r0, [r7, #-8] 
    str r1, [r7, #-12] 
    ldr r0, [r7, #-8] 
    adds r0, #3 
    bic r0, r0, #3 
    mov r1, sp 
    subs r0, r1, r0 
    mov sp, r0 
    str r0, [r7, #-16] 
    str r2, [r7, #-20] 
    ldr r0, [r7, #-12] 
    cmp r0, #0 
    beq LBB0_2 
@ BB#1: 
    movs r0, #1 
    movt r0, #0 
    str r0, [r7, #-20] 
    b LBB0_3 
LBB0_2: 
    movs r0, #2 
    movt r0, #0 
    str r0, [r7, #-24] 
    ldr r0, [r7, #-24] 
    movs r1, #5 
    movt r1, #0 
    muls r0, r1, r0 
    str r0, [r7, #-20] 
LBB0_3: 
    ldr r0, [r7, #-20] 
    subs r4, r7, #4 
    mov sp, r4 
    pop {r4, r7, pc} 
+0

यह सामान्य गोटो के लिए सच है, क्योंकि संकलक संकलन समय पर गंतव्य जानता है, लेकिन मुझे यकीन नहीं है कि यह एक गणना गोटो के लिए सही है या नहीं। – jleahy

+0

यह अभी भी सच है। एसपी को उस रजिस्टर में सहेजा जाता है जिसे फ़ंक्शन में कहीं और नहीं लगाया जाता है और जिसे किसी भी फ़ंक्शन द्वारा सहेजा जाता है/उसे पुनर्स्थापित किया जाता है। इसलिए फ़ंक्शन के माध्यम से कोड प्रवाह (चाहे सीधे या गतिशील रूप से कूद रहा हो) तब तक कोई फर्क नहीं पड़ता जब तक आप फ़ंक्शन एपिलॉग के माध्यम से जाते हैं। – James

+2

'r7' यहां आपका फ्रेम सूचक है। एलोका को लागू करने के लिए फ़ोकस पॉइंटर के रूप में फ़ंक्शन के लिए एलोका अक्षम-फ़ॉमिट-फ्रेम-पॉइंटर का उपयोग करना आवश्यक है। –

2

मुझे नहीं लगता कि तथ्य यह है कि गोटो के परिकलन किया जाता है यह स्थानीय चर पर है कि प्रभाव में जोड़ देते हैं। स्थानीय चर का जीवनकाल उनकी घोषणा में या उससे आगे की घोषणा में प्रवेश करने से शुरू होता है और जब वेरिएबल का दायरा किसी भी तरह तक नहीं पहुंचा जा सकता है। इसमें नियंत्रण प्रवाह के सभी प्रकार शामिल हैं, विशेष रूप से goto और longjmp। इसलिए ऐसे सभी चर हमेशा सुरक्षित होते हैं, जब तक कि उन्हें फ़ंक्शन से वापस नहीं किया जाता है।

सी में लेबल पूरे एनग्लोबिंग फ़ंक्शन के लिए दृश्यमान होते हैं, इसलिए यदि यह एक गणना goto है तो इससे कोई फर्क नहीं पड़ता है। आप हमेशा एक कम से कम switch कथन के साथ एक गणना goto को प्रतिस्थापित कर सकते हैं।

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

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