GDB का उपयोग करके C जानें

जीडीबी के साथ एलन ओ'डनेल लर्निंग सी के एक लेख का अनुवाद।

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

यह हाल ही में मेरे साथ हुआ है कि मैं GDB को छद्म-REPL के रूप में उपयोग कर सकता हूं। C के लिए मैंने GDB का प्रयोग एक भाषा सीखने के लिए एक उपकरण के रूप में किया, न कि केवल डिबगिंग, और यह बहुत मजेदार निकला।

इस पोस्ट का लक्ष्य आपको यह दिखाना है कि GDB, C के बारे में सीखने का एक बहुत अच्छा साधन है। मैं आपको अपने कुछ पसंदीदा GDB कमांड से परिचित कराऊंगा, और प्रदर्शित करता हूँ कि C के कठिन भागों में से एक को समझने के लिए आप GDB का उपयोग कैसे कर सकते हैं: सरणियों के बीच का अंतर। संकेत दिए गए।

जीडीबी का परिचय


चलो निम्नलिखित छोटे सी कार्यक्रम बनाकर शुरू करते हैं - minimal.c :

int main() { int i = 1337; return 0; } 

ध्यान दें कि कार्यक्रम पूरी तरह से कुछ भी नहीं करता है, और एक प्रिंटआउट कमांड भी नहीं है। अब GBD का उपयोग करके C सीखने की नई दुनिया में उतरें।

हम डिबगिंग जानकारी उत्पन्न करने के लिए -g ध्वज के साथ इस कार्यक्रम को संकलित करते हैं जो GDB के साथ काम करेगा, और इसे उसी जानकारी को फेंक देगा:

 $ gcc -g minimal.c -o minimal $ gdb minimal 

अब आपको GDB की कमांड लाइन पर तुरंत आना चाहिए। मैंने आपसे एक REPL का वादा किया था, इसलिए:

 (gdb) print 1 + 2 $1 = 3 

कमाल! प्रिंट एक अंतर्निहित GDB कमांड है जो Cth अभिव्यक्ति के परिणाम का मूल्यांकन करता है। यदि आपको पता नहीं है कि एक विशेष GDB कमांड क्या कर रहा है, तो बस सहायता का उपयोग करें - GDB कमांड प्रॉम्प्ट पर नाम-की-कमांड टाइप करें।

यहाँ एक और दिलचस्प उदाहरण है:

 (gbd) print (int) 2147483648 $2 = -2147483648 

मुझे 2147483648 == -2147483648 की व्याख्या क्यों याद आएगी । यहां मुख्य बिंदु यह है कि सी में अंकगणित भी असंवेदनशील हो सकता है और जीडीबी सी के अंकगणित को समझता है।

अब मुख्य कार्य में एक ब्रेकपॉइंट लगाते हैं और प्रोग्राम को चलाते हैं:

 (gdb) break main (gdb) run 

कार्यक्रम तीसरी पंक्ति पर बंद हो गया, ठीक उसी जगह जहां i चर आरंभिक है। दिलचस्प बात यह है कि हालाँकि अभी तक वैरिएबल को इनिशियलाइज़ नहीं किया गया है, अब हम प्रिंट कमांड का उपयोग करके इसकी वैल्यू देख सकते हैं:

 (gdb) print i $3 = 32767 

C में, स्थानीय असिंचित वैरिएबल के मान को परिभाषित नहीं किया गया है, इसलिए आपको प्राप्त होने वाला परिणाम भिन्न हो सकता है।

हम अगले कमांड का उपयोग करके कोड की वर्तमान लाइन निष्पादित कर सकते हैं:

 (gdb) next (gdb) print i $4 = 1337 

एक्स कमांड का उपयोग करके मेमोरी की खोज करना


C में चर स्मृति के सन्निहित ब्लॉक हैं। प्रत्येक चर के ब्लॉक को दो संख्याओं की विशेषता है:

1. ब्लॉक में पहले बाइट का संख्यात्मक पता।
2. बाइट में ब्लॉक का आकार। यह आकार चर के प्रकार से निर्धारित होता है।

सी भाषा की विशिष्ट विशेषताओं में से एक यह है कि आपके पास चर मेमोरी ब्लॉक तक सीधी पहुंच है। & ऑपरेटर हमें मेमोरी में वेरिएबल का पता देता है, और sizeof मेमोरी वेरिएबल के कब्जे वाले साइज की गणना करता है।

आप GDB में दोनों विशेषताओं के साथ खेल सकते हैं:

 (gdb) print &i $5 = (int *) 0x7fff5fbff584 (gdb) print sizeof(i) $6 = 4 

सामान्य भाषा में, इसका मतलब है कि वेरिएबल i 0x7fff5fbff5b4 पर स्थित है और मेमोरी में 4 बाइट्स रखता है।

मैंने पहले ही ऊपर उल्लेख किया है कि मेमोरी में एक चर का आकार उसके प्रकार पर निर्भर करता है, और आम तौर पर बोलना, आकार प्रकार ऑपरेटर भी अन्य प्रकारों पर काम कर सकता है:

 (gdb) print sizeof(int) $7 = 4 (gdb) print sizeof(double) $8 = 8 

इसका मतलब यह है कि कम से कम मेरी मशीन पर, प्रकार के चर int बाइट चार बाइट्स, और टाइप डबल ऑक्यूपी आठ बाइट्स।

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

जैसा कि आप पहले से ही जानते हैं, और ऑपरेटर एक वैरिएबल के पते की गणना करता है, जिसका अर्थ है कि आप x कमांड को मान & i से पास कर सकते हैं और इस तरह वैरिएबल के पीछे छिपे हुए व्यक्तिगत बाइट्स को देखने का अवसर प्राप्त कर सकते हैं:

 (gdb) x/4xb &i 0x7fff5fbff584: 0x39 0x05 0x00 0x00 

फ़ॉर्मेटिंग फ़्लैग से संकेत मिलता है कि मैं हेक्साडेसिमल (हे x ) बाइट बाइट ( b yte) में चार ( 4 ) वैल्यू आउटपुट प्राप्त करना चाहता हूं। मैंने केवल चार बाइट्स की जाँच करने का संकेत दिया था, क्योंकि i वैरिएबल बहुत अधिक मेमोरी लेता है। आउटपुट मेमोरी में चर का बाइट प्रतिनिधित्व दर्शाता है।

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

इस मुद्दे को स्पष्ट करने का एक तरीका यह है कि मैं चर को एक अधिक दिलचस्प मूल्य प्रदान करूं और फिर से स्मृति के इस टुकड़े की जांच करूं:

 (gdb) set var i = 0x12345678 (gdb) x/4xb &i 0x7fff5fbff584: 0x78 0x56 0x34 0x12 

खोज पॉटाइप मेमोरी


Ptype टीम शायद मेरे पसंदीदा में से एक है। यह Cth अभिव्यक्ति के प्रकार को दर्शाता है:

 (gdb) ptype i type = int (gdb) ptype &i type = int * (gdb) ptype main type = int (void) 

C के प्रकार जटिल हो सकते हैं, लेकिन ptype आपको अंतःक्रियात्मक रूप से उनका पता लगाने की अनुमति देता है।

संकेत और सारणी


ए। सी में आश्चर्यजनक रूप से सूक्ष्म अवधारणा है। इस पैराग्राफ का सार एक साधारण कार्यक्रम लिखना है और फिर इसे जीडीबी के माध्यम से चलाना है जब तक कि ऐरे कुछ अर्थ नहीं बनाते हैं।

इसलिए, हमें array.c के साथ प्रोग्राम कोड की आवश्यकता है:

 int main() { int a[] = {1, 2, 3}; return 0; } 

इसे -g ध्वज के साथ संकलित करें, इसे GDB में चलाएँ, और अगले की मदद से आरंभीकरण लाइन पर जाएँ:

 $ gcc -g arrays.c -o arrays $ gdb arrays (gdb) break main (gdb) run (gdb) next 

इस स्तर पर, आप चर की सामग्री को प्रदर्शित कर सकते हैं और इसके प्रकार का पता लगा सकते हैं:

 (gdb) print a $1 = {1, 2, 3} (gdb) ptype a type = int [3] 

अब जब हमारा कार्यक्रम GDB में ठीक से कॉन्फ़िगर किया गया है, तो सबसे पहले यह देखने के लिए x कमांड का उपयोग करना है कि चर "हुड के नीचे" कैसा दिखता है:

 (gdb) x/12xb &a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x7fff5fbff574: 0x03 0x00 0x00 0x00 

इसका मतलब यह है कि सरणी के लिए स्मृति स्थान 0x7fff5fbff56c पर प्रारंभ होता है। पहले चार बाइट्स में [0] , अगले चार में [1] और अंतिम चार बाइट्स [2] होते हैं । वास्तव में, आप जांच कर सकते हैं और सुनिश्चित कर सकते हैं कि आकार का पता है कि मेमोरी में वास्तव में बारह बाइट्स हैं:

 (gdb) print sizeof(a) $2 = 12 

इस बिंदु तक, सरणियां दिखती हैं जैसा कि उन्हें होना चाहिए। उनके पास सरणियों के अनुरूप प्रकार हैं और वे स्मृति के आसन्न वर्गों में सभी मूल्यों को संग्रहीत करते हैं। हालाँकि, कुछ स्थितियों में, सरणियाँ बहुत हद तक पॉइंटर्स की तरह व्यवहार करती हैं! उदाहरण के लिए, हम अंकगणित को एक में लागू कर सकते हैं:

 (gdb) print a + 1 $3 = (int *) 0x7fff5fbff570 

सामान्य शब्दों में, इसका मतलब यह है कि एक + 1 इंट का सूचक है जिसका पता 0x7fff5fbff570 है । इस बिंदु पर, आपको पहले से ही एक्स कमांड को रिफ्लेक्सिवली पास करना चाहिए, तो आइए देखें कि क्या हुआ:

 (gdb) x/4xb a + 1 0x7fff5fbff570: 0x02 0x00 0x00 0x00 


ध्यान दें कि पता 0x7fff5fbff570 0x7fff5fbff56c से ठीक चार अधिक है, अर्थात् , सरणी के पहले बाइट का पता a । यह देखते हुए कि अंतर प्रकार स्मृति में चार बाइट्स रखता है, हम यह निष्कर्ष निकाल सकते हैं कि + 1 अंक [1]

वास्तव में, सी में अनुक्रमण सरणियों सूचक अंकगणितीय के लिए वाक्यविन्यास चीनी है: एक [i] * (a + i) के बराबर है। आप इसे GDB में देख सकते हैं:

 (gdb) print a[0] $4 = 1 (gdb) print *(a + 0) $5 = 1 (gdb) print a[1] $6 = 2 (gdb) print *(a + 1) $7 = 2 (gdb) print a[2] $8 = 3 (gdb) print *(a + 2) $9 = 3 

तो, हमने देखा कि कुछ स्थितियों में एक सरणी की तरह व्यवहार होता है, और कुछ में - इसके पहले तत्व के लिए एक सूचक की तरह। क्या चल रहा है?

इसका उत्तर इस प्रकार है, जब सरणी का नाम C में एक अभिव्यक्ति में उपयोग किया जाता है, तो यह पहले तत्व के सूचक को "क्षय" करता है। इस नियम के केवल दो अपवाद हैं: जब सरणी नाम को आकार में पास किया जाता है और जब पते को लेने के लिए ऑपरेटर के साथ सरणी नाम का उपयोग किया जाता है और

तथ्य यह है कि नाम & ऑपरेटर के उपयोग करने पर पहले तत्व के लिए एक सूचक में विभाजित नहीं होता है एक दिलचस्प सवाल उठता है: क्या सूचक के बीच एक और एक विभाजन में क्या अंतर है?

संख्यात्मक रूप से, वे दोनों एक ही पते का प्रतिनिधित्व करते हैं:

 (gdb) x/4xb a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00 (gdb) x/4xb &a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00 

हालांकि, उनके प्रकार अलग हैं। जैसा कि हमने पहले ही देखा है, सरणी का नाम इसके पहले तत्व में एक सूचक में विभाजित हो जाता है और इसलिए इसे प्रकार * का होना चाहिए। प्रकार और ए के लिए , हम इसके बारे में GDB पूछ सकते हैं:

 (gdb) ptype &a type = int (*)[3] 

सीधे शब्दों में कहें, और तीन पूर्णांक की एक सरणी के लिए एक सूचक है। यह समझ में आता है: जब एक ऑपरेटर और एक प्रकार के int [3] को पारित नहीं किया जाता है और विभाजित नहीं होता है।

आप पॉइंटर के बीच के अंतर का पता लगा सकते हैं जिसमें एक ब्रेक अप और एक ऑपरेशन है कि वे पॉइंटर अंकगणित के संबंध में कैसे व्यवहार करते हैं:

 (gdb) print a + 1 $10 = (int *) 0x7fff5fbff570 (gdb) print &a + 1 $11 = (int (*)[3]) 0x7fff5fbff578 

ध्यान दें कि 1 को जोड़ने से चार इकाइयों द्वारा पते में वृद्धि होती है, जबकि पते में 1 और 1 जोड़ते हैं।

सूचक जो वास्तव में विभाजित होता है, उसका रूप और [0] है :

 (gdb) print &a[0] $11 = (int *) 0x7fff5fbff56c 

निष्कर्ष


मुझे उम्मीद है कि मैंने आपको यह समझा दिया था कि GDB C. के बारे में जानने के लिए एक सुरुचिपूर्ण शोध वातावरण है। यह आपको प्रिंट कमांड का उपयोग करके एक्सप्रेशन के साथ मेमोरी बाइट की जांच करने और ptype कमांड का उपयोग करके प्रकार के साथ काम करने की अनुमति देता है

यदि आप GDB का उपयोग करके C सीखने के साथ प्रयोग जारी रखना चाहते हैं, तो मेरे पास कुछ सुझाव हैं:

1. Ksplice पॉइंटर चैलेंज पर काम करने के लिए GDB का उपयोग करें।
2. समझें कि स्मृति में संरचनाओं को कैसे संग्रहीत किया जाता है। वे सरणियों से कैसे संबंधित हैं?
3. कोडांतरक प्रोग्रामिंग को बेहतर ढंग से समझने के लिए GDB डिस्सेम्बलर कमांड का उपयोग करें। यह विशेष रूप से मजेदार है कि फ़ंक्शन कॉल स्टैक कैसे काम करता है।
4. "टीयूआई" जीडीबी मोड की जाँच करें, जो परिचित जीडीबी पर एक ग्राफिकल नेकर्स ऐड-ऑन प्रदान करता है। ओएस एक्स पर, आपको संभवतः स्रोत से जीडीबी संकलित करना होगा।

अनुवादक से: परंपरागत रूप से, त्रुटियों को इंगित करने के लिए LAN का उपयोग करें। मुझे रचनात्मक आलोचना करने में खुशी होगी।

Source: https://habr.com/ru/post/In181738/


All Articles