असेंबली भाषा में स्टैक कैसे काम करता है?


84

मैं वर्तमान में यह समझने की कोशिश कर रहा हूं कि स्टैक कैसे काम करता है, इसलिए मैंने खुद को कुछ विधानसभा भाषा सिखाने का फैसला किया है , मैं इस पुस्तक का उपयोग कर रहा हूं:

http://savannah.nongnu.org/projects/pgubook/

मैं गैस का उपयोग कर रहा हूं और लिनक्स मिंट पर अपना विकास कर रहा हूं ।

मैं कुछ उलझन में हूँ:

जहाँ तक मुझे पता था कि एक स्टैक केवल एक डेटा संरचना है। तो मुझे लगता है कि अगर मैं विधानसभा में कोडिंग कर रहा था तो मुझे खुद को स्टैक लागू करना होगा। हालाँकि यह ऐसा प्रतीत नहीं होता है जैसे कमांड हैं

pushl
popl

तो x86 आर्किटेक्चर के लिए असेंबली में कोडिंग और गैस सिंटैक्स का उपयोग करते समय: क्या स्टैक सिर्फ एक डेटा संरचना है जो पहले से लागू है? या यह वास्तव में हार्डवेयर स्तर पर लागू किया गया है? या यह कुछ और है? अन्य चिप सेटों के लिए अधिकांश असेंबली भाषाओं में पहले से लागू स्टैक है?

मुझे पता है कि यह एक मूर्खतापूर्ण सवाल है, लेकिन मैं वास्तव में इससे काफी भ्रमित हूं।


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

जवाबों:


82

मैं मुख्य रूप से आप एक के बीच उलझन में हो रहे हैं लगता है program's stackऔर any old stack

ढेर

एक अमूर्त डेटा संरचना है जिसमें अंतिम आउट सिस्टम में अंतिम जानकारी होती है। आप स्टैक पर मनमानी वस्तुएं डालते हैं और फिर आप उन्हें फिर से बंद कर देते हैं, जैसे कि एक इन / आउट ट्रे, शीर्ष आइटम हमेशा वह होता है जिसे बंद कर दिया जाता है और आप हमेशा शीर्ष पर रख देते हैं।

एक कार्यक्रम ढेर

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

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

आपको पढ़ना चाहिए और सुनिश्चित करना चाहिए कि आप विकिपीडिया लेख को समझें क्योंकि यह हार्डवेयर स्टैक का अच्छा विवरण देता है जो आप के साथ काम कर रहे हैं।

यह ट्यूटोरियल भी है जो पुराने 16bit रजिस्टरों के संदर्भ में स्टैक की व्याख्या करता है, लेकिन सहायक हो सकता है और स्टैक के बारे में विशेष रूप से एक हो सकता है ।

निल्स पिपेनब्रिनक से:

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

उदाहरण के लिए, MIPs में एक पुश निर्देश लागू किया जाएगा:

addi $sp, $sp, -4  # Decrement stack pointer by 4  
sw   $t0, ($sp)   # Save $t0 to stack  

और एक पॉप निर्देश की तरह दिखेगा:

lw   $t0, ($sp)   # Copy from stack to $t0  
addi $sp, $sp, 4   # Increment stack pointer by 4  

2
Btw - x86 में ये विशेष स्टैक निर्देश हैं क्योंकि स्टैक से सामान को धक्का देने और पॉप करने से खुशी इतनी बार होती है कि उनके लिए एक छोटा ओपकोड (कम कोड-स्पेस) का उपयोग करना एक अच्छा विचार था। MIPS और ARM जैसे आर्किटेक्चर के पास ये नहीं हैं, इसलिए आपको स्टैक को अपने दम पर लागू करना होगा।
Nils Pipenbrinck

4
ध्यान रखें कि आपका गर्म नया प्रोसेसर 8086 के साथ कुछ हद तक बाइनरी-संगत है, और यह 8080 के साथ स्रोत-संगत था, 8008 का विकास, पहला माइक्रोप्रोसेसर। इनमें से कुछ निर्णय बहुत पीछे चले जाते हैं।
डेविड थॉर्नले

4
एआरएम में, स्टैक में हेरफेर करने के लिए एकल निर्देश हैं, वे सिर्फ इसलिए स्पष्ट नहीं हैं क्योंकि उन्हें एसटीएमडीबी एसपी कहा जाता है! (PUSH के लिए) और LDMIA SP! (पीओपी के लिए)।
एडम गॉड

1
मेरे भगवान यह जवाब +500 की जरूरत है ... मुझे कुछ भी नहीं मिला है यह अच्छी तरह से समझाया गया है हमेशा के लिए। यह अब के रूप में +1 करने के लिए नए खातों को ध्यान में रखते हुए ...
गेब्रियल 15


36

( यदि आप इसके साथ खेलना चाहते हैं तो मैंने इस उत्तर में सभी कोड का एक जिप बनाया है)

मैंने 2003 में अपने CS101 कोर्स के दौरान केवल कभी-कभी सबसे बुनियादी काम किया था। और मुझे वास्तव में "यह" नहीं मिला कि कैसे आस और स्टैक काम करते हैं जब तक कि मुझे एहसास नहीं हो गया कि यह C या C ++ में प्रोग्रामिंग की तरह सभी आधारभूत है ... लेकिन स्थानीय चर, मापदंडों और कार्यों के बिना। शायद अभी तक आसान नहीं लगता है :) मुझे आपको दिखाने के लिए ( इंटेल सिंटैक्स के साथ x86 asm के लिए )।


1. स्टैक क्या है

स्टैक आमतौर पर स्मृति का एक सन्निहित हिस्सा होता है जिसे वे शुरू करने से पहले हर धागे के लिए आवंटित करते हैं। आप जो चाहें वहां स्टोर कर सकते हैं। C ++ शब्दों में ( कोड स्निपेट # 1 ):

const int STACK_CAPACITY = 1000;
thread_local int stack[STACK_CAPACITY];

2. स्टैक के ऊपर और नीचे

सिद्धांत रूप में, आप stackसरणी के यादृच्छिक सेल ( स्निपेट # 2.1 ) में मान संग्रहीत कर सकते हैं :

stack[333] = 123;
stack[517] = 456;
stack[555] = stack[333] + stack[517];

लेकिन कल्पना करें कि यह याद रखना कितना मुश्किल होगा कि कौन-सी कोशिकाएँ stackपहले से ही उपयोग में हैं और जो "स्वतंत्र" हैं। इसलिए हम एक दूसरे के बगल में ढेर पर नए मूल्यों को संग्रहीत करते हैं।

(X86) asm के स्टैक के बारे में एक अजीब बात यह है कि आप वहां चीजें जोड़ते हैं जो आखिरी इंडेक्स से शुरू होती है और निचले इंडेक्स में जाती है: स्टैक [999], फिर स्टैक [998] और इसी तरह ( स्निपेट # 2.2 ):

stack[999] = 123;
stack[998] = 456;
stack[997] = stack[999] + stack[998];

और फिर भी के लिए "आधिकारिक" नाम (सतर्कता, अब आप भ्रमित होने वाले हैं) stack[999]है ढेर के नीचे
अंतिम प्रयुक्त सेल ( stack[997]ऊपर उदाहरण में) को स्टैक के ऊपर कहा जाता है (देखें कि स्टैक का शीर्ष x86 पर कहां है )।


3. स्टैक पॉइंटर (SP)

इस चर्चा के उद्देश्य से मान लें कि CPU रजिस्टर को वैश्विक चर के रूप में दर्शाया गया है ( सामान्य प्रयोजन रजिस्टर देखें )।

int AX, BX, SP, BP, ...;
int main(){...}

विशेष सीपीयू रजिस्टर (एसपी) है जो स्टैक के शीर्ष को ट्रैक करता है। SP एक पॉइंटर है (0xAAAABBCC जैसा मेमोरी एड्रेस रखता है)। लेकिन इस पोस्ट के प्रयोजनों के लिए मैं इसे एक सरणी सूचकांक (0, 1, 2, ...) के रूप में उपयोग करूंगा।

जब एक थ्रेड शुरू होता है, SP == STACK_CAPACITYऔर फिर प्रोग्राम और ओएस इसे आवश्यकतानुसार संशोधित करते हैं। नियम यह है कि आप स्टैक के ऊपर और किसी भी इंडेक्स से अधिक स्टैक सेल को नहीं लिख सकते हैं, तब एसपी अमान्य और असुरक्षित है ( सिस्टम में रुकावट के कारण ), इसलिए आप पहले एसपी को घटाते हैं और फिर नए आवंटित सेल के लिए एक मान लिखते हैं।

जब आप एक पंक्ति में ढेर में कई मूल्यों को धक्का देना चाहते हैं, तो आप उन सभी के लिए स्थान आरक्षित कर सकते हैं ( स्निपेट # 3 ):

SP -= 3;
stack[999] = 12;
stack[998] = 34;
stack[997] = stack[999] + stack[998];

ध्यान दें। अब आप देख सकते हैं कि स्टैक पर आवंटन इतनी तेजी से क्यों है - यह सिर्फ एक रजिस्टर में वृद्धि है।


4. स्थानीय चर

आइए एक नज़र डालते हैं इस सरलीकृत समारोह ( स्निपेट # 4.1 ) पर:

int triple(int a) {
    int result = a * 3;
    return result;
}

और स्थानीय चर ( स्निपेट # 4.2 ) का उपयोग किए बिना इसे फिर से लिखना :

int triple_noLocals(int a) {
    SP -= 1; // move pointer to unused cell, where we can store what we need
    stack[SP] = a * 3;
    return stack[SP];
}

और देखें कि इसे कैसे कहा जा रहा है ( स्निपेट # 4.3 ):

// SP == 1000
someVar = triple_noLocals(11);
// now SP == 999, but we don't need the value at stack[999] anymore
// and we will move the stack index back, so we can reuse this cell later
SP += 1; // SP == 1000 again

5. पुश / पॉप

स्टैक के शीर्ष पर एक नए तत्व का जोड़ एक ऐसा लगातार ऑपरेशन है, जो कि सीपीयू के लिए एक विशेष निर्देश है push। हम इसे इस तरह से निकालेंगे ( स्निपेट 5.1 ):

void push(int value) {
    --SP;
    stack[SP] = value;
}

इसी तरह, ढेर के शीर्ष तत्व को लेना ( स्निपेट 5.2 ):

void pop(int& result) {
    result = stack[SP];
    ++SP; // note that `pop` decreases stack's size
}

पुश / पॉप के लिए सामान्य उपयोग पैटर्न अस्थायी रूप से कुछ मूल्य बचा रहा है। कहें, हमारे पास चर में कुछ उपयोगी है myVarऔर किसी कारण से हमें गणना करने की आवश्यकता है जो इसे अधिलेखित कर देगा ( स्निपेट 5.3 ):

int myVar = ...;
push(myVar); // SP == 999
myVar += 10;
... // do something with new value in myVar
pop(myVar); // restore original value, SP == 1000

6. फंक्शन पैरामीटर

अब स्टैक का उपयोग करते हुए पैरामीटर पास करें ( स्निपेट # 6 ):

int triple_noL_noParams() { // `a` is at index 999, SP == 999
    SP -= 1; // SP == 998, stack[SP + 1] == a
    stack[SP] = stack[SP + 1] * 3;
    return stack[SP];
}

int main(){
    push(11); // SP == 999
    assert(triple(11) == triple_noL_noParams());
    SP += 2; // cleanup 1 local and 1 parameter
}

7. returnकथन

AX रजिस्टर में मान लौटाएं ( स्निपेट # 7 ):

void triple_noL_noP_noReturn() { // `a` at 998, SP == 998
    SP -= 1; // SP == 997

    stack[SP] = stack[SP + 1] * 3;
    AX = stack[SP];

    SP += 1; // finally we can cleanup locals right in the function body, SP == 998
}

void main(){
    ... // some code
    push(AX); // save AX in case there is something useful there, SP == 999
    push(11); // SP == 998
    triple_noL_noP_noReturn();
    assert(triple(11) == AX);
    SP += 1; // cleanup param
             // locals were cleaned up in the function body, so we don't need to do it here
    pop(AX); // restore AX
    ...
}

8. स्टैक बेस पॉइंटर (BP) (जिसे फ्रेम पॉइंटर भी कहा जाता है ) और स्टैक फ्रेम

अधिक "उन्नत" फंक्शन लें और इसे हमारे asm जैसे C ++ ( स्निपेट # 8.1 ) में फिर से लिखें :

int myAlgo(int a, int b) {
    int t1 = a * 3;
    int t2 = b * 3;
    return t1 - t2;
}

void myAlgo_noLPR() { // `a` at 997, `b` at 998, old AX at 999, SP == 997
    SP -= 2; // SP == 995

    stack[SP + 1] = stack[SP + 2] * 3; 
    stack[SP]     = stack[SP + 3] * 3;
    AX = stack[SP + 1] - stack[SP];

    SP += 2; // cleanup locals, SP == 997
}

int main(){
    push(AX); // SP == 999
    push(22); // SP == 998
    push(11); // SP == 997
    myAlgo_noLPR();
    assert(myAlgo(11, 22) == AX);
    SP += 2;
    pop(AX);
}

अब कल्पना करें कि हमने लौटने से पहले परिणाम को स्टोर करने के लिए नए स्थानीय वैरिएबल को पेश करने का फैसला किया, जैसा कि हम tripple(स्निपेट # 4.1) में करते हैं। समारोह का शरीर होगा ( स्निपेट # 8.2 ):

SP -= 3; // SP == 994
stack[SP + 2] = stack[SP + 3] * 3; 
stack[SP + 1] = stack[SP + 4] * 3;
stack[SP]     = stack[SP + 2] - stack[SP + 1];
AX = stack[SP];
SP += 3;

आप देखते हैं, हमें फ़ंक्शन पैरामीटर और स्थानीय चर के लिए हर एक संदर्भ को अपडेट करना था। उससे बचने के लिए, हमें एक एंकर इंडेक्स की आवश्यकता होती है, जो स्टैक बढ़ने पर बदलता नहीं है।

हम बीपी रजिस्टर में वर्तमान शीर्ष (एसपी के मूल्य) को बचाकर फ़ंक्शन एंट्री (स्थानीय लोगों के लिए स्थान आवंटित करने से पहले) पर लंगर का अधिकार बनाएंगे। स्निपेट # 8.3 :

void myAlgo_noLPR_withAnchor() { // `a` at 997, `b` at 998, SP == 997
    push(BP);   // save old BP, SP == 996
    BP = SP;    // create anchor, stack[BP] == old value of BP, now BP == 996
    SP -= 2;    // SP == 994

    stack[BP - 1] = stack[BP + 1] * 3;
    stack[BP - 2] = stack[BP + 2] * 3;
    AX = stack[BP - 1] - stack[BP - 2];

    SP = BP;    // cleanup locals, SP == 996
    pop(BP);    // SP == 997
}

स्टैक का स्लाइस, जो कि फंक्शन के पूर्ण नियंत्रण से संबंधित है और फ़ंक्शन का स्टैक फ्रेम कहलाता है । ईजी myAlgo_noLPR_withAnchorका स्टैक फ्रेम है stack[996 .. 994](दोनों आइडैक्स सम्मिलित)।
फ़्रेम फ़ंक्शन बीपी पर शुरू होता है (बाद में हमने इसे फ़ंक्शन के अंदर अपडेट किया है) और अगले स्टैक फ्रेम तक रहता है। तो स्टैक पर पैरामीटर कॉलर के स्टैक फ्रेम का हिस्सा हैं (नोट 8 ए देखें)।

नोट:
8 ए। विकिपीडिया मापदंडों के बारे में अन्यथा कहता है , लेकिन यहां मैं इंटेल सॉफ्टवेयर डेवलपर के मैनुअल का पालन ​​करता हूं , वॉल्यूम देखें। 1, खंड 6.2.4.1 स्टैक-फ़्रेम बेस पॉइंटर और चित्रा 6-2 खंड 6.3.2 सुदूर कॉल और आरईटी ऑपरेशन में । फ़ंक्शन के पैरामीटर और स्टैक फ्रेम फ़ंक्शन के सक्रियण रिकॉर्ड का हिस्सा हैं ( फ़ंक्शन पेरिलॉग्स पर जीन देखें )।
8b। बीपी से पॉजिटिव ऑफ़सेट फंक्शन पैरामीटर और नेगेटिव ऑफ़सेट स्थानीय वेरिएबल्स की ओर इशारा करते हैं। यह
8c डिबगिंग के लिए बहुत आसान है stack[BP]पिछले स्टैक फ्रेम के पते को संग्रहीत करता है,stack[stack[BP]]पूर्व-पूर्व स्टैक फ्रेम और इतने पर स्टोर करता है। इस श्रृंखला के बाद, आप प्रोग्राम में सभी कार्यों के फ्रेम की खोज कर सकते हैं, जो अभी तक वापस नहीं आया था। यह कैसे डिबगर दिखाते हैं कि आप स्टैक
8d कहते हैं के पहले 3 निर्देश myAlgo_noLPR_withAnchor, जहाँ हम फ्रेम को सेटअप करते हैं (पुराने बीपी को बचाएं, बीपी अपडेट करें, स्थानीय लोगों के लिए आरक्षित स्थान) को फ़ंक्शन प्रस्तावना कहा जाता है


9. सम्मेलन बुलाना

स्निपेट 8.1 में हमने myAlgoदाएं से बाएं और फिर से परिणाम के लिए मापदंडों को आगे बढ़ाया है AX। हम दायीं ओर के बाएं पारामों को भी पास कर सकते हैं और वापस लौट सकते हैं BX। या बीएक्स और सीएक्स में पैरामेट्स उत्तीर्ण करें और एएक्स में लौटें। जाहिर है, फोन करने वाले ( main()) और फंक्शन को इस बात से सहमत होना चाहिए कि यह सारा सामान कहां और किस क्रम में रखा गया है।

कॉलिंग कन्वेंशन नियमों का एक सेट है कि कैसे मापदंडों को पारित किया जाता है और परिणाम वापस किया जाता है।

उपरोक्त कोड में हमने cdecl कॉलिंग कन्वेंशन का उपयोग किया है :

  • पैरामीटर को स्टैक पर पास किया जाता है, कॉल के समय स्टैक पर सबसे कम पते पर पहला तर्क (अंतिम <...) धक्का दिया जाता है। कॉल के बाद स्टैक से वापस मापदंडों को पॉप करने के लिए कॉलर जिम्मेदार है।
  • वापसी मान को AX में रखा गया है
  • ईबीपी और ईएसपी को कैली ( myAlgo_noLPR_withAnchorहमारे मामले में कार्य) द्वारा संरक्षित किया जाना चाहिए , जैसे कि कॉलर ( mainफ़ंक्शन) उन रजिस्टरों पर भरोसा कर सकता है जिन्हें कॉल द्वारा नहीं बदला गया है।
  • अन्य सभी रजिस्टर (EAX, <...>) कैली द्वारा स्वतंत्र रूप से संशोधित किया जा सकता है; यदि कोई कॉल फ़ंक्शन कॉल के पहले और बाद में किसी मान को संरक्षित करना चाहता है, तो उसे कहीं और मान को सहेजना होगा (हम इसे AX के साथ करते हैं)

(स्रोत: से स्टैक ओवरफ़्लो प्रलेखन उदाहरण "32-बिट cdecl"; द्वारा कॉपीराइट 2016 icktoofay और पीटर Cordes ।; 3.0 सीसी BY-SA एक के अंतर्गत लाइसेंस प्राप्त पूर्ण स्टैक ओवरफ़्लो प्रलेखन सामग्री के संग्रह archive.org में पाया जा सकता है, जिसमें यह उदाहरण विषय आईडी 3261 और उदाहरण आईडी 11196 द्वारा अनुक्रमित है।)


10. फंक्शन कॉल

अब सबसे दिलचस्प हिस्सा। डेटा की तरह, निष्पादन योग्य कोड भी मेमोरी में संग्रहीत किया जाता है (स्टैक के लिए मेमोरी से पूरी तरह असंबंधित) और प्रत्येक निर्देश में एक पता होता है।
जब अन्यथा कमांड नहीं किया जाता है, तो सीपीयू एक के बाद एक निर्देशों को निष्पादित करता है, जिस क्रम में वे मेमोरी में संग्रहीत होते हैं। लेकिन हम CPU को मेमोरी में दूसरे स्थान पर "जंप" करने और वहां से निर्देशों को निष्पादित करने के लिए कमांड कर सकते हैं। Asm में यह कोई भी पता हो सकता है, और C ++ जैसी अधिक उच्च-स्तरीय भाषाओं में आप केवल लेबल द्वारा चिह्नित पते पर जा सकते हैं ( वर्कअराउंड हैं, लेकिन वे कम से कम कहने के लिए सुंदर नहीं हैं)।

आइए इस फ़ंक्शन ( स्निपेट # 10.1 ) को लें:

int myAlgo_withCalls(int a, int b) {
    int t1 = triple(a);
    int t2 = triple(b);
    return t1 - t2;
}

और trippleC ++ तरीके से कॉल करने के बजाय , निम्नलिखित करें:

  1. शरीर trippleकी शुरुआत के लिए कोड की प्रतिलिपि बनाएँmyAlgo
  2. पर myAlgoप्रवेश के ऊपर से छलांग trippleके साथ के कोडgoto
  3. जब हमें trippleकोड को निष्पादित करने की आवश्यकता होती है tripple, तो कॉल करने के बाद कोड लाइन के स्टैक पते पर सहेजें , इसलिए हम बाद में यहां लौट सकते हैं और निष्पादन जारी रख सकते हैं ( PUSH_ADDRESSनीचे मैक्रो)
  4. पहली पंक्ति ( trippleफ़ंक्शन) के पते पर जाएं और इसे अंत तक निष्पादित करें (3. और 4. साथ में CALLमैक्रो हैं)
  5. के अंत में tripple(हम स्थानीय लोगों को साफ करने के बाद), स्टैक के शीर्ष से वापसी का पता लें और वहां कूदें ( RETमैक्रो)

चूँकि C ++ में विशेष कोड एड्रेस पर कूदने का कोई आसान तरीका नहीं है, हम लेबल का उपयोग जम्प के स्थानों को चिह्नित करने के लिए करेंगे। मैं विस्तार से नहीं बताऊंगा कि मैक्रोज़ नीचे काम कैसे करते हैं, बस मुझे विश्वास है कि वे वही करते हैं जो मैं कहता हूँ कि वे करते हैं ( स्निपेट # 10.2 ):

// pushes the address of the code at label's location on the stack
// NOTE1: this gonna work only with 32-bit compiler (so that pointer is 32-bit and fits in int)
// NOTE2: __asm block is specific for Visual C++. In GCC use https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
#define PUSH_ADDRESS(labelName) {               \
    void* tmpPointer;                           \
    __asm{ mov [tmpPointer], offset labelName } \
    push(reinterpret_cast<int>(tmpPointer));    \
}

// why we need indirection, read https://stackoverflow.com/a/13301627/264047
#define TOKENPASTE(x, y) x ## y
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)

// generates token (not a string) we will use as label name. 
// Example: LABEL_NAME(155) will generate token `lbl_155`
#define LABEL_NAME(num) TOKENPASTE2(lbl_, num)

#define CALL_IMPL(funcLabelName, callId)    \
    PUSH_ADDRESS(LABEL_NAME(callId));       \
    goto funcLabelName;                     \
    LABEL_NAME(callId) :

// saves return address on the stack and jumps to label `funcLabelName`
#define CALL(funcLabelName) CALL_IMPL(funcLabelName, __LINE__)

// takes address at the top of stack and jump there
#define RET() {                                         \
    int tmpInt;                                         \
    pop(tmpInt);                                        \
    void* tmpPointer = reinterpret_cast<void*>(tmpInt); \
    __asm{ jmp tmpPointer }                             \
}

void myAlgo_asm() {
    goto my_algo_start;

triple_label:
    push(BP);
    BP = SP;
    SP -= 1;

    // stack[BP] == old BP, stack[BP + 1] == return address
    stack[BP - 1] = stack[BP + 2] * 3;
    AX = stack[BP - 1];

    SP = BP;     
    pop(BP);
    RET();

my_algo_start:
    push(BP);   // SP == 995
    BP = SP;    // BP == 995; stack[BP] == old BP, 
                // stack[BP + 1] == dummy return address, 
                // `a` at [BP + 2], `b` at [BP + 3]
    SP -= 2;    // SP == 993

    push(AX);
    push(stack[BP + 2]);
    CALL(triple_label);
    stack[BP - 1] = AX;
    SP -= 1;
    pop(AX);

    push(AX);
    push(stack[BP + 3]);
    CALL(triple_label);
    stack[BP - 2] = AX;
    SP -= 1;
    pop(AX);

    AX = stack[BP - 1] - stack[BP - 2];

    SP = BP; // cleanup locals, SP == 997
    pop(BP);
}

int main() {
    push(AX);
    push(22);
    push(11);
    push(7777); // dummy value, so that offsets inside function are like we've pushed return address
    myAlgo_asm();
    assert(myAlgo_withCalls(11, 22) == AX);
    SP += 1; // pop dummy "return address"
    SP += 2;
    pop(AX);
}

नोट्स:
10 ए। क्योंकि रिटर्न पता स्टैक पर संग्रहीत है, सिद्धांत रूप में हम इसे बदल सकते हैं। इस तरह से स्टैक स्मैशिंग हमला
10 बी काम करता है triple_label(क्लीनअप लोकल, पुराने बीपी को पुनर्स्थापित करें, वापसी) के अंतिम 3 निर्देशों को फ़ंक्शन का उपसंहार कहा जाता है


11. असेम्बली

अब के लिए वास्तविक asm को देखते हैं myAlgo_withCalls। दृश्य स्टूडियो में ऐसा करने के लिए:

  • x86 के लिए बिल्ड प्लेटफ़ॉर्म सेट करें ( x86_64 नहीं )
  • बिल्ड प्रकार: डीबग
  • myAlgo_withCalls के अंदर कहीं और ब्रेक पॉइंट सेट करें
  • रन, और जब निष्पादन ब्रेक पॉइंट पर रुक जाता है Ctrl + Alt + D दबाएं

हमारे asm- जैसे C ++ के साथ एक अंतर यह है कि asm का स्टैक ints के बजाय बाइट्स पर काम करता है। तो एक के लिए स्थान आरक्षित करने के लिए int, एसपी को 4 बाइट्स से घटाया जाएगा।
यहाँ हम जाते हैं ( स्निपेट # 11.1 , टिप्पणियों में लाइन नंबर, जीस्ट से हैं ):

;   114: int myAlgo_withCalls(int a, int b) {
 push        ebp        ; create stack frame 
 mov         ebp,esp  
; return address at (ebp + 4), `a` at (ebp + 8), `b` at (ebp + 12)
 
 sub         esp,0D8h   ; reserve space for locals. Compiler can reserve more bytes then needed. 0D8h is hexadecimal == 216 decimal 
 
 push        ebx        ; cdecl requires to save all these registers
 push        esi  
 push        edi  
 
 ; fill all the space for local variables (from (ebp-0D8h) to (ebp)) with value 0CCCCCCCCh repeated 36h times (36h * 4 == 0D8h)
 ; see https://stackoverflow.com/q/3818856/264047
 ; I guess that's for ease of debugging, so that stack is filled with recognizable values
 ; 0CCCCCCCCh in binary is 110011001100...
 lea         edi,[ebp-0D8h]     
 mov         ecx,36h    
 mov         eax,0CCCCCCCCh  
 rep stos    dword ptr es:[edi]  
 
;   115:    int t1 = triple(a);
 mov         eax,dword ptr [ebp+8]   ; push parameter `a` on the stack
 push        eax  
 
 call        triple (01A13E8h)  
 add         esp,4                   ; clean up param 
 mov         dword ptr [ebp-8],eax   ; copy result from eax to `t1`
 
;   116:    int t2 = triple(b);
 mov         eax,dword ptr [ebp+0Ch] ; push `b` (0Ch == 12)
 push        eax  
 
 call        triple (01A13E8h)  
 add         esp,4  
 mov         dword ptr [ebp-14h],eax ; t2 = eax
 
 mov         eax,dword ptr [ebp-8]   ; calculate and store result in eax
 sub         eax,dword ptr [ebp-14h]  

 pop         edi  ; restore registers
 pop         esi  
 pop         ebx  
 
 add         esp,0D8h  ; check we didn't mess up esp or ebp. this is only for debug builds
 cmp         ebp,esp  
 call        __RTC_CheckEsp (01A116Dh)  
 
 mov         esp,ebp  ; destroy frame
 pop         ebp  
 ret  

और इसके लिए tripple( स्निपेट # 11.2 ):

 push        ebp  
 mov         ebp,esp  
 sub         esp,0CCh  
 push        ebx  
 push        esi  
 push        edi  
 lea         edi,[ebp-0CCh]  
 mov         ecx,33h  
 mov         eax,0CCCCCCCCh  
 rep stos    dword ptr es:[edi]  
 imul        eax,dword ptr [ebp+8],3  
 mov         dword ptr [ebp-8],eax  
 mov         eax,dword ptr [ebp-8]  
 pop         edi  
 pop         esi  
 pop         ebx  
 mov         esp,ebp  
 pop         ebp  
 ret  

आशा है, इस पोस्ट को पढ़ने के बाद, विधानसभा पहले की तरह गूढ़ नहीं दिखेगी :)


यहाँ पोस्ट के शरीर से लिंक और कुछ आगे पढ़ने हैं:


यह एक लंबे समय पहले था कि मैंने यह पूछा, यह वास्तव में गहराई से जवाब में बहुत अच्छा है। धन्यवाद।
bplus

आप अपने उत्तर के शुरुआती भाग में रजिस्टरों के लिए 16-बिट नामों का उपयोग क्यों कर रहे हैं? यदि आप वास्तविक 16-बिट कोड के बारे में बात कर रहे थे, [SP]तो 16-बिट मोड के लिए एक मान्य एड्रेसिंग नहीं है। शायद सबसे अच्छा उपयोग करने के लिए ESP। इसके अलावा, अगर आप यह घोषणा SPएक के रूप में int, आप इसे 4 से प्रत्येक तत्व के लिए संशोधित किया जाना चाहिए, नहीं 1. (यदि आप घोषित long *SP, तो SP += 2द्वारा बढ़ा देते हैं 2 * sizeof(int), और इस तरह 2 तत्वों को हटा दें। लेकिन साथ intसपा, कि होना चाहिए SP += 8, जैसे add esp, 8। 32 में -एटीएम एसम।
पीटर कॉर्ड्स

चित्त आकर्षण करनेवाला! मुझे लगता है कि यह दिलचस्प है कि आप सी का उपयोग करके विधानसभा को समझाने की कोशिश करें। मैंने पहले ऐसा नहीं देखा। साफ। मैं "कैसे स्थानीय चर काम करता हूं" या "केवल स्थानीय चर" के रूप में "कोई स्थानीय चर" का नाम बदलने का सुझाव नहीं दे सकता।
डेव डॉपसन

@ क्रिकेटर 16-बिट नामों (एसपी, बीपी) का कारण स्पष्टता है - एसपी आसानी से "स्टैक पॉइंटर" का अनुवाद करता है। अगर मैं उचित 32-बिट नामों का उपयोग करता हूं, तो मुझे 16/32/64 बिट मोड के बीच के अंतर को समझाने या इसे अस्पष्टीकृत छोड़ने की आवश्यकता होगी। मेरा अभिप्राय यह था कि कोई व्यक्ति जो केवल जावा या पायथन जानता है, वह बिना सिर खुजाये पोस्ट का अनुसरण कर सकता है। और मुझे लगता है कि स्मृति संबोधन पाठक को विचलित कर देगा। साथ ही, मैंने जिज्ञासु के लिए विषय पर विकाइबूक लिंक डाला है और पोस्ट के अंत में ईएसपी के बारे में कुछ शब्द कहा है।
अलेक्जेंडर मालाखोव

1
उससे बचने के लिए, हमें एक एंकर इंडेक्स की आवश्यकता होती है, जो स्टैक बढ़ने पर बदलता नहीं है। आवश्यकता गलत शब्द है; -fomit-frame-pointerसालों से gcc और clang में डिफ़ॉल्ट रहा है। वास्तविक asm को देखने वाले लोगों को यह जानना होगा कि EBP / RBP को आमतौर पर फ्रेम पॉइंटर के रूप में उपयोग नहीं किया जाएगा। मैं कहता हूं "परंपरागत रूप से, मानव एक एंकर चाहते थे जो पुश / पॉप के साथ नहीं बदलता है, लेकिन कंपाइलर बदलते ऑफसेट का ट्रैक रख सकते हैं।" तब आप बैकग्राउंड के बारे में यह कहने के लिए कि यह विरासत विधि का उपयोग करके खंड का अद्यतन कर सकता है, जब DWARF .eh_frameमेटाडेटा या Windows x86-64 मेटाडेटा उपलब्ध नहीं है।
पीटर कॉर्डेस

7

हार्डवेयर में स्टैक लागू किया गया है या नहीं, इस बारे में विकिपीडिया लेख मदद कर सकता है।

कुछ प्रोसेसर परिवारों, जैसे कि x86, में वर्तमान में निष्पादित धागे के ढेर में हेरफेर करने के लिए विशेष निर्देश हैं। PowerPC और MIPS सहित अन्य प्रोसेसर परिवारों के पास स्पष्ट स्टैक समर्थन नहीं है, लेकिन इसके बजाय ऑपरेटिंग सिस्टम के एप्लिकेशन बाइनरी इंटरफ़ेस (ABI) के लिए कन्वेंशन और प्रतिनिधि स्टैक प्रबंधन पर भरोसा करते हैं।

यह लेख और अन्य यह लिंक प्रोसेसर में स्टैक के उपयोग के लिए एक अनुभव पाने के लिए उपयोगी हो सकता है।


4

संकल्पना

पहले पूरी बात सोचें जैसे कि आप वह व्यक्ति थे जिसने इसका आविष्कार किया था। ऐशे ही:

पहले एक सरणी के बारे में सोचें और इसे निम्न स्तर पर कैसे लागू किया जाता है -> यह मूल रूप से केवल सन्निहित स्मृति स्थानों (स्मृति स्थान जो एक दूसरे के बगल में हैं) का एक सेट है। अब जब आपके दिमाग में वह मानसिक छवि है, तो इस तथ्य के बारे में सोचें कि आप उन मेमोरी स्थानों में से किसी का भी उपयोग कर सकते हैं और इसे हटा सकते हैं। अब उसी सरणी के बारे में सोचें, लेकिन किसी भी स्थान को हटाने की संभावना के बजाय आप यह निर्णय लेते हैं कि आप अपने सरणी में डेटा हटाते या जोड़ते समय केवल LAST स्थान हटा देंगे। अब उस सरणी में डेटा में हेरफेर करने के आपके नए विचार को LIFO कहा जाता है जिसका अर्थ है लास्ट इन फर्स्ट आउट। आपका विचार बहुत अच्छा है क्योंकि इससे हर बार जब आप उसमें से कुछ निकालते हैं तो उस सरणी के कंटेंट पर नज़र रखना आसान हो जाता है। इसके अलावा, हर समय यह जानने के लिए कि सरणी में अंतिम ऑब्जेक्ट का पता क्या है, आप इसे ट्रैक करने के लिए सीपीयू में एक रजिस्टर समर्पित करते हैं। अब, जिस तरह से रजिस्टर होता है, उसका ट्रैक ऐसा रहता है कि हर बार जब आप हटाते हैं या अपने सरणी में कुछ जोड़ते हैं तो आप अपने रजिस्टर में पते का मूल्य घटाते या बढ़ाते हैं, जो आपके द्वारा हटाए गए या जोड़े गए सरणी से राशि के अनुसार पता स्थान की राशि, जिस पर उन्होंने कब्जा किया था)। आप यह भी सुनिश्चित करना चाहते हैं कि वह राशि जिसके द्वारा आप पंजीकरण करते हैं या रजिस्टर करते हैं, उस राशि को एक राशि के लिए तय किया जाता है (जैसे 4 मेमोरी स्थान यानी 4 बाइट्स) प्रति ऑब्जेक्ट, फिर से, इसे ट्रैक रखना आसान बनाने के लिए और इसे संभव बनाने के लिए भी। कुछ लूप कंस्ट्रक्शंस के साथ उस रजिस्टर का उपयोग करने के लिए क्योंकि लूप प्रति इरेक्शन प्रति निश्चित वृद्धि का उपयोग करते हैं (जैसे। लूप के साथ अपने एरे को ट्रूप करने के लिए आप लूप का निर्माण करते हैं जिससे आप अपने रजिस्टर को 4 प्रत्येक पुनरावृत्ति में बढ़ा सकते हैं, जो कि संभव नहीं होगा यदि आपके एरे में अलग-अलग आकार की वस्तुएं हों)। अंत में, आप इस नई डेटा संरचना को "स्टैक" कहना पसंद करते हैं, क्योंकि यह आपको एक रेस्तरां में प्लेटों के ढेर की याद दिलाता है जहां वे हमेशा उस स्टैक के शीर्ष पर एक प्लेट को हटाते हैं या जोड़ते हैं।

कार्यान्वयन

जैसा कि आप देख सकते हैं कि एक स्टैक सन्निहित स्मृति स्थानों की एक सरणी से अधिक कुछ नहीं है, जहां आपने तय किया था कि इसे कैसे हेरफेर करना है। उसके कारण आप देख सकते हैं कि स्टैक को नियंत्रित करने के लिए आपको विशेष निर्देशों और रजिस्टरों का उपयोग करने की आवश्यकता नहीं है। आप इसे इस तरह से ESP और EBP के बजाय मूल mov, add और sub निर्देशों और सामान्य प्रयोजन रजिस्टरों का उपयोग करके अपने आप को कार्यान्वित कर सकते हैं:

mov edx, 0FFFFFFFFh

; -> यह आपके कोड और डेटा से दूर, आपके स्टैक का प्रारंभ पता होगा, यह उस रजिस्टर के रूप में भी काम करेगा जो स्टैक में अंतिम ऑब्जेक्ट का ट्रैक रखता है जिसे मैंने पहले समझाया था। आप इसे "स्टैक पॉइंटर" कहते हैं, इसलिए आप ईडीपी को सामान्य रूप से उपयोग करने के लिए रजिस्टर ईडीएक्स चुनते हैं।

उप edx, ४

Mov [edx], dword ptr [someVar]

; -> ये दो निर्देश आपके स्टैक पॉइंटर को 4 मेमोरी लोकेशन से घटाएँगे और 4 बाइट्स को [someVar] मेमोरी लोकेशन से शुरू होने वाली मेमोरी लोकेशन पर कॉपी करेंगे, जिसे EDX अब इंगित करता है, जैसे PUSH इंस्ट्रक्शन ESP को घटाता है, केवल यहाँ आपने किया यह मैन्युअल रूप से और आपने EDX का उपयोग किया। इसलिए PUSH निर्देश मूल रूप से केवल एक छोटा ओपोड है जो वास्तव में ESP के साथ ऐसा करता है।

Mov eax, dword ptr [edx]

edx जोड़ें, ४

; -> और यहां हम विपरीत करते हैं, हम पहले मेमोरी स्थान पर शुरू होने वाले 4 बाइट्स की प्रतिलिपि बनाते हैं जो EDX अब रजिस्टर ईएक्सएक्स में इंगित करता है (मनमाने ढंग से यहां चुना गया, हम इसे कहीं भी कॉपी कर सकते थे जो हम चाहते थे)। और फिर हम अपने स्टैक पॉइंटर EDX को 4 मेमोरी लोकेशन से बढ़ाते हैं। पीओपी इंस्ट्रक्शन यही करता है।

अब आप देख सकते हैं कि PUSH और POP और रजिस्टर ESP ans EBP निर्देश इंटेल द्वारा केवल "स्टैक" डेटा संरचना की उपरोक्त अवधारणा को लिखने और पढ़ने के लिए आसान बनाने के लिए जोड़े गए थे। अभी भी कुछ RISC (कम किए गए निर्देश सेट) Cpu-s हैं जिनके पास PUSH ans POP निर्देश नहीं हैं और स्टैक हेरफेर के लिए समर्पित रजिस्टर हैं, और उन Cpu-s के लिए असेंबली प्रोग्राम लिखते समय आपको स्टैक को अपने जैसे ही लागू करना होगा मैंने तुम्हें दिखाया।


3

आप एक अमूर्त स्टैक और हार्डवेयर कार्यान्वित स्टैक को भ्रमित करते हैं। बाद वाला पहले से ही लागू है।


3

मुझे लगता है कि आप जिस मुख्य उत्तर की तलाश कर रहे हैं, वह पहले ही संकेत दे चुका है।

जब एक x86 कंप्यूटर बूट होता है, तो स्टैक सेटअप नहीं होता है। प्रोग्रामर को स्पष्ट रूप से इसे बूट समय पर सेट करना होगा। हालाँकि, यदि आप पहले से ही एक ऑपरेटिंग सिस्टम में हैं, तो इस बात का ध्यान रखा गया है। नीचे एक साधारण बूटस्ट्रैप प्रोग्राम से एक कोड नमूना है।

पहले डेटा और स्टैक सेगमेंट रजिस्टर सेट होते हैं, और फिर स्टैक पॉइंटर 0x4000 से परे सेट किया जाता है।


    movw    $BOOT_SEGMENT, %ax
    movw    %ax, %ds
    movw    %ax, %ss
    movw    $0x4000, %ax
    movw    %ax, %sp

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


3

स्टैक सिर्फ एक तरीका है कि प्रोग्राम और फ़ंक्शंस मेमोरी का उपयोग करते हैं।

स्टैक ने हमेशा मुझे भ्रमित किया, इसलिए मैंने एक चित्रण किया:

स्टैक स्टैलेक्टाइट्स की तरह है

( यहाँ svg संस्करण )


1

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


1

स्टैक पॉइंटर के माध्यम से स्टैक को "कार्यान्वित" किया जाता है, जो (x86 आर्किटेक्चर को यहां मानते हुए) स्टैक सेगमेंट में इंगित करता है । हर बार कुछ स्टैक पर धकेल दिया जाता है (पुशल, कॉल या एक समान स्टैक ओपकोड के माध्यम से), यह स्टैक पॉइंटर पॉइंट के पते को लिखा जाता है, और स्टैक पॉइंटर डीक्रिएटेड (स्टैक नीचे की ओर बढ़ रहा है , यानी छोटे पते) । जब आप ढेर (popl, सेवानिवृत्त) बंद कुछ पॉप, ढेर सूचक है वृद्धि और मूल्य ढेर बंद पढ़ें।

एक उपयोगकर्ता-स्पेस एप्लिकेशन में, जब आपका एप्लिकेशन शुरू होता है, तो स्टैक आपके लिए पहले से ही सेट होता है। एक कर्नेल-स्पेस वातावरण में, आपको स्टैक सेगमेंट और स्टैक पॉइंटर को पहले सेट करना होगा ...


1

मैंने गैस असेंबलर को विशेष रूप से नहीं देखा है, लेकिन सामान्य रूप से मेमोरी में स्थान के संदर्भ को बनाए रखते हुए स्टैक "कार्यान्वित" किया जाता है, जहां स्टैक के शीर्ष रहता है। मेमोरी लोकेशन को एक रजिस्टर में संग्रहीत किया जाता है, जिसमें विभिन्न आर्किटेक्चर के लिए अलग-अलग नाम होते हैं, लेकिन स्टैक पॉइंटर रजिस्टर के रूप में सोचा जा सकता है।

माइक्रो निर्देशों पर निर्माण करके पॉप और पुश कमांड आपके लिए अधिकांश आर्किटेक्चर में लागू किए जाते हैं। हालांकि, कुछ "एजुकेशनल आर्किटेक्चर" के लिए जरूरी है कि आप उन्हें अपने यहां लागू करें। कार्यात्मक रूप से, धक्का कुछ इस तरह से लागू किया जाएगा:

   load the address in the stack pointer register to a gen. purpose register x
   store data y at the location x
   increment stack pointer register by size of y

इसके अलावा, कुछ आर्किटेक्चर स्टैक पॉइंटर के रूप में अंतिम उपयोग किए गए मेमोरी एड्रेस को स्टोर करते हैं। कुछ अगले उपलब्ध पते को संग्रहीत करते हैं।


1

स्टैक क्या है? स्टैक एक प्रकार की डेटा संरचना है - कंप्यूटर में सूचनाओं को संग्रहीत करने का एक साधन। जब एक नई वस्तु को एक स्टैक में दर्ज किया जाता है, तो यह पहले से दर्ज सभी वस्तुओं के शीर्ष पर रखा जाता है। दूसरे शब्दों में, स्टैक डेटा संरचना कार्ड, पेपर, क्रेडिट कार्ड मेलिंग, या किसी अन्य वास्तविक दुनिया की वस्तुओं के ढेर की तरह है, जिनके बारे में आप सोच सकते हैं। स्टैक से किसी ऑब्जेक्ट को हटाते समय, सबसे ऊपर वाला पहला हटा दिया जाता है। इस विधि को LIFO (अंतिम में, पहले बाहर) के रूप में जाना जाता है।

"स्टैक" शब्द नेटवर्क प्रोटोकॉल स्टैक के लिए भी छोटा हो सकता है। नेटवर्किंग में, कंप्यूटर के बीच कनेक्शन छोटे कनेक्शन की एक श्रृंखला के माध्यम से किया जाता है। ये कनेक्शन, या परतें, स्टैक डेटा संरचना की तरह कार्य करते हैं, जिसमें वे उसी तरह से निर्मित और निपटाए जाते हैं।


0

आप सही हैं कि एक स्टैक एक डेटा संरचना है। अक्सर, डेटा स्ट्रक्चर्स (शामिल किए गए स्टैक) आपके साथ काम करते हैं और स्मृति में प्रतिनिधित्व के रूप में मौजूद होते हैं।

इस मामले में आप जिस स्टैक के साथ काम कर रहे हैं, उसका भौतिक अस्तित्व अधिक है- यह सीधे प्रोसेसर में वास्तविक भौतिक रजिस्टरों को मैप करता है। एक डेटा संरचना के रूप में, स्टाॅक FILO (पहली बार, आखिरी बाहर) संरचनाएं हैं जो यह सुनिश्चित करती हैं कि रिवर्स ऑर्डर में डेटा को हटा दिया गया था। एक दृश्य के लिए StackOverflow लोगो को देखें! ;)

आप निर्देश स्टैक के साथ काम कर रहे हैं । यह वास्तविक निर्देशों का ढेर है जो आप प्रोसेसर को खिला रहे हैं।


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

0

कॉल स्टैक x86 इंस्ट्रक्शन सेट और ऑपरेटिंग सिस्टम द्वारा कार्यान्वित किया जाता है।

पुश और पॉप जैसे निर्देश स्टैक पॉइंटर को समायोजित करते हैं जबकि ऑपरेटिंग सिस्टम मेमोरी को आवंटित करने का ख्याल रखता है क्योंकि स्टैक प्रत्येक थ्रेड के लिए बढ़ता है।

तथ्य यह है कि x86 स्टैक उच्च से निचले पते पर "बढ़ता है" इस वास्तुकला को बफर अतिप्रवाह हमले के लिए अतिसंवेदनशील बनाता है।


1
यह तथ्य क्यों है कि x86 स्टैक बढ़ता है यह बफर ओवरफ्लो के लिए अधिक अतिसंवेदनशील बनाता है? क्या आप एक सेगमेंट वाले सेगमेंट के साथ एक ही ओवरफ्लो नहीं कर सकते?
नाथन फेलमैन

@ नथन: यदि आप स्टैक पर मेमोरी की एक नकारात्मक राशि आवंटित करने के लिए केवल आवेदन प्राप्त कर सकते हैं।
जेवियर

1
बफ़र ओवरफ़्लो हमले स्टैक आधारित सरणी के अंत में लिखते हैं - char userName [256], यह मेमोरी को निम्न से उच्चतर तक लिखता है जो आपको रिटर्न एड्रेस जैसी चीजों को अधिलेखित करने देता है। यदि स्टैक एक ही दिशा में बढ़ता है, तो आप केवल असंबद्ध स्टैक को अधिलेखित करने में सक्षम होंगे।
मौरिस फलागन

0

आप सही हैं कि एक स्टैक एक डेटा संरचना 'सिर्फ' है। हालांकि, यह एक विशेष उद्देश्य के लिए उपयोग किए जाने वाले हार्डवेयर कार्यान्वित स्टैक को संदर्भित करता है - "द स्टैक"।

बहुत से लोगों ने हार्डवेयर लागू स्टैक बनाम (सॉफ़्टवेयर) स्टैक डेटा संरचना के बारे में टिप्पणी की है। मैं जोड़ना चाहूंगा कि तीन प्रमुख स्टैक संरचना प्रकार हैं -

  1. एक कॉल स्टैक - जो आप के बारे में पूछ रहे हैं! यह फंक्शन पैरामीटर्स और रिटर्न एड्रेस आदि को स्टोर करता है। उस किताब में चैप्टर 4 (सभी चौथे पेज यानी पेज 53) फंक्शन्स को पढ़ें। एक अच्छी व्याख्या है।
  2. एक जेनेरिक स्टैक जिसका उपयोग आप अपने कार्यक्रम में कुछ विशेष करने के लिए कर सकते हैं ...
  3. एक सामान्य हार्डवेयर स्टैक
    मुझे इस बारे में निश्चित नहीं है, लेकिन मुझे याद है कि कुछ आर्किटेक्चर में उपलब्ध सामान्य प्रयोजन हार्डवेयर कार्यान्वित स्टैक है। अगर किसी को पता है कि यह सही है, तो कृपया टिप्पणी करें।

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


0

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


0

stackस्मृति का हिस्सा है। यह inputऔर outputके लिए उपयोग करें functions। यह भी समारोह की वापसी को याद करने के लिए उपयोग करें।

esp रजिस्टर स्टैक पता याद है।

stackऔर espहार्डवेयर द्वारा कार्यान्वित किया जाता है। भी आप इसे अपने आप को लागू कर सकते हैं। यह आपके प्रोग्राम को बहुत धीमा कर देगा।

उदाहरण:

एनओपी // esp= 0012ffc4

पुश 0 // esp= 0012ffc0, Dword [0012ffc0] = 00000000

कॉल proc01 // esp= 0012ffbc, Dword [0012ffbc] = eip, eip= adrr [proc01]

pop eax// eax= Dword [ esp], esp= esp+ 4


0

मैं इस बारे में खोज कर रहा था कि फ़ंक्शन के संदर्भ में स्टैक कैसे काम करता है और मैंने इस ब्लॉग को इसकी भयानक और इसके स्टैक की अवधारणा को खरोंच से और स्टैक के स्टोर मूल्य को स्टैक में कैसे पाया।

अब आपके जवाब पर मैं अजगर के साथ समझाऊंगा लेकिन आपको अच्छा विचार मिलेगा कि किसी भी भाषा में स्टैक कैसे काम करता है।

यहां छवि विवरण दर्ज करें

इसका एक कार्यक्रम:

def hello(x):
    if x==1:
        return "op"
    else:
        u=1
        e=12
        s=hello(x-1)
        e+=1
        print(s)
        print(x)
        u+=1
    return e

hello(3)

यहां छवि विवरण दर्ज करें

यहां छवि विवरण दर्ज करें

स्रोत: क्रिप्टोकरंसी

इसके कुछ विषय जो इसे ब्लॉग में शामिल करते हैं:

How Function work ?
Calling a Function
 Functions In a Stack
 What is Return Address
 Stack
Stack Frame
Call Stack
Frame Pointer (FP) or Base Pointer (BP)
Stack Pointer (SP)
Allocation stack and deallocation of stack
StackoverFlow
What is Heap?

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


Criptoix साइट मृत है और web.archive.org पर इसकी कोई प्रति नहीं है
अलेक्जेंडर मालाखोव

1
@AlexanderMalakhov Cryptroix होस्टिंग इश्यू के कारण काम नहीं कर रहा था। Cryptroix अभी काम कर रहा है।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.