यदि मैं एक निश्चित प्रकार के एक चर को परिभाषित करता हूं (जो, जहां तक मुझे पता है, बस चर की सामग्री के लिए डेटा आवंटित करता है), यह किस प्रकार के चर का ट्रैक रखता है?
यदि मैं एक निश्चित प्रकार के एक चर को परिभाषित करता हूं (जो, जहां तक मुझे पता है, बस चर की सामग्री के लिए डेटा आवंटित करता है), यह किस प्रकार के चर का ट्रैक रखता है?
जवाबों:
चर (या अधिक सामान्यतः: C के अर्थ में "ऑब्जेक्ट") रनटाइम पर अपने प्रकार को संग्रहीत नहीं करते हैं। जहां तक मशीन कोड का सवाल है, केवल अनपेड मेमोरी है। इसके बजाय, इस डेटा के संचालन डेटा को एक विशिष्ट प्रकार (जैसे एक फ्लोट के रूप में या एक पॉइंटर के रूप में) की व्याख्या करते हैं। प्रकार केवल संकलक द्वारा उपयोग किए जाते हैं।
उदाहरण के लिए, हमारे पास एक संरचना या वर्ग struct Foo { int x; float y; };
और एक चर हो सकता है Foo f {}
। फ़ील्ड एक्सेस कैसे auto result = f.y;
संकलित किया जा सकता है? संकलक जानता है कि f
एक प्रकार की वस्तु है Foo
और Foo
-objects के लेआउट को जानता है । प्लेटफ़ॉर्म-विशिष्ट विवरणों के आधार पर, इसे "स्टार्टर के पॉइंटर को ले लो f
, 4 बाइट्स जोड़ने, फिर 4 बाइट्स लोड करने और इस डेटा को फ्लोट के रूप में व्याख्या करने के लिए संकलित किया जा सकता है ।" ) फ़्लोटिंग या इन्ट्स लोड करने के लिए अलग-अलग प्रोसेसर निर्देश हैं।
एक उदाहरण जहां C ++ टाइप सिस्टम हमारे लिए टाइप का ट्रैक नहीं रख सकता है वह एक यूनियन जैसा है union Bar { int as_int; float as_float; }
। एक संघ में विभिन्न प्रकार की एक वस्तु तक होती है। यदि हम किसी वस्तु को संघ में संग्रहीत करते हैं, तो यह संघ का सक्रिय प्रकार है। हमें केवल उस प्रकार को संघ से बाहर निकालने की कोशिश करनी चाहिए, और कुछ भी अपरिभाषित व्यवहार नहीं होगा। या तो हम "जानते हैं" प्रोग्रामिंग करते समय कि सक्रिय प्रकार क्या है, या हम एक टैग किए गए संघ बना सकते हैं जहां हम एक प्रकार का टैग (आमतौर पर एक एनम) अलग से संग्रहीत करते हैं। यह C में एक सामान्य तकनीक है, लेकिन क्योंकि हमें संघ को रखना है और सिंक में टाइप टैग यह काफी त्रुटि प्रवण है। एक void*
पॉइंटर एक संघ के समान होता है लेकिन केवल पॉइंटर ऑब्जेक्ट्स को पकड़ सकता है, सिवाय फ़ंक्शन पॉइंटर्स के।
C ++ अज्ञात प्रकार की वस्तुओं से निपटने के लिए दो बेहतर तंत्र प्रदान करता है: प्रकार के क्षरण को करने के लिए हम ऑब्जेक्ट-ओरिएंटेड तकनीकों का उपयोग कर सकते हैं (केवल वर्चुअल विधियों के माध्यम से ऑब्जेक्ट के साथ इंटरैक्ट करते हैं ताकि हमें वास्तविक प्रकार जानने की आवश्यकता न हो), या हम कर सकते हैं का उपयोग करें std::variant
, एक प्रकार का सुरक्षित संघ।
एक मामला है जहां C ++ ऑब्जेक्ट के प्रकार को संग्रहीत करता है: यदि ऑब्जेक्ट के वर्ग में कोई वर्चुअल तरीके ("पॉलीमॉर्फिक प्रकार", उर्फ इंटरफ़ेस) है। वर्चुअल मेथड कॉल का लक्ष्य संकलन समय पर अज्ञात है और इसे गतिशील प्रकार के ऑब्जेक्ट ("डायनेमिक डिस्पैच") के आधार पर रन टाइम पर हल किया जाता है। अधिकांश कंपाइलर ऑब्जेक्ट के शुरू में एक वर्चुअल फंक्शन टेबल ("वॉयटेबल") स्टोर करके इसे लागू करते हैं। रन टाइम पर ऑब्जेक्ट के प्रकार को प्राप्त करने के लिए विटेबल का भी उपयोग किया जा सकता है। इसके बाद हम संकलित-स्थिर ज्ञात अभिव्यक्ति के प्रकार और रनटाइम पर किसी ऑब्जेक्ट के गतिशील प्रकार के बीच अंतर कर सकते हैं।
C ++ हमें typeid()
ऑपरेटर के साथ ऑब्जेक्ट के गतिशील प्रकार का निरीक्षण करने की अनुमति देता है जो हमें एक std::type_info
ऑब्जेक्ट देता है । या तो संकलक को संकलन के समय वस्तु का प्रकार पता है, या संकलक ने वस्तु के अंदर आवश्यक प्रकार की जानकारी संग्रहीत की है और इसे रनटाइम पर पुनः प्राप्त कर सकता है।
void*
)।
typeid(e)
अभिव्यक्ति के स्थिर प्रकार का परिचय देता है e
। यदि स्थैतिक प्रकार एक बहुरूपी प्रकार है, तो अभिव्यक्ति का मूल्यांकन किया जाएगा और उस वस्तु के गतिशील प्रकार को पुनः प्राप्त किया जाएगा। आप अज्ञात प्रकार की स्मृति में टाइपिड को इंगित नहीं कर सकते हैं और उपयोगी जानकारी प्राप्त कर सकते हैं। जैसे एक संघ का प्रकार संघ का वर्णन करता है, न कि संघ की वस्तु। एक void*
का प्रकार केवल एक शून्य सूचक है। और void*
इसकी सामग्री को प्राप्त करना संभव नहीं है । C ++ में कोई बॉक्सिंग नहीं है जब तक कि स्पष्ट रूप से उस तरह से प्रोग्राम न किया गया हो।
अन्य उत्तर अच्छी तरह से तकनीकी पहलू बताते हैं, लेकिन मैं कुछ सामान्य "मशीन कोड के बारे में कैसे सोचूं" जोड़ना चाहूंगा।
संकलन के बाद मशीन कोड बहुत गूंगा है, और यह वास्तव में सिर्फ यह मानता है कि सब कुछ उद्देश्य के अनुसार काम करता है। कहते हैं कि आपके पास एक सरल कार्य है
bool isEven(int i) { return i % 2 == 0; }
यह एक इंट लेता है, और एक बूल को बाहर निकालता है।
इसे संकलित करने के बाद, आप इसके बारे में इस स्वचालित संतरे के जूसर की तरह सोच सकते हैं:
यह संतरे में लेता है, और रस देता है। क्या यह उन वस्तुओं के प्रकार को पहचानता है जो इसमें मिलती हैं? नहीं, वे केवल संतरे होने वाले हैं। अगर इसे संतरे की जगह सेब मिले तो क्या होगा? शायद यह टूट जाएगा। इससे कोई फर्क नहीं पड़ता, एक जिम्मेदार मालिक के रूप में यह इस तरह से उपयोग करने की कोशिश नहीं करेगा।
उपरोक्त फ़ंक्शन समान है: यह कुछ अन्य खिलाए जाने पर अप्रासंगिक होने के लिए डिज़ाइन किया गया है, और यह कुछ अप्रासंगिक हो सकता है। यह (आमतौर पर) कोई फर्क नहीं पड़ता है, क्योंकि कंपाइलर (आम तौर पर) यह जांचता है कि यह कभी नहीं होता है - और यह वास्तव में कभी भी अच्छी तरह से गठित कोड में नहीं होता है। यदि संकलक एक संभावना का पता लगाता है कि किसी फ़ंक्शन को गलत टाइप किया गया मान मिलेगा, तो वह कोड संकलित करने से इनकार कर देता है और इसके बजाय टाइप त्रुटियां देता है।
चेतावनी यह है कि बीमार कोड के कुछ मामले हैं जो संकलक पास करेंगे। उदाहरण हैं:
void*
करने के लिए orange*
जब वहाँ सूचक के दूसरे छोर पर एक सेब है,जैसा कि कहा गया है, संकलित कोड जूसर मशीन की तरह है - यह नहीं जानता कि यह क्या प्रक्रिया करता है, यह सिर्फ निर्देशों को निष्पादित करता है। और अगर निर्देश गलत हैं, तो यह टूट जाता है। यही कारण है कि C ++ में उपरोक्त समस्याओं के परिणामस्वरूप अनियंत्रित क्रैश होते हैं।
void*
के लिए coerces foo*
, हमेशा की तरह गणित प्रोन्नति, union
प्रकार punning, NULL
बनाम nullptr
, यहां तक कि बस होने एक बुरा सूचक यूबी, आदि है लेकिन मैं वास्तव में अपने जवाब में सुधार होगा उन चीजों बाहर के सभी लिस्टिंग नहीं लगता कि, तो यह शायद छुट्टी के लिए सबसे अच्छा है जैसा है वैसा है।
void*
स्पष्ट रूप से परिवर्तित नहीं होता है foo*
, और union
टाइपिंग का समर्थन समर्थित नहीं है (UB है)।
एक चर में C जैसी भाषा में कई मूलभूत गुण होते हैं:
आपके स्रोत कोड में , स्थान (5), वैचारिक है, और इस स्थान को इसके नाम से संदर्भित किया जाता है, (1)। तो, मान (6) के लिए स्थान और स्थान बनाने के लिए एक चर घोषणा का उपयोग किया जाता है, और स्रोत की अन्य पंक्तियों में, हम उस स्थान और उस मूल्य को संदर्भित करते हैं जो चर को कुछ अभिव्यक्ति में नाम देकर रखता है।
केवल कुछ हद तक सरल करना, एक बार जब आपके प्रोग्राम को संकलक द्वारा मशीन कोड में अनुवादित किया जाता है, तो स्थान (5), कुछ मेमोरी या सीपीयू रजिस्टर स्थान है, और कोई भी स्रोत कोड अभिव्यक्ति जो चर का संदर्भ मशीन कोड अनुक्रमों में अनुवादित होता है जो उस संदर्भ को संदर्भित करता है या सीपीयू रजिस्टर स्थान।
इस प्रकार, जब अनुवाद पूरा हो जाता है और प्रोग्राम प्रोसेसर पर चल रहा होता है, तो चर के नाम मशीन कोड के भीतर प्रभावी रूप से भूल जाते हैं, और, संकलक द्वारा उत्पन्न निर्देश केवल चर के निर्दिष्ट स्थानों को संदर्भित करते हैं (बजाय उनके नाम नहीं)। यदि आप डिबगिंग कर रहे हैं और डिबगिंग का अनुरोध कर रहे हैं, तो नाम के साथ जुड़े चर का स्थान, प्रोग्राम के लिए मेटाडेटा में जोड़ा जाता है, हालांकि प्रोसेसर अभी भी स्थानों का उपयोग करके मशीन कोड निर्देशों को देखता है (न कि मेटाडेटा को)। (यह एक अति सरलीकरण है क्योंकि कुछ नाम प्रोग्राम के मेटाडेटा में लिंकिंग, लोडिंग और डायनेमिक लुकअप के प्रयोजनों के लिए हैं - फिर भी प्रोसेसर प्रोग्राम के लिए बताए गए मशीन कोड निर्देशों को निष्पादित करता है, और इस मशीन कोड में नाम हैं स्थानों में परिवर्तित कर दिया गया है।)
यही प्रकार, कार्यक्षेत्र और जीवनकाल के लिए भी सही है। कंपाइलर जनरेट मशीन कोड निर्देश लोकेशन के मशीन संस्करण को जानता है, जो मूल्य को संग्रहीत करता है। अन्य गुण, जैसे प्रकार, अनुवादित स्रोत कोड में विशिष्ट निर्देशों के रूप में संकलित किए जाते हैं जो चर के स्थान तक पहुंचते हैं। उदाहरण के लिए, यदि विचाराधीन चर एक हस्ताक्षरित 8-बिट बाइट बनाम एक अहस्ताक्षरित 8-बिट बाइट है, तो स्रोत कोड में अभिव्यक्त होने वाले चर का अनुवाद किया जाएगा, कहते हैं, हस्ताक्षरित बाइट लोड बनाम अहस्ताक्षरित बाइट लोड, (C) भाषा के नियमों को पूरा करने के लिए आवश्यक है। चर का प्रकार इस प्रकार मशीन निर्देशों में स्रोत कोड के अनुवाद में एन्कोड किया गया है, जो सीपीयू को मेमोरी या सीपीयू रजिस्टर स्थान की व्याख्या करने का तरीका बताता है और हर बार चर के स्थान का उपयोग करता है।
सार यह है कि हमें सीपीयू को यह बताना है कि प्रोसेसर के मशीन कोड निर्देश सेट में निर्देशों (और अधिक निर्देशों) के माध्यम से क्या करना है। प्रोसेसर बहुत कम याद करता है कि उसने अभी क्या किया था या उसे बताया गया था - यह केवल दिए गए निर्देशों को निष्पादित करता है, और यह कंपाइलर या असेंबली भाषा प्रोग्रामर का काम है कि वह चर को ठीक से हेरफेर करने के लिए निर्देश अनुक्रमों का एक पूरा सेट दे।
एक प्रोसेसर सीधे कुछ मूलभूत डेटा प्रकारों का समर्थन करता है, जैसे कि बाइट / वर्ड / इंट / लॉन्ग साइन / अनसाइन्ड, फ्लोट, डबल इत्यादि। प्रोसेसर आमतौर पर शिकायत या ऑब्जेक्ट नहीं करेगा यदि आप एक ही मेमोरी लोकेशन को साइन या अनसाइनड के लिए ट्रीट करते हैं, तो उदाहरण के लिए, हालांकि यह आमतौर पर कार्यक्रम में एक तर्क त्रुटि होगी। वेरिएबल के साथ प्रत्येक इंटरैक्शन में प्रोसेसर को निर्देश देना प्रोग्रामिंग का काम है।
उन मौलिक आदिम प्रकारों से परे, हमें डेटा संरचनाओं में चीजों को सांकेतिक शब्दों में बदलना है और उन प्राइमेटिव के संदर्भ में उन्हें हेरफेर करने के लिए एल्गोरिदम का उपयोग करना है।
सी ++ में, बहुरूपता के लिए वर्ग पदानुक्रम में शामिल वस्तुओं में एक सूचक होता है, आमतौर पर ऑब्जेक्ट की शुरुआत में, यह एक वर्ग-विशिष्ट डेटा संरचना को संदर्भित करता है, जो आभासी प्रेषण, कास्टिंग, आदि के साथ मदद करता है।
सारांश में, प्रोसेसर अन्यथा भंडारण स्थानों के इच्छित उपयोग को नहीं जानता या याद नहीं रखता है - यह प्रोग्राम के मशीन कोड निर्देशों को निष्पादित करता है जो यह बताता है कि सीपीयू रजिस्टरों और मुख्य मेमोरी में भंडारण में हेरफेर कैसे करें। प्रोग्रामिंग, तब, सॉफ्टवेयर (और प्रोग्रामर) का काम है कि भंडारण को सार्थक रूप से उपयोग करना, और प्रोसेसर को मशीन कोड निर्देशों का एक सुसंगत सेट प्रस्तुत करना जो विश्वासपूर्वक कार्यक्रम को संपूर्ण रूप से निष्पादित करता है।
useT1(&unionArray[i].member1); useT2(&unionArray[j].member2); useT1(&unionArray[i].member1);
, क्लैंग और जीसीसी को यह अनुमान है कि दोनों एक ही से प्राप्त होने के बावजूद सूचक तक unionArray[j].member2
नहीं पहुंच सकते unionArray[i].member1
हैं unionArray[]
।
अगर मैं एक निश्चित प्रकार के चर को परिभाषित करता हूं तो यह किस प्रकार के चर का ट्रैक रखता है।
यहां दो प्रासंगिक चरण हैं:
C संकलक मशीन भाषा के लिए C कोड संकलित करता है। संकलक के पास सभी जानकारी है कि वह आपकी स्रोत फ़ाइल (और पुस्तकालयों, और जो भी अन्य सामान को अपना काम करने की आवश्यकता है) से प्राप्त कर सकता है। सी कंपाइलर का मतलब क्या है का ट्रैक रखता है। C संकलक जानता है कि यदि आप एक चर घोषित करते हैं char
, तो यह चार है।
यह एक तथाकथित "प्रतीक तालिका" का उपयोग करके ऐसा करता है जो चर, उनके प्रकार और अन्य जानकारी के नामों को सूचीबद्ध करता है। यह एक बल्कि जटिल डेटा संरचना है, लेकिन आप इसके बारे में सोच सकते हैं कि मानव-पठनीय नामों का क्या मतलब है। कंपाइलर से बाइनरी आउटपुट में, इस तरह का कोई चर नाम अब नहीं दिखाई देता है (यदि हम वैकल्पिक डिबग जानकारी को अनदेखा करते हैं जो प्रोग्रामर द्वारा अनुरोध किया जा सकता है)।
संकलक का आउटपुट - संकलित निष्पादन योग्य - मशीन भाषा है, जिसे आपके ओएस द्वारा रैम में लोड किया गया है, और सीधे आपके सीपीयू द्वारा निष्पादित किया जाता है। मशीन की भाषा में, "टाइप" की कोई धारणा नहीं है - इसमें केवल कमांड हैं जो रैम में कुछ स्थान पर काम करते हैं। आदेशों वास्तव में एक निश्चित प्रकार वे के साथ काम क्या है (यानी, वहाँ एक मशीन भाषा आदेश "इन दो 16-बिट रैम स्थानों 0x100 और 0x521 पर संग्रहीत पूर्णांकों जोड़ें" हो सकता है), लेकिन वहाँ कोई जानकारी नहीं है कहीं भी प्रणाली है कि में उन स्थानों पर बाइट्स वास्तव में पूर्णांक का प्रतिनिधित्व कर रहे हैं। यहां सभी प्रकार की त्रुटियों से कोई सुरक्षा नहीं है।
char *ptr = 0x123
सी)। मेरा मानना है कि शब्द "पॉइंटर" का मेरा उपयोग इस संदर्भ में बहुत स्पष्ट होना चाहिए। यदि नहीं, तो बेझिझक मुझे सिर चढ़ा दें और मैं जवाब में एक वाक्य जोड़ दूंगा।
कुछ महत्वपूर्ण विशेष मामले हैं जहां C ++ रनटाइम पर एक प्रकार की दुकान करता है।
क्लासिक समाधान एक विभेदित संघ है: एक डेटा संरचना जिसमें कई प्रकार की वस्तु होती है, साथ ही एक क्षेत्र जो कहता है कि वर्तमान में किस प्रकार का है। एक टेम्प्लेटेड संस्करण C ++ मानक लाइब्रेरी में है std::variant
। आम तौर पर, टैग एक होगा enum
, लेकिन अगर आपको अपने डेटा के लिए सभी बिट स्टोरेज की आवश्यकता नहीं है, तो यह एक बिटफील्ड हो सकता है।
इसका दूसरा सामान्य मामला डायनामिक टाइपिंग है। जब आपके class
पास एक virtual
फ़ंक्शन होता है, तो प्रोग्राम उस फ़ंक्शन को एक वर्चुअल फ़ंक्शन तालिका में एक पॉइंटर स्टोर करेगा , जिसे यह class
निर्माण होने पर प्रत्येक उदाहरण के लिए प्रारंभ करेगा । आम तौर पर, इसका मतलब होगा कि सभी वर्ग उदाहरणों के लिए एक आभासी फ़ंक्शन तालिका, और प्रत्येक उदाहरण उपयुक्त तालिका के लिए एक पॉइंटर पकड़े। (यह समय और मेमोरी बचाता है क्योंकि टेबल एक सिंगल पॉइंटर से बहुत बड़ी होगी।) जब आप उस virtual
फ़ंक्शन को पॉइंटर या संदर्भ के माध्यम से कॉल करते हैं , तो प्रोग्राम वर्चुअल टेबल में फ़ंक्शन पॉइंटर को देखेगा। (यदि यह संकलन के समय सटीक प्रकार जानता है, तो यह इस चरण को छोड़ सकता है।) यह कोड को बेस क्लास के बजाय व्युत्पन्न प्रकार के कार्यान्वयन को कॉल करने की अनुमति देता है।
बात यह है कि यह यहाँ प्रासंगिक हो जाती है: प्रत्येक ofstream
के लिए एक सूचक होता है ofstream
आभासी मेज, प्रत्येक ifstream
के लिए ifstream
आभासी मेज, और इतने पर। क्लास पदानुक्रमों के लिए, वर्चुअल टेबल पॉइंटर टैग के रूप में कार्य कर सकता है जो प्रोग्राम को बताता है कि क्लास ऑब्जेक्ट किस प्रकार का है!
हालांकि भाषा मानक उन लोगों को नहीं बताता है जो कंपाइलर डिजाइन करते हैं कि उन्हें हुड के तहत रनटाइम कैसे लागू करना चाहिए, यह आप कैसे उम्मीद कर सकते हैं dynamic_cast
और typeof
काम कर सकते हैं।