एक विभाजन दोष तब होता है जब कोई प्रोग्राम उस क्षेत्र के बाहर मेमोरी तक पहुंचने की कोशिश करता है जिसे इसके लिए आवंटित किया गया है।
इस मामले में, एक अनुभवी सी प्रोग्रामर देख सकता है कि समस्या उस रेखा में हो रही है जहां sprintf
कहा जाता है। लेकिन अगर आप यह नहीं बता सकते हैं कि आपका विभाजन दोष कहाँ हो रहा है, या यदि आप इसे जानने की कोशिश करने के लिए कोड के माध्यम से पढ़ने की जहमत नहीं उठाना चाहते हैं , तो आप अपने प्रोग्राम को डिबग प्रतीकों (साथ gcc
, -g
ध्वज ) के साथ बना सकते हैं ) और फिर इसे डिबगर के माध्यम से चलाएं।
मैंने आपके स्रोत कोड की प्रतिलिपि बनाई और इसे मेरे द्वारा नामित फ़ाइल में पेस्ट कर दिया slope.c
। फिर मैंने इसे इस तरह बनाया:
gcc -Wall -g -o slope slope.c
( -Wall
वैकल्पिक है। यह सिर्फ अधिक स्थितियों के लिए चेतावनी का उत्पादन करने के लिए है। यह पता लगाने में मदद कर सकता है कि क्या गलत हो सकता है, भी।)
तब मैंने प्रोग्राम शुरू करने के लिए gdb
पहले डिबगर में प्रोग्राम चलाया, और फिर डिबगर में एक बार, डिबगर को कमांड दे रहा था :gdb ./slope
gdb
run
ek@Kip:~/source$ gdb ./slope
GNU gdb (GDB) 7.5-ubuntu
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/ek/source/slope...done.
(gdb) run
Starting program: /home/ek/source/slope
warning: Cannot call inferior functions, you have broken Linux kernel i386 NX (non-executable pages) support!
Program received signal SIGSEGV, Segmentation fault.
0x001a64cc in _IO_default_xsputn () from /lib/i386-linux-gnu/libc.so.6
(मेरे you have broken Linux kernel i386 NX
... support
संदेश के बारे में चिंता मत करो , यह gdb
प्रभावी ढंग से इस कार्यक्रम को डीबग करने से रोका नहीं जा सकता है।)
यह जानकारी अत्यधिक गूढ़ है ... और यदि आपके पास libc के लिए डिबग प्रतीक स्थापित नहीं हैं, तो आपको एक और भी अधिक गूढ़ संदेश मिलेगा जिसमें प्रतीकात्मक फ़ंक्शन नाम के बजाय हेक्साडेसिमल पता होगा _IO_default_xsputn
। सौभाग्य से, इससे कोई फर्क नहीं पड़ता, क्योंकि हम वास्तव में जानना चाहते हैं कि आपके कार्यक्रम में समस्या कहां है।
तो, समाधान पीछे की ओर देखना है, यह देखने के लिए कि सिस्टम लाइब्रेरी में उस विशेष फ़ंक्शन कॉल के लिए कौन-सी फ़ंक्शन कॉल हुईं, जहां SIGSEGV
सिग्नल को अंततः ट्रिगर किया गया था।
gdb
(और किसी भी डीबगर) में इस सुविधा का निर्माण किया गया है: इसे स्टैक ट्रेस या बैकट्रेस कहा जाता है । मैं bt
एक backtrace उत्पन्न करने के लिए डीबगर कमांड का उपयोग करता हूं gdb
:
(gdb) bt
#0 0x001a64cc in _IO_default_xsputn () from /lib/i386-linux-gnu/libc.so.6
#1 0x00178e04 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
#2 0x0019b234 in vsprintf () from /lib/i386-linux-gnu/libc.so.6
#3 0x0017ff7b in sprintf () from /lib/i386-linux-gnu/libc.so.6
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
#5 0x08048578 in main () at slope.c:52
(gdb)
आप देख सकते हैं कि आपका main
फ़ंक्शन फ़ंक्शन को कॉल करता calc_slope
है (जो आपने इरादा किया है), और फिर calc_slope
कॉल करता है sprintf
, जो (इस सिस्टम पर) कॉल के साथ कार्यान्वित किया गया है और कुछ अन्य संबंधित पुस्तकालय कार्यों के लिए।
आप आमतौर पर जिस चीज में रुचि रखते हैं, वह आपके प्रोग्राम में फ़ंक्शन कॉल है जो आपके प्रोग्राम के बाहर एक फ़ंक्शन को कॉल करती है । जब तक पुस्तकालय / पुस्तकालयों में कोई बग न हो जिसे आप उपयोग कर रहे हैं (इस मामले में, मानक सी लाइब्रेरी libc
लाइब्रेरी फ़ाइल द्वारा प्रदान की जाती है libc.so.6
), बग जो दुर्घटना का कारण बनता है वह आपके कार्यक्रम में है और अक्सर या उसके पास या उसके पास होगा आपके कार्यक्रम में अंतिम कॉल
इस मामले में, यह है:
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
यहीं से आपका कार्यक्रम पुकारता है sprintf
। हम इसे जानते हैं क्योंकि sprintf
अगला कदम है। लेकिन यहां तक कि यह बताते हुए कि, आप यह जानते हैं क्योंकि यह वही है जो लाइन 26 पर होता है , और यह कहता है:
... at slope.c:26
आपके प्रोग्राम में, लाइन 26 में शामिल हैं:
sprintf(s,"%d",curr);
(आपको हमेशा एक पाठ संपादक का उपयोग करना चाहिए जो स्वचालित रूप से लाइन नंबर दिखाता है, कम से कम उस पंक्ति के लिए जो आप वर्तमान में हैं। यह संकलन-समय त्रुटियों दोनों की व्याख्या करने में बहुत सहायक है, और डिबगर का उपयोग करते समय रनटाइम समस्याओं का पता चलता है।)
जैसा कि डेनिस कार्सेमाकर के जवाब में चर्चा की गई है , s
एक-एक बाइट है। (शून्य नहीं है, क्योंकि आपने जो मान दिया है ""
, वह एक बाइट लंबी है, यानी यह { '\0' }
उसी के बराबर है , उसी तरह है "Hello, world!\n"
जो बराबर है { 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }
।)
तो, क्यों यह अभी भी कुछ मंच पर काम कर सकता है (और जाहिर है जब विंडोज के लिए VC9 के साथ संकलित किया जाता है)?
लोग अक्सर कहते हैं कि जब आप मेमोरी आवंटित करते हैं और फिर इसके बाहर मेमोरी एक्सेस करने की कोशिश करते हैं, तो यह एक त्रुटि पैदा करता है। लेकिन यह वास्तव में सच नहीं है। सी और सी ++ तकनीकी मानकों के अनुसार, यह वास्तव में जो भी उत्पादन करता है वह अपरिभाषित व्यवहार है।
दूसरे शब्दों में, कुछ भी हो सकता है!
फिर भी, कुछ चीजें दूसरों की तुलना में अधिक संभावना हैं। ऐसा क्यों है कि स्टैक पर एक छोटा सा सरणी, कुछ कार्यान्वयन पर, स्टैक पर एक बड़े सरणी की तरह काम करता दिखाई देता है?
यह नीचे आता है कि स्टैक आवंटन कैसे लागू किया जाता है, जिसे प्लेटफ़ॉर्म से प्लेटफ़ॉर्म पर अलग-अलग करने की अनुमति है। आपका निष्पादन योग्य अपने स्टैक की तुलना में अधिक मेमोरी आवंटित कर सकता है, जिसका वास्तव में किसी एक समय में उपयोग करने का इरादा है। कभी-कभी यह आपको स्मृति स्थानों पर लिखने की अनुमति दे सकता है जो आपने अपने कोड में स्पष्ट रूप से दावा नहीं किया है । यह बहुत संभव है कि जब VC9 में आपका प्रोग्राम बन जाए तो यही हो।
हालाँकि, आपको VC9 में भी इस व्यवहार पर भरोसा नहीं करना चाहिए। यह संभावित रूप से पुस्तकालयों के विभिन्न संस्करणों पर निर्भर हो सकता है जो विभिन्न विंडोज सिस्टम पर मौजूद हो सकते हैं। लेकिन इससे भी अधिक समस्या यह है कि अतिरिक्त स्टैक स्पेस को इस आशय के साथ आवंटित किया जाता है कि यह वास्तव में उपयोग किया जाएगा, और इसलिए इसका वास्तव में उपयोग किया जा सकता है।तब आप "अपरिभाषित व्यवहार" की पूरी रात का अनुभव करते हैं, जहां, इस मामले में, एक से अधिक चर एक ही स्थान पर संग्रहीत किए जा सकते हैं, जहां एक को लिखना दूसरे को अधिलेखित करता है ... लेकिन हमेशा नहीं, क्योंकि कभी-कभी चर को लिखता है रजिस्टरों में कैश किया जाता है और वास्तव में तुरंत नहीं किया जाता है (या चर को पढ़ता है, कैश किया जा सकता है, या एक वैरिएबल को उसी तरह माना जा सकता है जैसा कि पहले था, क्योंकि इसे आवंटित की गई मेमोरी को कंपाइलर के माध्यम से नहीं लिखा जाता है। चर)।
और यह मुझे इस बात के लिए अन्य संभावित संभावना के लिए लाता है कि जब वीसी 9 के साथ बनाया गया था तो कार्यक्रम क्यों काम किया। यह संभव है, और कुछ हद तक संभावना है कि एक सरणी बाइट के बाद स्थान का उपयोग करने के लिए कुछ सरणी या अन्य चर वास्तव में आपके प्रोग्राम द्वारा आवंटित किया गया था (जिसमें आपके प्रोग्राम का उपयोग कर रहे एक पुस्तकालय द्वारा आवंटित किया जा सकता है) s
। तो, s
एक बाइट की तुलना में लंबे समय तक एक सरणी के रूप में व्यवहार करने से उस / उन चर / सरणियों की सामग्री तक पहुंचने का प्रभाव होगा, जो खराब भी हो सकते हैं।
अंत में, जब आप इस तरह की गलती करते हैं, तो "सेगमेंटेशन फॉल्ट" या "सामान्य सुरक्षा दोष" जैसी त्रुटि प्राप्त करना भाग्यशाली होता है। जब आपके पास ऐसा नहीं होता है, तब तक आपको पता नहीं चल सकता है कि जब तक आपके कार्यक्रम का अपरिभाषित व्यवहार नहीं हो जाता है तब तक बहुत देर हो चुकी है ।