কলস্ট্যাকটি ঠিক কীভাবে কাজ করে?


103

আমি প্রোগ্রামিং ভাষার নিম্ন স্তরের অপারেশনগুলি কীভাবে কাজ করে এবং বিশেষত তারা ওএস / সিপিইউয়ের সাথে কীভাবে ইন্টারেক্ট করে তা আরও গভীরভাবে বোঝার চেষ্টা করছি। আমি সম্ভবত স্ট্যাক ওভারফ্লোতে এখানে প্রতিটি স্ট্যাক / হিপ সম্পর্কিত থ্রেডের প্রতিটি উত্তর পড়েছি এবং সেগুলি সব উজ্জ্বল। তবে এখনও একটি জিনিস আছে যা আমি এখনও পুরোপুরি বুঝতে পারি নি।

সিউডো কোডে এই ফাংশনটি বিবেচনা করুন যা বৈধ মরচে কোড ;-) হতে পারে

fn foo() {
    let a = 1;
    let b = 2;
    let c = 3;
    let d = 4;

    // line X

    doSomething(a, b);
    doAnotherThing(c, d);
}

এক্স স্ট্রাইকটির মতো দেখতে এই স্ট্যাকটি আমি এইভাবে ধরেছি:

Stack

a +-------------+
  | 1           | 
b +-------------+     
  | 2           |  
c +-------------+
  | 3           | 
d +-------------+     
  | 4           | 
  +-------------+ 

এখন, স্ট্যাকটি কীভাবে কাজ করে সে সম্পর্কে আমি যা কিছু পড়েছি তা হ'ল এটি লিফোর নিয়মগুলি কঠোরভাবে মান্য করে (শেষ পর্যন্ত, প্রথমে আউট)। নেট, জাভা বা অন্য কোনও প্রোগ্রামিং ল্যাঙ্গুয়েজে স্ট্যাক ডেটাটাইপের মতো।

তবে তা যদি হয় তবে এক্স লাইনের পরে কী হবে? কারণ স্পষ্টত, পরবর্তী জিনিস আমরা প্রয়োজন সঙ্গে কাজ করার aএবং bকিন্তু অর্থ দাঁড়ায় এই যে যা OS / CPU- র (?) পপ আউট হয়েছে dএবং cপ্রথম অবস্থায় ফিরিয়ে আনতে aএবং b। তবে তারপরে এটি নিজেই পায়ে গুলি করবে, কারণ এটির প্রয়োজন cএবং dপরবর্তী লাইনে।

তো, আমি আশ্চর্য হই যে পর্দার আড়ালে ঠিক কী ঘটে?

আরও একটি সম্পর্কিত প্রশ্ন। বিবেচনা করুন আমরা এর মতো অন্যান্য কার্যগুলির একটিতে একটি রেফারেন্স পাস করি:

fn foo() {
    let a = 1;
    let b = 2;
    let c = 3;
    let d = 4;

    // line X

    doSomething(&a, &b);
    doAnotherThing(c, d);
}

আমি কীভাবে জিনিসগুলি বুঝি তার থেকে এর অর্থ হবে যে প্যারামিটারগুলি doSomethingমূলত একই মেমরি ঠিকানার মতো aএবং bভিতরে ইশারা করে foo। তবে তারপরে আবার এর অর্থ হ'ল আমাদের কাছে পৌঁছে যাওয়া aএবং হওয়া অবধি স্ট্যাকেরb কোনও পপ আপ নেই ।

এই দুটি ক্ষেত্রে আমাকে ভাবতে বাধ্য করে যে স্ট্যাকটি ঠিক কীভাবে কাজ করে এবং কীভাবে এটি লিফোর নিয়মকে কঠোরভাবে অনুসরণ করে আমি পুরোপুরি বুঝতে পারি নি ।


14
LIFO কেবল স্ট্যাকের মধ্যে স্থান সংরক্ষণের জন্য গুরুত্বপূর্ণ। আপনি সর্বদা আপনার স্ট্যাক ফ্রেমে (ফাংশনের অভ্যন্তরে ঘোষিত) কোনও ভেরিয়েবল অ্যাক্সেস করতে পারেন এমনকি যদি এটি প্রচুর অন্যান্য ভেরিয়েবলের অধীনে থাকে
ভয়েডস্টার

2
অন্য কথায়, এর LIFOঅর্থ আপনি কেবল স্ট্যাকের শেষে উপাদানগুলি যুক্ত করতে বা সরাতে পারবেন এবং আপনি যে কোনও উপাদান সর্বদা পড়তে / পরিবর্তন করতে পারবেন।
হলিব্ল্যাকগেট

12
আপনি -O0 দিয়ে সংকলন করার পরে উত্পন্ন নির্দেশাবলীর দিকে তাকানোর পরে কেন একটি সাধারণ ক্রিয়াকলাপটি বিচ্ছিন্ন করেন না? এটি সুন্দর, ভাল, শিক্ষামূলক ;-)। আপনি দেখতে পাবেন যে কোডটি র‌্যামের আর অংশটির ভাল ব্যবহার করে; এটি সরাসরি ইচ্ছায় ঠিকানাগুলি অ্যাক্সেস করে। আপনি কোনও ঠিকানা নিবন্ধের (স্ট্যাক পয়েন্টার) অফসেট হিসাবে চলক নামটি ভাবতে পারেন। অন্যরা যেমন বলেছিল, স্ট্যাকটি স্ট্যাকিংয়ের ক্ষেত্রে কেবল লাইফো (পুনরাবৃত্তির জন্য ভাল)। এটি অ্যাক্সেস করার ক্ষেত্রে এটি লাইফো নয়। অ্যাক্সেস সম্পূর্ণ এলোমেলো।
পিটার - মনিকা

6
আপনি একটি অ্যারের ব্যবহার করে আপনার নিজের স্ট্যাকের ডেটা স্ট্রাকচার তৈরি করতে পারেন, এবং কেবল শীর্ষ উপাদানটির সূচকটি সংরক্ষণ করে, যখন আপনি ধাক্কা দেন তখন এটি বৃদ্ধি করে, যখন আপনি পপ করেন তখন এটি হ্রাস করে। আপনি যদি এটি করেন তবে আপনি যেভাবে অ্যারে দিয়ে সর্বদা করতে পারেন ঠিক তেমনভাবে আপনি যেকোন সময় অ্যারেতে কোনও পৃথক উপাদান অ্যাক্সেস করতে সক্ষম হবেন। প্রায় একই জিনিস এখানে ঘটছে।
ক্রোম্যান

3
মূলত, স্ট্যাক / গাদা নামকরণ দুর্ভাগ্যজনক। তারা ডেটা স্ট্রাকচারের পরিভাষায় স্ট্যাক এবং গাদা করার ক্ষেত্রে সামান্য সাদৃশ্য রাখে, তাই তাদের একই বলা খুব বিভ্রান্তিকর।
সিয়ুয়ান রেন

উত্তর:


117

কল স্ট্যাকটিকে ফ্রেম স্ট্যাকও বলা যেতে পারে।
যে জিনিস হয় স্তুপীকৃত ছিল LIFO নীতি পর স্থানীয় ভেরিয়েবল কিন্তু ফাংশন সমগ্র স্ট্যাকের ফ্রেম ( "কল") নামক হচ্ছে না । স্থানীয় ভেরিয়েবলগুলি যথাক্রমে তথাকথিত ফাংশন প্রোলোগ এবং এপিলেগের সেই ফ্রেমগুলির সাথে একসাথে ধাক্কা দেওয়া এবং পপ করা হয়।

ফ্রেমের ভিতরে ভেরিয়েবলের ক্রম সম্পূর্ণরূপে অনির্ধারিত; সংকলকগুলি তাদের ফ্রেমটির অভ্যন্তরে স্থানীয় ভেরিয়েবলগুলির অবস্থানগুলি যথাযথভাবে "পুনর্বিন্যাস" করে যাতে তাদের প্রান্তিককরণটি অনুকূলিত করা যায় যাতে প্রসেসর যত তাড়াতাড়ি তাদের এনে দিতে পারে। গুরুত্বপূর্ণ ঘটনাটি হ'ল কিছু স্থির ঠিকানার সাথে সম্পর্কিত ভেরিয়েবলগুলির অফসেট ফ্রেমের জীবদ্দশায় স্থির থাকে - সুতরাং এটি কোনও অ্যাঙ্কর ঠিকানা নেওয়া, বলা, ফ্রেমের ঠিকানা নিজেই গ্রহণ করা এবং সেই ঠিকানার অফসেটের সাথে কাজ করার পক্ষে যথেষ্ট ices ভেরিয়েবল। যেমন একটি অ্যাঙ্কর ঠিকানা আসলে তথাকথিত বেস বা ফ্রেম পয়েন্টার মধ্যে থাকেযা ইবিপি রেজিস্টারে সংরক্ষিত আছে। অন্যদিকে, অফসেটগুলি সংকলনের সময় স্পষ্টভাবে পরিচিত এবং তাই মেশিন কোডে হার্ডকোড করা হয়।

উইকিপিডিয়া থেকে প্রাপ্ত এই গ্রাফিকটি দেখায় যে টিপিকাল কল স্ট্যাকটি 1 এর মতো কী কাঠামোগত হয় :

একটি স্ট্যাকের ছবি

ফ্রেম পয়েন্টারে থাকা ঠিকানার অ্যাক্সেসে আমরা একটি ভেরিয়েবলের অফসেট যুক্ত করুন এবং আমরা আমাদের ভেরিয়েবলের ঠিকানা পাই। সুতরাং শীঘ্রই বলেছি, কোডটি কেবলমাত্র বেস পয়েন্টার থেকে ধ্রুবক সংকলন-সময় অফসেটগুলির মাধ্যমে তাদের অ্যাক্সেস করে; এটি সরল পয়েন্টার গাণিতিক।

উদাহরণ

#include <iostream>

int main()
{
    char c = std::cin.get();
    std::cout << c;
}

gcc.godbolt.org আমাদের দেয়

main:
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp

    movl    std::cin, %edi
    call    std::basic_istream<char, std::char_traits<char> >::get()
    movb    %al, -1(%rbp)
    movsbl  -1(%rbp), %eax
    movl    %eax, %esi
    movl    std::cout, %edi
    call    [... the insertion operator for char, long thing... ]

    movl    $0, %eax
    leave
    ret

.. জন্য main। আমি কোডটি তিনটি বিভাগে বিভক্ত করেছি। প্রথম তিনটি ক্রিয়াকলাপটি ফাংশন প্রচারে থাকে:

  • বেস পয়েন্টারটি স্ট্যাকের দিকে ঠেলে দেওয়া হয়।
  • স্ট্যাক পয়েন্টারটি বেস পয়েন্টারে সংরক্ষণ করা হয়
  • স্থানীয় ভেরিয়েবলের জন্য জায়গা তৈরি করতে স্ট্যাক পয়েন্টারটি বিয়োগ করা হয়।

তারপরে cinইডিআই রেজিস্টার 2 এ সরানো হয় এবং getতাকে বলা হয়; ফেরতের মান EAX এ in

এ পর্যন্ত সব ঠিকই. আকর্ষণীয় জিনিসটি ঘটে:

EAX এর নিম্ন-অর্ডার বাইট, 8-বিট রেজিস্টার AL দ্বারা মনোনীত, বেস পয়েন্টারের ঠিক পরে বাইটে নিয়ে যায় এবং সংরক্ষণ করা হয় : এটি হল -1(%rbp)বেস পয়েন্টারের অফসেট -1এই বাইটটি আমাদের পরিবর্তনশীলc । অফসেটটি নেতিবাচক কারণ স্ট্যাকটি নীচের দিকে x86 এর উপরে বৃদ্ধি পায়। cEAX- এর পরবর্তী অপারেশন স্টোরগুলি: EAX ESI coutএ স্থানান্তরিত হয়, EDI তে সরানো হয় এবং তারপরে সন্নিবেশ অপারেটরটি ডাকা হয় coutএবং cযুক্তিযুক্ত হয়ে থাকে।

অবশেষে,

  • এর রিটার্ন মান mainEAX এ সংরক্ষণ করা হয়: 0 এটি অন্তর্নিহিত returnবক্তব্যের কারণে। xorl rax raxপরিবর্তে আপনি দেখতে পারেন movl
  • ছেড়ে এবং কল সাইটে ফিরে। leaveএই পর্বটি সংক্ষিপ্ত বিবরণ দিচ্ছে এবং অন্তর্নিহিতভাবে
    • বেস পয়েন্টার এবং স্ট্যাক পয়েন্টার প্রতিস্থাপন করে
    • বেস পয়েন্টারটি পোপ করে।

এই অপারেশনটির পরে এবং retসঞ্চালিত হওয়ার পরে , ফ্রেমটি কার্যকরভাবে পপ করা হয়েছে, যদিও আমরা সিডেস্কল কলিং কনভেনশনটি ব্যবহার করছি বলে কলারের এখনও তর্কগুলি পরিষ্কার করতে হবে। অন্যান্য কনভেনশন, যেমন স্টডক্যাল, কলিটিকে পরিপাটি করা প্রয়োজন, যেমন বাইটের পরিমাণটি পাস করে ret

ফ্রেম পয়েন্টার ওমিশন

বেস / ফ্রেম পয়েন্টার থেকে স্ট্যাক পয়েন্টার (ইএসবি) থেকে অফসেট ব্যবহার না করাও সম্ভব। এটি EBP- রেজিস্টার করে যা অন্যথায় নির্বিচারে ব্যবহারের জন্য ফ্রেম পয়েন্টার মানটি উপলব্ধ রাখে - তবে এটি কিছু মেশিনে ডিবাগিংকে অসম্ভব করে তুলতে পারে এবং কিছু ফাংশনের জন্য স্পষ্টভাবে বন্ধ হয়ে যাবে । X86 সহ কয়েকটি রেজিস্টার সহ প্রসেসরের সংকলন করার সময় এটি বিশেষত কার্যকর।

এই অপ্টিমাইজেশন এফপিও হিসাবে পরিচিত (ফ্রেম পয়েন্টার বাদ) এবং -fomit-frame-pointerজিসিসি এবং -Oyক্ল্যাং এ সেট করেছেন ; মনে রাখবেন যে এটি প্রতিটি অপ্টিমাইজেশন স্তর> 0 দ্বারা স্পষ্টভাবে ট্রিগার করা হয়েছে এবং কেবলমাত্র যদি ডিবাগিং এখনও সম্ভব হয়, যেহেতু এর ব্যতীত কোনও ব্যয় নেই। আরও তথ্যের জন্য এখানে এবং এখানে দেখুন


1 মন্তব্যে নির্দেশিত হিসাবে, ফ্রেম পয়েন্টারটি সম্ভবত প্রত্যাবর্তনের ঠিকানার পরে ঠিকানার দিকে নির্দেশ করা।

2 দ্রষ্টব্য যে আর-এর সাথে শুরু হওয়া নিবন্ধগুলি হ'ল ই-এর সাথে শুরু হওয়া of৪-বিট সমকক্ষ E ইএএএক্স র‌্যাক্সের চারটি নিম্ন-অর্ডার বাইট নির্ধারণ করে। আমি স্পষ্টতার জন্য 32-বিট নিবন্ধগুলির নাম ব্যবহার করেছি।


1
দুর্দান্ত উত্তর। অফসেটের মাধ্যমে ডেটা সম্বোধনের জিনিসটি আমার জন্য অনুপস্থিত বিট ছিল :)
ক্রিস্টোফ

1
আমি মনে করি অঙ্কনটিতে একটি ছোটখাটো ভুল আছে। ফ্রেম পয়েন্টারটি ফেরতের ঠিকানার অন্যদিকে থাকতে হবে। ফাংশন ছেড়ে যাওয়া সাধারণত নিম্নলিখিতভাবে করা হয়: ফ্রেম পয়েন্টারে স্ট্যাক পয়েন্টার সরান, স্ট্যাক থেকে কলার ফ্রেম পয়েন্টারটি পপ করুন, ফিরে
আসুন

ক্যাস্পারড একদম ঠিক আছে। আপনি হয় মোটেও ফ্রেম পয়েন্টার ব্যবহার করবেন না (বৈধ অপ্টিমাইজেশন এবং বিশেষত রেজিস্টার-অনাহারে আর্কিটেকচার যেমন x86 অত্যন্ত দরকারী) বা আপনি এটি ব্যবহার করেন এবং পূর্ববর্তীটি স্ট্যাকের মধ্যে সংরক্ষণ করেন - সাধারণত ফেরতের ঠিক ঠিক পরে after ফ্রেম কীভাবে সেট আপ এবং সরানো হয় তা আর্কিটেকচার এবং এবিআইয়ের উপর একটি দুর্দান্ত চুক্তির উপর নির্ভর করে। বেশ কয়েকটি স্থাপত্য রয়েছে (হ্যালো ইটানিয়াম) যেখানে পুরো জিনিসটি .. আরও আকর্ষণীয় (এবং এখানে পরিবর্তনশীল আকারের যুক্তি তালিকার মতো জিনিস রয়েছে!)
ভু

3
@ ক্রিসটফ আমার মনে হয় আপনি ধারণার দিক থেকে এটি কাছে এসেছেন। এখানে একটি মন্তব্য যা আশা করে এটি পরিষ্কার হয়ে যাবে - আরটিএস, বা রানটাইম স্ট্যাক, এটি অন্য একটি স্ট্যাকের থেকে কিছুটা আলাদা, এটি একটি "নোংরা স্ট্যাক" - সেখানে এমন কোনও মানের দিকে নজর দেওয়া থেকে বিরত আসলে কিছুই নেই ' শীর্ষে টি। লক্ষ্য করুন যে চিত্রটিতে, সবুজ পদ্ধতির জন্য "রিটার্ন ঠিকানা" - যা নীল পদ্ধতি দ্বারা প্রয়োজন! পরামিতি পরে হয়। আগের ফ্রেমটি পপ করার পরে নীল পদ্ধতি কীভাবে রিটার্নের মান পাবে? ঠিক আছে, এটি একটি নোংরা স্ট্যাক, সুতরাং এটি কেবল পৌঁছতে পারে এবং এটি ধরে নিতে পারে।
Riking

1
ফ্রেম পয়েন্টারটি আসলে প্রয়োজন হয় না কারণ তার পরিবর্তে স্ট্যাক পয়েন্টার থেকে সর্বদা অফসেট ব্যবহার করা যায়। জিসিসি x64 আর্কিটেকচারকে ডিফল্টরূপে লক্ষ্য করে স্ট্যাক পয়েন্টার ব্যবহার করে এবং rbpঅন্যান্য কাজ করতে মুক্ত করে ।
সিয়ুয়ান রেন

27

কারণ স্পষ্টতই, আমাদের পরবর্তী জিনিসটি দরকার একটি এবং বি নিয়ে কাজ করা তবে এর অর্থ হ'ল ওএস / সিপিইউ (?) কে প্রথমে ডি এবং সি পপ আউট করতে হবে a এবং b এ ফিরে যেতে। তবে তারপরে এটি নিজেই পায়ে গুলি করবে কারণ পরবর্তী লাইনে এটি সি এবং ডি দরকার।

সংক্ষেপে:

যুক্তিগুলি পপ করার দরকার নেই। কলারের fooমাধ্যমে কাজ করার জন্য আর্গুমেন্টগুলি doSomethingএবং স্থানীয় ভেরিয়েবলগুলি doSomething সমস্ত বেস পয়েন্টার থেকে অফসেট হিসাবে উল্লেখ করা যেতে পারে
সুতরাং,

  • যখন কোনও ফাংশন কল করা হয়, তখন ফাংশনের যুক্তিগুলি স্ট্যাকের উপর পুশ করা হয়। এই যুক্তিগুলি আরও বেস পয়েন্টার দ্বারা রেফারেন্স করা হয়।
  • যখন ফাংশনটি তার কলারে ফিরে আসে, ফিরে আসার ফাংশনের যুক্তিগুলি LIFO পদ্ধতিটি ব্যবহার করে স্ট্যাক থেকে পোপ করা হয়।

বিস্তারিত:

নিয়মটি হ'ল প্রতিটি ফাংশন কলের ফলাফল স্ট্যাক ফ্রেম তৈরি করে (যার সর্বনিম্ন ফিরে আসার ঠিকানা থাকে)। সুতরাং, যদি funcAকল funcBএবং funcBকল হয় funcC, তিনটি স্ট্যাক ফ্রেম অন্যটির উপরে একটি সেট আপ করা হয়। যখন কোনও ফাংশন ফিরে আসে, তখন এর ফ্রেমটি অবৈধ হয়ে যায় । একটি ভাল আচরণযুক্ত ক্রিয়াকলাপটি কেবল তার নিজের স্ট্যাক ফ্রেমে কাজ করে এবং অন্যের প্রতি অবিরাম হয় না। অন্য কথায় পপিং শীর্ষে স্ট্যাক ফ্রেমে করা হয় (ফাংশন থেকে ফিরে আসার সময়)।

এখানে চিত্র বর্ণনা লিখুন

আপনার প্রশ্নের স্ট্যাকটি কলার দ্বারা সেটআপ করা আছে foo। যখন doSomethingএবং doAnotherThingডাকা হয়, তারপরে তারা তাদের নিজস্ব স্ট্যাক সেটআপ করে। চিত্রটি আপনাকে এটি বুঝতে সাহায্য করতে পারে:

এখানে চিত্র বর্ণনা লিখুন

মনে রাখবেন, আর্গুমেন্টগুলি অ্যাক্সেস করতে, ফাংশন বডিটি রিটার্নের ঠিকানাটি সংরক্ষণ করা হয়েছে এমন জায়গা থেকে (উচ্চ ঠিকানাগুলি) অতিক্রম করতে হবে এবং স্থানীয় ভেরিয়েবলগুলি অ্যাক্সেস করতে, ফাংশন বডিটি স্ট্যাকটি অতিক্রম করতে হবে (নিম্ন ঠিকানাগুলি) ) রিটার্নের ঠিকানাটি সংরক্ষণ করা হয়েছে সেই অবস্থানের সাথে সম্পর্কিত। আসলে, ফাংশনের জন্য সাধারণ সংকলক উত্পন্ন কোড ঠিক এটি করবে। সংকলক এটির (বেস পয়েন্টার) জন্য ইবিপি নামে একটি নিবন্ধক উত্সর্গ করে। একই জন্য অন্য নাম ফ্রেম পয়েন্টার। সংকলকটি সাধারণত ফাংশন বডির প্রথম জিনিস হিসাবে বর্তমান EBP মানটিকে স্ট্যাকের দিকে ঠেলে দেয় এবং EBP কে বর্তমান ESP তে সেট করে। এর অর্থ, ফাংশন কোডের যে কোনও অংশে এটি শেষ হয়ে গেলে, আর্গুমেন্ট 1 হ'ল EBP + 8 দূরে (প্রতিটি কলার এর EBP এবং প্রত্যাবর্তনের ঠিকানার জন্য 4 বাইট), আর্গুমেন্ট 2 EBP + 12 (দশমিক) দূরে, স্থানীয় ভেরিয়েবল হয় EBP-4n দূরে আছে

.
.
.
[ebp - 4]  (1st local variable)
[ebp]      (old ebp value)
[ebp + 4]  (return address)
[ebp + 8]  (1st argument)
[ebp + 12] (2nd argument)
[ebp + 16] (3rd function argument) 

ফাংশনের স্ট্যাক ফ্রেম গঠনের জন্য নিম্নলিখিত সি কোডটি একবার দেখুন:

void MyFunction(int x, int y, int z)
{
     int a, int b, int c;
     ...
}

ফোন করার সময় ফোন করুন

MyFunction(10, 5, 2);  

নিম্নলিখিত কোড উত্পন্ন করা হবে

^
| call _MyFunction  ; Equivalent to: 
|                   ; push eip + 2
|                   ; jmp _MyFunction
| push 2            ; Push first argument  
| push 5            ; Push second argument  
| push 10           ; Push third argument  

এবং ফাংশনটির জন্য সমাবেশ কোডটি হবে (ফিরে আসার আগে কলি দ্বারা সেট আপ)

^
| _MyFunction:
|  sub esp, 12 ; sizeof(a) + sizeof(b) + sizeof(c)
|  ;x = [ebp + 8], y = [ebp + 12], z = [ebp + 16]
|  ;a = [ebp - 4] = [esp + 8], b = [ebp - 8] = [esp + 4], c = [ebp - 12] =   [esp]
|  mov ebp, esp
|  push ebp
 

তথ্যসূত্র:


1
আপনার উত্তর করার জন্য আপনাকে ধন্যবাদ। এছাড়াও লিঙ্কগুলি সত্যিই দুর্দান্ত এবং কম্পিউটারগুলি কীভাবে কাজ করে তার কখনই শেষ না হওয়া প্রশ্নে আরও আলোকপাত করতে সহায়তা করে :)
ক্রিস্টোফ

আপনি কী বলতে চাইছেন "বর্তমান ইবিপি মানটিকে স্ট্যাকের উপর চাপ দেয়" এবং স্ট্যাক পয়েন্টারটিও রেজিস্টারে সংরক্ষণ করা হয় বা এটি স্ট্যাকের একটি অবস্থান দখল করে ... আমি কিছুটা বিভ্রান্ত
সুরজ জৈন

এবং এটি * [ইবিপি + 8] হওয়া উচিত নয় [ইবিপি + 8]?
সুরজ জৈন

@ সুরজ জৈন; আপনি কি জানেন EBPএবং কি ESP?
হ্যাক

esp হ'ল স্ট্যাক পয়েন্টার এবং ebp হল বেস পয়েন্টার। আমার যদি কিছু মিস জ্ঞান থাকে তবে দয়া করে দয়া করে এটি সংশোধন করুন।
সুরজ জৈন

19

অন্যদের মতো উল্লিখিত, প্যারামিটারগুলি পপ করার দরকার নেই, যতক্ষণ না তারা সুযোগের বাইরে চলে যায়।

আমি নিক পার্লান্টের "পয়েন্টারস এবং মেমোরি" থেকে কিছু উদাহরণ পেস্ট করব। আমার ধারণা আপনি পরিস্থিতিটি কল্পনা করা থেকে কিছুটা সহজ।

এখানে কোড:

void X() 
{
  int a = 1;
  int b = 2;

  // T1
  Y(a);

  // T3
  Y(b);

  // T5
}

void Y(int p) 
{
  int q;
  q = p + 2;
  // T2 (first time through), T4 (second time through)
}

সময় পয়েন্ট T1, T2, etc। কোডটিতে চিহ্নিত করা হয়েছে এবং সেই সময়ে মেমরির স্থিতিটি অঙ্কনটিতে দেখানো হয়েছে:

এখানে চিত্র বর্ণনা লিখুন


2
দুর্দান্ত দর্শনীয় ব্যাখ্যা। আমি এখানে কাগজটি গুগল করে খুঁজে পেয়েছি: cslibrary.stanford.edu/102/PointersAndMemory.pdf সত্যিই সহায়ক কাগজ!
ক্রিস্টোফ

7

বিভিন্ন প্রসেসর এবং ভাষা কয়েকটি পৃথক স্ট্যাক ডিজাইন ব্যবহার করে। 8x86 এবং 68000 উভয় ক্ষেত্রে দুটি traditionalতিহ্যবাহী নিদর্শনকে বলা হয় পাস্কাল কলিং কনভেনশন এবং সি কলিং কনভেনশন; প্রতিটি কনভেনশন উভয় প্রসেসরে একইভাবে পরিচালনা করা হয়, রেজিস্টারের নাম বাদে। স্ট্যাক এবং সম্পর্কিত ভেরিয়েবলগুলি পরিচালনা করার জন্য প্রত্যেকে দুটি রেজিস্টার ব্যবহার করে, যাকে বলা হয় স্ট্যাক পয়েন্টার (এসপি বা এ 7) এবং ফ্রেম পয়েন্টার (বিপি বা এ 6)।

উভয় কনভেনশন ব্যবহার করে সাব্রোটিন কল করার সময়, রুটিন কল করার আগে কোনও পরামিতি স্ট্যাকের উপরে চাপ দেওয়া হয়। রুটিনের কোডটি তারপরে ফ্রেমের পয়েন্টারটির বর্তমান মানটিকে স্ট্যাকের দিকে ঠেলে দেয়, স্ট্যাক পয়েন্টারের বর্তমান মান ফ্রেম পয়েন্টারটিতে অনুলিপি করে এবং স্ট্যাক পয়েন্টার থেকে স্থানীয় ভেরিয়েবল [যদি থাকে] দ্বারা ব্যবহৃত বাইটের সংখ্যা বিয়োগ করে। এটি সম্পন্ন হয়ে গেলে, অতিরিক্ত তথ্য স্ট্যাকের দিকে ধাক্কা দেওয়া হলেও, সমস্ত স্থানীয় ভেরিয়েবলগুলি স্ট্যাক পয়েন্টার থেকে ধ্রুবক নেতিবাচক স্থানচ্যুতি সহ ভেরিয়েবলগুলিতে সংরক্ষণ করা হবে এবং কলারের দ্বারা স্ট্যাকের উপরে চাপানো সমস্ত প্যারামিটারগুলি একটিতে প্রবেশ করা যেতে পারে ফ্রেম পয়েন্টার থেকে ধ্রুবক ইতিবাচক স্থানচ্যুতি।

দুটি কনভেনশনগুলির মধ্যে পার্থক্যটি যেভাবে তারা সাব্রোটিন থেকে একটি প্রস্থান পরিচালনা করে in সি কনভেনশনে, রিটার্নিং ফাংশন ফ্রেম পয়েন্টারটিকে স্ট্যাক পয়েন্টারে অনুলিপি করে [পুরানো ফ্রেম পয়েন্টারটি ধাকানোর ঠিক পরে এটির মানটি পুনরুদ্ধার করে], পুরাতন ফ্রেম পয়েন্টার মানটি টুকরো টুকরো করে তোলে এবং একটি রিটার্ন সম্পাদন করে। কল থাকবে না এমন আগে কলার যে কোনও পরামিতি স্ট্যাকের উপরে চাপ দিয়েছিল। পাস্কাল কনভেনশনে, পুরানো ফ্রেম পয়েন্টারটি পপ করার পরে, প্রসেসর ফাংশন রিটার্নের ঠিকানাটি পপ করে, স্ট্যাক পয়েন্টারটিতে কলার দ্বারা চালিত প্যারামিটারের বাইট সংখ্যা যুক্ত করে এবং তারপরে পপড রিটার্ন ঠিকানায় যায়। আসল 68000 এ কলারের প্যারামিটারগুলি সরাতে 3-নির্দেশের ক্রম ব্যবহার করা প্রয়োজন; 8x86 এবং সমস্ত 680x0 প্রসেসরের মূলটির পরে একটি "রেট এন" অন্তর্ভুক্ত ছিল

পাস্কাল কনভেনশনের কলারের পক্ষে কিছুটা কোড সংরক্ষণের সুবিধা রয়েছে, যেহেতু কলকারীকে কোনও ফাংশন কল করার পরে স্ট্যাক পয়েন্টারটি আপডেট করতে হবে না। তবে এটির প্রয়োজন, ডাকা ফাংশনটি ঠিক কতটি বাইট মূল্যবান প্যারামিটার কলকারী স্ট্যাকের উপরে রাখছে তা জেনে রাখা উচিত। পাস্কাল কনভেনশন ব্যবহার করে এমন ক্রিয়াকলাপটি কল করার আগে স্ট্যাকের উপরে পরামিতিগুলির যথাযথ সংখ্যায় চাপ দেওয়া ব্যর্থতা প্রায়শ্চিত্ত হওয়ার কারণ হিসাবে নিশ্চিত। এটি অফসেট, তবে, প্রতিটি কল করা পদ্ধতির মধ্যে কিছুটা অতিরিক্ত কোড যেখানে পদ্ধতিটি বলা হয় সেখানে কোড সংরক্ষণ করবে। যে কারণে, বেশিরভাগ মূল ম্যাকিনটোস টুলবক্স রুটিনগুলি প্যাসকাল কলিং কনভেনশন ব্যবহার করেছিল।

সি কলিং কনভেনশনটিতে রুটিনগুলি একটি পরিবর্তনশীল সংখ্যক পরামিতি গ্রহণ করার অনুমতি দেওয়া এবং দৃ routine় হওয়া সত্ত্বেও যদি কোনও রুটিন পাশ করা সমস্ত পরামিতি ব্যবহার না করে (কলার জানতে পারে যে এটি কতটা বাইটের প্যারামিটারকে ধাক্কা দিয়েছে, এবং এইভাবে তাদের পরিষ্কার করতে সক্ষম হবে)। তদ্ব্যতীত, প্রতিটি ফাংশন কলের পরে স্ট্যাক ক্লিনআপ করা প্রয়োজন হয় না। যদি একটি রুটিন ধারাবাহিকভাবে চারটি ফাংশন কল করে, যার প্রত্যেকটিতে চারটি বাইট মূল্যমানের প্যারামিটার ব্যবহার করা হয়, তবে - ADD SP,4প্রতিটি কলের পরে একটি ব্যবহার করার পরিবর্তে, ADD SP,16চারটি কল থেকে প্যারামিটারগুলি পরিষ্কার করার জন্য শেষ কলের পরে একটি ব্যবহার করতে পারে।

আজকাল বর্ণিত কলিং কনভেনশনগুলিকে কিছুটা পুরাকী হিসাবে বিবেচনা করা হয়। যেহেতু সংকলকগণ রেজিস্টার ব্যবহারের ক্ষেত্রে আরও দক্ষ হয়েছেন, তাই সমস্ত পরামিতিগুলি স্ট্যাকের উপরে চাপ দেওয়া প্রয়োজনের পরিবর্তে পদ্ধতিতে নিবন্ধগুলিতে কয়েকটি পরামিতি গ্রহণ করা সাধারণ বিষয়; যদি কোনও পদ্ধতি সমস্ত পরামিতি এবং স্থানীয় ভেরিয়েবলগুলি ধরে রাখতে রেজিস্টারগুলি ব্যবহার করতে পারে তবে ফ্রেম পয়েন্টার ব্যবহার করার দরকার নেই এবং এইভাবে পুরানোটিকে সংরক্ষণ এবং পুনরুদ্ধার করার প্রয়োজন নেই। তবুও, কখনও কখনও গ্রন্থাগারগুলিতে কল করার সময় তাদের পুরানো কলিং কনভেনশনগুলি ব্যবহার করা প্রয়োজন যা সেগুলি ব্যবহারের সাথে যুক্ত ছিল।


1
কি দারুন! আমি কি এক সপ্তাহ বা তার জন্য আপনার মস্তিষ্ক ধার নিতে পারি? কিছু কৌতুকপূর্ণ-কৌতুকপূর্ণ জিনিস উত্তোলনের প্রয়োজন! দুর্দান্ত উত্তর!
ক্রিস্টোফ

স্ট্যাক নিজেই বা অন্য কোথাও ফ্রেম এবং স্ট্যাক পয়েন্টার সংরক্ষণ করা হয়?
সুরজ জৈন

@ সুরজজাইন: সাধারণত, ফ্রেম পয়েন্টারটির প্রতিটি সংরক্ষিত অনুলিপি নতুন ফ্রেম পয়েন্টার মানের তুলনায় একটি স্থির স্থানচ্যুতিতে সংরক্ষণ করা হবে।
সুপারক্যাট

স্যার, আমি দীর্ঘদিন ধরে এই সন্দেহটি করছি। আমার ফাংশনে আমি লেখেন, তাহলে (g==4)তারপর int d = 3এবং gআমি ব্যবহার ইনপুট নিতে scanfযে পরে আমি অন্য পরিবর্তনশীল সংজ্ঞায়িত int h = 5। এখন, সংকলকটি এখন d = 3স্ট্যাকের মধ্যে স্থান দেয়। অফসেটটি কীভাবে কাজ করে কারণ যদি gতা না 4হয় তবে স্ট্যাকের জন্য ডি এর জন্য কোনও মেমরি থাকবে না এবং কেবল অফসেটটি দেওয়া হবে hএবং g == 4তারপরে অফসেটটি প্রথমে জি এবং তার জন্য হবে h। সংকলনের সময় সংকলক কীভাবে এটি করে, এটি আমাদের ইনপুটটি জানে নাg
সুরজ জৈন

@ সুরজজাইন: সি এর প্রাথমিক সংস্করণগুলির প্রয়োজন ছিল যে কোনও ফাংশনের মধ্যে থাকা সমস্ত স্বয়ংক্রিয় ভেরিয়েবলগুলি কোনও নির্বাহযোগ্য বক্তব্যের আগে উপস্থিত হওয়া উচিত। সেই জটিল সংকলনটি কিছুটা শিথিল করুন, তবে একটি পদ্ধতির কোনও ফাংশনের শুরুতে কোড তৈরি করা যা এসপি থেকে একটি ফরোয়ার্ড-ঘোষিত লেবেলের মানকে বিয়োগ করে। ফাংশনটির মধ্যে, সংকলকটি কোডের প্রতিটি বিন্দুতে স্থানীয়দের কতগুলি বাইটের মূল্য এখনও অবধি রয়েছে তা ট্র্যাক করতে পারে এবং সেই সাথে স্থানীয়দের সর্বাধিক সংখ্যক বাইটের সন্ধানও করতে পারে scope ফাংশন শেষে এটি পূর্বের জন্য মান সরবরাহ করতে পারে ...
সুপারক্যাট

5

ইতিমধ্যে এখানে কিছু ভাল উত্তর আছে। তবে, আপনি যদি এখনও স্ট্যাকের LIFO আচরণ সম্পর্কে উদ্বিগ্ন হন তবে এটিকে ভেরিয়েবলের স্ট্যাকের পরিবর্তে ফ্রেমের স্ট্যাক হিসাবে ভাবেন। আমি যেটা বলতে চাইছি তা হ'ল, যদিও কোনও ফাংশনটি ভেরিয়েবলগুলি অ্যাক্সেস করতে পারে যা স্ট্যাকের শীর্ষে নেই, এটি এখনও কেবল স্ট্যাকের শীর্ষে থাকা আইটেমটিতে কাজ করছে: একটি একক স্ট্যাক ফ্রেম।

অবশ্যই, এটি ব্যতিক্রম আছে। পুরো কল চেইনের স্থানীয় ভেরিয়েবলগুলি এখনও বরাদ্দ এবং উপলব্ধ। তবে তাদের সরাসরি অ্যাক্সেস করা হবে না। পরিবর্তে, তারা রেফারেন্স দ্বারা (বা পয়েন্টার দ্বারা, যা সত্যিই কেবল শব্দার্থগতভাবে পৃথক) দ্বারা পাস করা হয়। এই ক্ষেত্রে স্ট্যাক ফ্রেমের স্থানীয় ভেরিয়েবলটি আরও নিচে প্রবেশ করা যেতে পারে। তবে এই ক্ষেত্রেও, বর্তমানে সম্পাদনকারী ফাংশনটি এখনও কেবল তার নিজস্ব স্থানীয় ডেটাতে কাজ করছে। এটি তার নিজের স্ট্যাক ফ্রেমে সঞ্চিত একটি রেফারেন্স অ্যাক্সেস করছে, যা স্তূপে স্থির মেমরিতে বা স্ট্যাকের আরও কিছু অংশে রেফারেন্স হতে পারে।

এটি স্ট্যাক বিমূর্তির অংশ যা কোনও ক্রমে ফাংশনগুলিকে কলযোগ্য করে তোলে এবং পুনরাবৃত্তির অনুমতি দেয়। শীর্ষ স্ট্যাক ফ্রেমটি কেবলমাত্র অবজেক্ট যা কোড দ্বারা সরাসরি অ্যাক্সেস করা হয়। অন্য যে কোনও কিছুই পরোক্ষভাবে অ্যাক্সেস করা হয় (পয়েন্টারের মাধ্যমে যা শীর্ষ স্ট্যাক ফ্রেমে থাকে)।

আপনার সামান্য প্রোগ্রামের সমাবেশটি দেখে শিক্ষণীয় হতে পারে, বিশেষত যদি আপনি অপ্টিমাইজেশন ছাড়াই সংকলন করেন। আমি মনে করি আপনি দেখতে পাবেন যে আপনার ফাংশনে সমস্ত মেমরি অ্যাক্সেস স্ট্যাক ফ্রেম পয়েন্টার থেকে একটি অফসেটের মাধ্যমে ঘটে, যা ফাংশনটির কোডটি সংকলক দ্বারা কীভাবে লিখিত হবে। রেফারেন্স দ্বারা পাসের ক্ষেত্রে, আপনি স্ট্যাক ফ্রেম পয়েন্টার থেকে কোনও অফসেটে সঞ্চিত একটি পয়েন্টারের মাধ্যমে অপ্রত্যক্ষ মেমরি অ্যাক্সেস নির্দেশাবলী দেখতে পাবেন।


4

কল স্ট্যাক আসলে কোনও স্ট্যাক ডেটা কাঠামো নয়। পর্দার আড়ালে, আমরা যে কম্পিউটারগুলি ব্যবহার করি তা হ'ল এলোমেলো অ্যাক্সেস মেশিন আর্কিটেকচারের বাস্তবায়ন। সুতরাং, a এবং b সরাসরি অ্যাক্সেস করা যায়।

পর্দার আড়ালে, যন্ত্রটি করে:

  • "একটি" পান স্ট্যাক শীর্ষ নীচে চতুর্থ উপাদান মান পড়ার সমান।
  • "বি" স্ট্যাক শীর্ষের নীচে তৃতীয় উপাদানের মান সমান সমান পান।

http://en.wikipedia.org/wiki/Random-access_machine


1

সি এর কল স্ট্যাকের জন্য আমি এখানে একটি চিত্রটি তৈরি করেছি। এটি গুগল ইমেজ সংস্করণগুলির চেয়ে আরও নির্ভুল এবং সমসাময়িক

এখানে চিত্র বর্ণনা লিখুন

এবং উপরের চিত্রের যথাযথ কাঠামোর সাথে সম্পর্কিত, এখানে উইন্ডোজ on-এ নোটপ্যাড.এক্সে x64 এর একটি ডিবাগ রয়েছে।

এখানে চিত্র বর্ণনা লিখুন

নিম্ন ঠিকানা এবং উচ্চ ঠিকানাগুলি অদলবদল করা হয়েছে যাতে স্ট্যাকটি এই চিত্রের উপরের দিকে উঠছে। লাল ফ্রেমটিকে ঠিক প্রথম চিত্রের মতোই ইঙ্গিত করে (যা লাল এবং কালো রঙের ব্যবহৃত হয়েছিল, তবে কালোটি এখন পুনরুত্পাদন করা হয়েছে); কালো হল হোম স্পেস; নীল হল রিটার্ন ঠিকানা, যা কলের পরে নির্দেশের জন্য কলার ফাংশনে অফসেট; কমলা হল প্রান্তিককরণ এবং গোলাপী যেখানে নির্দেশের পয়েন্টারটি কল করার পরে এবং প্রথম নির্দেশের আগেই নির্দেশ করছে। হোমস্পেস + রিটার্ন মান উইন্ডোজের সর্বনিম্ন অনুমোদিত ফ্রেম এবং ডাকা ফাংশনটির শুরুতে ডানদিকে 16 বাইট আরএসপি প্রান্তিককরণ বজায় রাখতে হবে, এতে সর্বদা 8 বাইট প্রান্তিককরণও অন্তর্ভুক্ত থাকে।BaseThreadInitThunk ইত্যাদি।

লাল ফাংশন ফ্রেমগুলি আউটলাইন করে যে কলি ফাংশনটি যৌক্তিকভাবে কীভাবে 'মালিকানাধীন' + পড়ে / পরিবর্তিত করে (এটি স্ট্যাকের পাশ করা একটি প্যারামিটারটি পরিবর্তন করতে পারে যা -অনেষ্টায় রেজিস্টারে পাস করার জন্য খুব বড় ছিল)। ফাংশনটির শুরু থেকে শেষ পর্যন্ত ফাংশনটি নিজেকে বরাদ্দ করে এমন স্থান সবুজ রেখাগুলি সীমাবদ্ধ করে।


আপনি ডিবাগ মোডে সংকলন করলে আরডিআই এবং অন্যান্য রেজিস্টার আরগগুলি কেবলমাত্র স্ট্যাকের কাছে ছড়িয়ে পড়ে এবং কোনও গ্যারান্টি দেয় যে কোনও কম্পাইল সেই আদেশটি গ্রহণ করে। এছাড়াও, কেন পুরানো ফাংশন কলের জন্য ডায়াগ্রামের শীর্ষে স্ট্যাক আরোগুলি প্রদর্শিত হচ্ছে না? কোন ফ্রেম কোন ডেটা "মালিকানাধীন" এর মধ্যে আপনার চিত্রটিতে একটি স্পষ্ট সীমাবদ্ধতা নেই। (একটি কলি তার স্ট্যাক আরোগুলির মালিক)। ডায়াগ্রামের শীর্ষ থেকে স্ট্যাক আরগগুলি ছাড়াই এটি আরও কঠিন করে তোলে যে "রেজিস্টারে পাস করা যায় না এমন পরামিতি" প্রতিটি ফাংশনের রিটার্ন ঠিকানার ঠিক উপরে থাকে।
পিটার কর্ডেস

@ পিটারকর্ডস সোনারবোল্ট এএসএম আউটপুট ডিগ্রিবিহীন আচরণ হিসাবে স্ট্যাকের একটি রেজিস্টারে পাস করা একটি প্যারামিটারে চাপ দেওয়া জঞ্জাল এবং জিসিসি কলি দেখায়, সুতরাং এর একটি ঠিকানা রয়েছে। জিসিসি- registerতে প্যারামিটারের পিছনে ব্যবহার করে এটি সর্বোত্তম করে তোলে তবে আপনি মনে করেন যে কোনওভাবেই ঠিকানাটি ফাংশনের মধ্যে নেওয়া হয়নি বলে এটি যেভাবেই অপ্টিমাইজ হবে। আমি শীর্ষ ফ্রেম ঠিক করব; অবশ্যই আমার উপবৃত্তির একটি আলাদা ফাঁকা ফ্রেমে রাখা উচিত ছিল। 'একজন কলি তার স্ট্যাক আরোগুলির মালিক', যদি ফোনকারী রেজিস্টারে পাস করতে না পারে তবে ফোনকারী কী ধাক্কা দিয়ে থাকে?
লুইস কেলসি

হ্যাঁ, আপনি যদি অপটিমাইজেশন অক্ষম করে সংকলন করেন, কলি কোথাও এটি ছড়িয়ে দেবে। তবে স্ট্যাক আরগগুলির অবস্থানের বিপরীতে (এবং যুক্তিযুক্তভাবে সংরক্ষিত-আরবিপি) কোথা থেকে কিছুই মানসম্মত হয়নি। পুনরায়: কলি তার স্ট্যাক আরোগুলির মালিক: হ্যাঁ, ফাংশনগুলি তাদের আগত আর্গগুলিকে সংশোধন করার অনুমতি দেয়। এটি যে স্পষ্টভাবে ছড়িয়ে পড়ে সেগুলি রেগ আরগগুলি স্ট্যাক আরগস নয়। সংকলকরা মাঝে মাঝে এটি করেন তবে আইআরসি প্রায়শই তারা আরগটি পুনরায় পাঠ না করে এমনকি রিটার্ন ঠিকানার নীচে স্থান ব্যবহার করে স্ট্যাকের জায়গাটি নষ্ট করে দেয়। যদি কোনও কলার একই আর্গগুলি দিয়ে অন্য কোনও কল করতে চায় তবে নিরাপদ থাকতে তাদের পুনরাবৃত্তি করার আগে অন্য কপিটি সংরক্ষণ করতে হবেcall
পিটার কর্ডেস

@ পিটারকর্ডস ওয়েল, আমি যুক্তিগুলিকে কলার স্ট্যাকের অংশ হিসাবে তৈরি করেছি কারণ আমি যেখানে স্ট্রিম ফ্রেমগুলি rbp পয়েন্টের ভিত্তিতে নির্ধারণ করছি। কিছু ডায়াগ্রাম এটিকে কলি স্ট্যাকের অংশ হিসাবে দেখায় (যেমনটি এই প্রশ্নের প্রথম চিত্র হিসাবে রয়েছে) এবং কেউ কেউ এটিকে কলার স্ট্যাকের অংশ হিসাবে দেখায়, তবে প্যারামিটারের স্কোপ হিসাবে এগুলি দেখতে কলি স্ট্যাকের অংশ হিসাবে বোধগম্য হতে পারে না উচ্চ স্তরের কোডে কলারের কাছে অ্যাক্সেসযোগ্য নয়। হ্যাঁ, এটি মনে হয় registerএবং constঅপটিমাইজেশনগুলি কেবল -O0 এ একটি পার্থক্য করে।
লুইস কেলসি

@ পিটারকর্ডস আমি এটি পরিবর্তন করেছি। যদিও আমি এটি আবার পরিবর্তন করতে পারি
লুইস কেলসি
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.