जब कंप्यूटर एक वैरिएबल को स्टोर करता है, जब एक प्रोग्राम को वैरिएबल की वैल्यू प्राप्त करने की आवश्यकता होती है, तो कंप्यूटर को यह कैसे पता चलता है कि उस वैरिएबल की वैल्यू के लिए मेमोरी में कहां देखना है?
जब कंप्यूटर एक वैरिएबल को स्टोर करता है, जब एक प्रोग्राम को वैरिएबल की वैल्यू प्राप्त करने की आवश्यकता होती है, तो कंप्यूटर को यह कैसे पता चलता है कि उस वैरिएबल की वैल्यू के लिए मेमोरी में कहां देखना है?
जवाबों:
मेरा सुझाव है कि आप संकलक निर्माण की अद्भुत दुनिया में देखो! जवाब यह है कि यह एक जटिल प्रक्रिया का एक सा है।
आपको एक अंतर्ज्ञान देने की कोशिश करने के लिए, याद रखें कि प्रोग्रामर की खातिर चर नाम शुद्ध रूप से हैं। कंप्यूटर अंत में सब कुछ पते में बदल जाएगा।
स्थानीय चर (आमतौर पर) स्टैक पर संग्रहीत होते हैं: अर्थात, वे डेटा संरचना का हिस्सा हैं जो एक फ़ंक्शन कॉल का प्रतिनिधित्व करता है। हम उस फ़ंक्शन को देखकर किसी फ़ंक्शन (शायद) का उपयोग करने वाले चर की पूरी सूची निर्धारित कर सकते हैं, इसलिए कंपाइलर देख सकता है कि इस फ़ंक्शन के लिए कितने चर की आवश्यकता है और प्रत्येक चर को कितनी जगह चाहिए।
स्टैक पॉइंटर नामक एक छोटा सा जादू है, जो एक रजिस्टर है जो हमेशा वर्तमान स्टैक शुरू होने के पते को संग्रहीत करता है।
प्रत्येक चर को एक "स्टैक ऑफ़सेट" दिया जाता है, जो कि जहाँ स्टैक में संग्रहीत होता है। फिर, जब प्रोग्राम को एक चर का उपयोग करने की आवश्यकता होती है x
, तो संकलक की जगह लेता x
है STACK_POINTER + x_offset
, वास्तविक भौतिक स्थान प्राप्त करने के लिए जिसे यह मेमोरी में संग्रहीत किया जाता है।
ध्यान दें कि, यह है जब आप का उपयोग कारण है कि आप एक सूचक वापस पाने malloc
या new
सी या सी ++ में। आप यह निर्धारित नहीं कर सकते कि वास्तव में स्मृति में ढेर-आवंटित मूल्य कहां है, इसलिए आपको इसके लिए एक संकेतक रखना होगा। वह सूचक स्टैक पर होगा, लेकिन यह ढेर को इंगित करेगा।
फ़ंक्शन कॉल और रिटर्न के लिए स्टैक अपडेट करने का विवरण जटिल है, इसलिए यदि आप रुचि रखते हैं, तो मैं ड्रैगन बुक या टाइगर बुक को पुनः प्राप्त करूंगा ।
जब कंप्यूटर एक वैरिएबल को स्टोर करता है, जब एक प्रोग्राम को वैरिएबल की वैल्यू प्राप्त करने की आवश्यकता होती है, तो कंप्यूटर को यह कैसे पता चलता है कि उस वैरिएबल की वैल्यू के लिए मेमोरी में कहां देखना है?
कार्यक्रम इसे बताता है। कंप्यूटर में मूल रूप से "चर" की अवधारणा नहीं है - यह पूरी तरह से एक उच्च स्तरीय भाषा है!
यहाँ एक सी कार्यक्रम है:
int main(void)
{
int a = 1;
return a + 3;
}
और यहां असेंबली कोड इसका संकलन करता है: (टिप्पणियों की शुरुआत ;
)
main:
; {
pushq %rbp
movq %rsp, %rbp
; int a = 1
movl $1, -4(%rbp)
; return a + 3
movl -4(%rbp), %eax
addl $3, %eax
; }
popq %rbp
ret
"Int a = 1;" सीपीयू निर्देश को "पता 1 पर मूल्य 1 (रजिस्टर आरबीपी, माइनस 4 का मान)" देखता है। यह जानता है कि मूल्य 1 कहाँ संग्रहीत करना है क्योंकि कार्यक्रम इसे बताता है।
इसी तरह, अगला निर्देश कहता है कि "पते पर मूल्य (रजिस्टर आरबीपी का मान, माइनस 4) रजिस्टर में लोड करें"। कंप्यूटर को चर जैसी चीजों के बारे में जानने की जरूरत नहीं है।
%rsp
सीपीयू का स्टैक पॉइंटर है। %rbp
एक रजिस्टर है जो वर्तमान फ़ंक्शन द्वारा उपयोग किए जाने वाले स्टैक के बिट को संदर्भित करता है। दो रजिस्टरों का उपयोग डिबगिंग को सरल बनाता है।
जब संकलक या दुभाषिया एक चर की घोषणा का सामना करता है, तो यह तय करता है कि उस चर को संग्रहीत करने के लिए वह किस पते का उपयोग करेगा और फिर एक प्रतीक तालिका में पता दर्ज करता है। जब उस चर के बाद के संदर्भ सामने आते हैं, तो प्रतीक तालिका से पता प्रतिस्थापित किया जाता है।
प्रतीक तालिका में दर्ज पता एक रजिस्टर (जैसे स्टैक पॉइंटर) से ऑफसेट हो सकता है लेकिन यह एक कार्यान्वयन विवरण है।
सटीक तरीके इस बात पर निर्भर करते हैं कि आप किस बारे में विशेष रूप से बात कर रहे हैं और कितनी गहराई तक जाना चाहते हैं। उदाहरण के लिए, हार्ड ड्राइव पर फ़ाइलों को संग्रहीत करना स्मृति में कुछ संग्रहीत करने या डेटाबेस में कुछ संग्रहीत करने से अलग है। हालांकि अवधारणाएं समान हैं। और आप इसे प्रोग्रामिंग स्तर पर कैसे करते हैं, यह एक अलग स्पष्टीकरण है कि कंप्यूटर I / O स्तर पर कैसे करता है।
अधिकांश सिस्टम कंप्यूटर को डेटा को खोजने और एक्सेस करने की अनुमति देने के लिए किसी प्रकार की निर्देशिका / सूचकांक / रजिस्ट्री तंत्र का उपयोग करते हैं। इस अनुक्रमणिका / निर्देशिका में एक या एक से अधिक कुंजियाँ होंगी, और पता वास्तव में डेटा में स्थित है (चाहे वह हार्ड ड्राइव हो, रैम, डेटाबेस, आदि)।
कंप्यूटर प्रोग्राम उदाहरण
एक कंप्यूटर प्रोग्राम विभिन्न तरीकों से मेमोरी तक पहुंच सकता है। आमतौर पर ऑपरेटिंग सिस्टम प्रोग्राम को एड्रेस स्पेस देता है, और प्रोग्राम वही कर सकता है जो उस एड्रेस स्पेस के साथ चाहता है। यह अपने मेमोरी स्पेस के भीतर किसी भी पते पर सीधे लिख सकता है, और यह इस बात का ट्रैक रख सकता है कि यह कैसे चाहता है। यह कभी-कभी प्रोग्रामिंग भाषा और ऑपरेटिंग सिस्टम या प्रोग्रामर की पसंदीदा तकनीकों के अनुसार भी भिन्न होगा।
जैसा कि कुछ अन्य उत्तरों में उल्लेख किया गया है, सटीक कोडिंग या प्रोग्रामिंग का उपयोग अलग-अलग होता है, लेकिन आमतौर पर पर्दे के पीछे यह एक स्टैक की तरह कुछ का उपयोग करता है। इसमें एक रजिस्टर होता है जो मेमोरी लोकेशन को स्टोर करता है जहां करंट स्टैक शुरू होता है, और फिर उस स्टैक में एक फंक्शन या वेरिएबल को जानने का एक तरीका है।
कई उच्च स्तरीय प्रोग्रामिंग भाषाओं में, यह आपके लिए सभी का ध्यान रखता है। आपको बस एक चर घोषित करना है, और उस चर में कुछ स्टोर करना है, और यह आपके लिए पर्दे के पीछे आवश्यक ढेर और सरणियां बनाता है।
लेकिन यह देखते हुए कि बहुमुखी प्रोग्रामिंग कैसे होती है, वास्तव में एक जवाब नहीं है, क्योंकि एक प्रोग्रामर किसी भी समय अपने आवंटित स्थान के भीतर किसी भी पते पर सीधे लिखना चुन सकता है (यह मानते हुए कि वह एक प्रोग्रामिंग भाषा का उपयोग कर रहा है जो कि अनुमति देता है)। तब वह अपने स्थान को एक सरणी में संग्रहीत कर सकता है, या यहां तक कि इसे प्रोग्राम में केवल कठिन कोड (यानी चर "अल्फा" हमेशा स्टैक की शुरुआत में संग्रहीत किया जाता है या हमेशा आवंटित 32 बिट के पहले मेमोरी में संग्रहीत किया जाता है)।
सारांश
इसलिए मूल रूप से, कंप्यूटर के डेटा को संग्रहीत करने वाले दृश्यों के पीछे कुछ तंत्र होना चाहिए। सबसे लोकप्रिय तरीकों में से एक कुछ प्रकार का सूचकांक / निर्देशिका है जिसमें कुंजी (एस) और मेमोरी एड्रेस शामिल हैं। यह सभी प्रकार से कार्यान्वित किया जाता है और आमतौर पर उपयोगकर्ता से एनकैप्सुलेट किया जाता है (और कभी-कभी प्रोग्रामर से एनकैप्सुलेटेड भी)।
संदर्भ: कंप्यूटर कैसे याद करते हैं कि वे चीजों को कहाँ स्टोर करते हैं?
यह टेम्प्लेट और फॉर्मेट के कारण जानता है।
प्रोग्राम / फंक्शन / कंप्यूटर वास्तव में नहीं जानता कि कुछ भी कहाँ है। यह सिर्फ एक निश्चित स्थान पर कुछ होने की उम्मीद करता है। आइए एक उदाहरण का उपयोग करें।
class simpleClass{
public:
int varA=58;
int varB=73;
simpleClass* nextObject=NULL;
};
हमारे नए वर्ग 'सिंपलक्लास' में 3 महत्वपूर्ण चर हैं - दो पूर्णांक जिनमें कुछ डेटा हो सकते हैं जब हमें उनकी आवश्यकता होती है, और एक सूचक 'अन्य' simpleClass ऑब्जेक्ट '। मान लेते हैं कि हम सादगी के लिए 32-बिट मशीन पर हैं। 'gcc' या अन्य 'C' संकलक कुछ डेटा आवंटित करने के लिए हमारे साथ काम करने के लिए एक खाका तैयार करेंगे।
सरल प्रकार
सबसे पहले, जब कोई 'int' जैसे सरल प्रकार के लिए एक कीवर्ड का उपयोग करता है, तो निष्पादन फ़ाइल के '.data' या '.bss' खंड में कंपाइलर द्वारा एक नोट बनाया जाता है ताकि जब इसे ऑपरेटिंग सिस्टम द्वारा निष्पादित किया जाए, तो डेटा कार्यक्रम के लिए उपलब्ध है। 'Int' कीवर्ड 4 बाइट्स (32 बिट्स) आवंटित करेगा, जबकि एक 'लॉन्ग इंट' 8 बाइट्स (64 बिट्स) आवंटित करेगा।
कभी-कभी, सेल-दर-सेल तरीके से, एक चर उस निर्देश के ठीक बाद आ सकता है जिसे इसे मेमोरी में लोड करना है, इसलिए यह छद्म-असेंबली में इस तरह दिखाई देगा:
...
clear register EAX
clear register EBX
load the immediate (next) value into EAX
5
copy the value in register EAX to register EBX
...
यह EAX के साथ ही EBX में '5' मूल्य के साथ समाप्त होगा।
जबकि कार्यक्रम निष्पादित होता है, तत्काल लोड के संदर्भ के बाद से प्रत्येक निर्देश को '5' के अलावा निष्पादित किया जाता है और सीपीयू को इस पर छोड़ देता है।
इस पद्धति का नकारात्मक पक्ष यह है कि यह केवल स्थिरांक के लिए वास्तव में व्यावहारिक है, क्योंकि आपके कोड के मध्य में सरणियों / बफ़र्स / स्ट्रिंग्स को रखना अव्यावहारिक होगा। इसलिए, आम तौर पर, अधिकांश चर प्रोग्राम हेडर में रखे जाते हैं।
यदि इन गतिशील चर में से किसी एक को एक्सेस करने की आवश्यकता है, तो कोई तात्कालिक मूल्य का इलाज कर सकता है जैसे कि यह एक संकेतक था:
...
clear register EAX
clear register EBX
load the immediate value into EAX
0x0AF2CE66 (Let's say this is the address of a cell containing '5')
load the value pointed to by EAX into EBX
...
यह रजिस्टर EAX में मूल्य '0x0AF2CE66' और रजिस्टर EBX में '5' के मूल्य के साथ समाप्त होगा। एक साथ रजिस्टरों में भी मान जोड़ सकते हैं, इसलिए हम इस पद्धति का उपयोग करके किसी सरणी या स्ट्रिंग के तत्वों को खोजने में सक्षम होंगे।
एक और महत्वपूर्ण बिंदु यह है कि एक समान तरीके से पतों का उपयोग करते समय मूल्यों को संग्रहीत करने में सक्षम है, ताकि बाद में उन कोशिकाओं पर मूल्यों का संदर्भ दिया जा सके।
जटिल प्रकार
यदि हम इस वर्ग की दो वस्तुएँ बनाते हैं:
simpleClass newObjA;
simpleClass newObjB;
तब हम पहली वस्तु में इसके लिए उपलब्ध क्षेत्र के लिए दूसरी वस्तु को एक पॉइंटर दे सकते हैं:
newObjA.nextObject=&newObjB;
अब कार्यक्रम पहली वस्तु के सूचक क्षेत्र के भीतर दूसरी वस्तु का पता खोजने की उम्मीद कर सकता है। स्मृति में, यह कुछ इस तरह दिखाई देगा:
newObjA: 58
73
&newObjB
...
newObjB: 58
73
NULL
यहां ध्यान देने योग्य एक महत्वपूर्ण तथ्य यह है कि 'newObjA' और 'newObjB' का नाम तब नहीं होता जब वे संकलित होते हैं। वे केवल वे स्थान हैं जहाँ हम कुछ डेटा के होने की उम्मीद करते हैं। इसलिए, यदि हम & newObjA में 2 सेल जोड़ते हैं तो हम 'नेक्स्टबॉजेक्ट' के रूप में कार्य करने वाले सेल को खोजते हैं। इसलिए, यदि हम 'newObjA' का पता जानते हैं और जहाँ 'nextObject' सेल इसके सापेक्ष है, तो हम 'newObjB' का पता जान सकते हैं:
...
load the immediate value into EAX
&newObjA
add the immediate value to EAX
2
load the value in EAX into EBX
यह 'EAX' में '2 + & newObjA' और 'EBO' में 'newObjB' के साथ समाप्त होगा।
टेम्पलेट्स / प्रारूप
जब कंपाइलर वर्ग की परिभाषा को संकलित करता है, तो यह वास्तव में प्रारूप बनाने का तरीका, प्रारूप लिखने का तरीका और प्रारूप से पढ़ने का तरीका संकलित करता है।
ऊपर दिया गया उदाहरण दो 'int' चर के साथ एक एकल-लिंक-सूची के लिए एक टेम्पलेट है। द्विआधारी और एन-एरी पेड़ों के साथ गतिशील स्मृति आवंटन के लिए इस प्रकार के निर्माण बहुत महत्वपूर्ण हैं। एन-एरी ट्री के व्यावहारिक अनुप्रयोग फाइलों से बने निर्देशिकाओं से युक्त होते हैं, जो ड्राइवरों / ऑपरेटिंग सिस्टम द्वारा मान्यता प्राप्त फाइलों, निर्देशिकाओं या अन्य उदाहरणों की ओर इशारा करते हैं।
सभी तत्वों को एक्सेस करने के लिए, संरचना को ऊपर और नीचे काम करने वाले इंचवर्म के बारे में सोचें। इस तरह, प्रोग्राम / फ़ंक्शन / कंप्यूटर को कुछ भी पता नहीं है, यह सिर्फ डेटा को स्थानांतरित करने के निर्देशों को निष्पादित करता है।