2011-06-23 14 views
14

नीचे दिए गए कोड को किसी भी क्रैश @ रनटाइम के बिना क्यों काम करता है?जब मैं किसी सरणी के अंत में लिखता हूं तो मेरा प्रोग्राम क्रैश क्यों नहीं होता है?

और आकार मशीन/प्लेटफ़ॉर्म/कंपाइलर पर पूरी तरह से निर्भर है !! मैं 64-बिट मशीन में 200 तक भी दे सकता हूं। ओएस में मुख्य समारोह में सेगमेंटेशन गलती कैसे पता चलेगी?

void main(int argc, char* argv[]) 
{ 
    int arr[3]; 
    arr[4] = 99; 
} 

यह बफर स्पेस कहां से आता है? क्या यह एक प्रक्रिया में आवंटित ढेर है?

+7

स्टैक ओवरफ़्लो तब होता है जब ढेर से बहुत अधिक मेमोरी आवंटित की जाती है। इस मामले में, 'sizeof (int) == 4' मानते हुए, आपने स्टैक से कम से कम 12 बाइट आवंटित किए हैं। आपका कोड एक सरणी के अंत से परे लिख रहा है। यह ढेर अतिप्रवाह नहीं है। यह _undefined व्यवहार_ है। –

+0

उसी स्थान से आता है जैसे आपको अपनी बाकी रैम मिलती है, शायद जो भी आपको कंप्यूटर बेचती है। 'arr [3]' का अर्थ है "मेरे उपयोग के लिए उपलब्ध अंतरिक्ष के 3 'int' को निर्दिष्ट करें", इसका अर्थ यह नहीं है कि "ईथर के बाहर अंतरिक्ष का 3 'int' बनाएं", हालांकि यह कानूनी कार्यान्वयन होगा यदि यह था शारीरिक रूप से संभव है। आप जो कुछ भी स्मृति/पता 'arr' के निकट होने के लिए होता है) (अच्छी तरह से, अगले दरवाजे-लेकिन-वास्तव में), जो डेविड कहते हैं, यूबी है। हां, यह आपके ढेर का हिस्सा है (सी और सी ++ मानक स्टैक के बारे में बात नहीं करते हैं, लेकिन व्यावहारिक रूप से जहां स्वचालित चर चलते हैं)। –

+0

@vprajan - मैंने प्रश्न को प्रतिबिंबित करने के लिए अपना शीर्षक अपडेट किया है, क्योंकि ध्यान आकर्षित करने के लिए यहां एक अच्छा जवाब है। –

उत्तर

67

कुछ ऐसा जो मुझे शिक्षा-प्रयोजनों के लिए कुछ समय पहले लिखा ...

int q[200]; 

main(void) { 
    int i; 
    for(i=0;i<2000;i++) { 
     q[i]=i; 
    } 
} 

यह संकलन और यह क्रियान्वित करने के बाद, एक कोर डंप का उत्पादन किया है::

निम्नलिखित सी कार्यक्रम पर विचार करें

$ gcc -ggdb3 segfault.c 
$ ulimit -c unlimited 
$ ./a.out 
Segmentation fault (core dumped) 

अब gdb का उपयोग कर पोस्टमार्टम विश्लेषण करने के लिए:

$ gdb -q ./a.out core 
Program terminated with signal 11, Segmentation fault. 
[New process 7221] 
#0 0x080483b4 in main() at s.c:8 
8  q[i]=i; 
(gdb) p i 
$1 = 1008 
(gdb) 

हू, कार्यक्रम को आवंटित 200 आइटमों के बाहर लिखा गया था, तो प्रोग्राम segfault नहीं था यह क्रैश हो गया जब मैं = 1008, क्यों?

पृष्ठों को दर्ज करें।

#include <stdio.h> 
#include <unistd.h> // sysconf(3) 

int main(void) { 
    printf("The page size for this system is %ld bytes.\n", 
      sysconf(_SC_PAGESIZE)); 

    return 0; 
} 

जो उत्पादन देता है:

एक यूनिक्स/लिनक्स पर कई मायनों में पृष्ठ आकार निर्धारित कर सकते हैं, एक तरह से प्रणाली समारोह को sysconf() इस तरह उपयोग करने के लिए है

इस प्रणाली के लिए पृष्ठ का आकार 4096 बाइट है।

या एक इस तरह कमांडलाइन उपयोगिता getconf उपयोग कर सकते हैं:

$ getconf PAGESIZE 
4096 

पोस्टमार्टम

ऐसा लगता है कि segfault पर मैं = 200 नहीं होता है, लेकिन मैं = 1008 पर, आइए क्यों पता करें। शुरू gdb कुछ पोस्टमार्टम ananlysis करना है:

$gdb -q ./a.out core 

Core was generated by `./a.out'. 
Program terminated with signal 11, Segmentation fault. 
[New process 4605] 
#0 0x080483b4 in main() at seg.c:6 
6   q[i]=i; 
(gdb) p i 
$1 = 1008 
(gdb) p &q 
$2 = (int (*)[200]) 0x804a040 
(gdb) p &q[199] 
$3 = (int *) 0x804a35c 

क्ष पता 0x804a35c पर पर समाप्त हो गया, या बल्कि, क्ष के अंतिम बाइट [199] उस स्थान पर था। पेज का आकार जैसा कि हमने पहले 4096 बाइट्स देखा था और मशीन के 32-बिट शब्द का आकार देता है कि वर्चुअल एड्रेस 20-बिट पेज नंबर और 12-बिट ऑफ़सेट में टूट जाता है।

क्ष [] में आभासी पृष्ठ संख्या समाप्त हो गया:

0x804a = 32,842 ऑफसेट:

0x35c = 860 तो वहाँ अभी भी थे:

4096 - 864 = 3232 बाइट्स कि पर छोड़ दिया स्मृति का पृष्ठ जिस पर q [] आवंटित किया गया था। यही कारण है कि अंतरिक्ष पकड़ कर सकते हैं:

3232/4 = 808 पूर्णांकों, और कोड यह इलाज किया जैसे कि वह 1008

को स्थिति 200 पर क्ष के तत्वों का समावेश है हम सभी जानते हैं कि उन तत्वों मौजूद नहीं करते हैं और संकलक ने शिकायत नहीं की, न ही एचडब्ल्यू किया क्योंकि हमारे पास उस पृष्ठ पर लिखने की अनुमति है। केवल जब मैं = 1008 q था [] एक अलग पृष्ठ पर एक पते का संदर्भ लें जिसके लिए हमारे पास लेखन अनुमति नहीं थी, वर्चुअल मेमोरी एचडब्ल्यू ने इसका पता लगाया और एक सेगफॉल्ट ट्रिगर किया।

एक पूर्णांक 4 बाइट्स में संग्रहीत किया जाता है, जिसका अर्थ है कि इस पृष्ठ में 808 (3236/4) अतिरिक्त नकली तत्व हैं जिसका अर्थ है कि यह अभी भी q [200], q [201] से इन तत्वों तक पहुंचने के लिए पूरी तरह से कानूनी है एक एसईजी गलती ट्रिगर किए बिना तत्व 199 + 808 = 1007 (क्यू [1007]) तक। Q [1008] तक पहुंचने पर आप एक नया पृष्ठ दर्ज करते हैं जिसके लिए अनुमति अलग होती है।

+4

+1 और इच्छा है कि मैं दो बार – SJuan76

+0

+1 वोट कर सकता हूं, एक उत्कृष्ट उत्तर! – Nim

+0

+! .. उत्कृष्ट .......... –

5

चूंकि आप अपनी सरणी की सीमाओं के बाहर लिख रहे हैं, तो आपके कोड का व्यवहार अपरिभाषित में है।

यह अपरिभाषित व्यवहार की प्रकृति है कि कुछ भी हो सकता है segfaults की कमी (संकलक का दायित्व नहीं है सीमा जाँच प्रदर्शन करने के लिए) सहित, है।

आप स्मृति को लिख रहे हैं जिसे आपने आवंटित नहीं किया है, लेकिन ऐसा होता है और वह - शायद - किसी और चीज़ के लिए उपयोग नहीं किया जा रहा है। आपका कोड अलग ढंग से व्यवहार हो सकता है अगर आप अपने ओएस, संकलक, अनुकूलन झंडे आदि

दूसरे शब्दों में करने के लिए कोड का दूसरे से असंबद्ध लगने भागों में परिवर्तन करने, एक बार आप उस क्षेत्र में कर रहे हैं, सभी दांव बंद कर रहे हैं।

2

यह अनिर्धारित व्यवहार है - आप बस किसी भी समस्या का पालन नहीं करते हैं। सबसे संभावित कारण यह है कि आप स्मृति के क्षेत्र को ओवरराइट करते हैं, प्रोग्राम व्यवहार पहले पर निर्भर नहीं करता है - वह स्मृति तकनीकी रूप से लिखने योग्य है (अधिकांश मामलों में स्टैक आकार लगभग 1 मेगाबाइट आकार में है) और आपको कोई त्रुटि संकेत नहीं दिखाई देता है। आपको इस पर भरोसा नहीं करना चाहिए।

0

आपके कोड में अनिश्चित व्यवहार है। इसका मतलब है कि यह कुछ भी या कुछ भी नहीं कर सकता है। आपके कंपाइलर और ओएस आदि के आधार पर, यह क्रैश हो सकता है।

यह कहा गया है कि यदि आपके कोड अधिकतर कंपाइलर नहीं हैं तो संकलित नहीं होंगे।

ऐसा इसलिए है क्योंकि आपके पास void main है, जबकि सी मानक और सी ++ मानक दोनों को int main की आवश्यकता है।

केवल संकलक void main के साथ खुश है कि बारे में माइक्रोसॉफ्ट ’ रों, दृश्य C++ है।

एक संकलक दोष है यही कारण है, लेकिन जब से माइक्रोसॉफ्ट और उदाहरण प्रलेखन के बहुत सारे भी कोड पीढ़ी उपकरण है कि void main उत्पन्न है, तो वे संभावित इसे ठीक कर कभी नहीं होगा। हालांकि, मान लें कि माइक्रोसॉफ्ट-विशिष्ट void main लिखना मानक int main से टाइप करने के लिए एक वर्ण अधिक है। तो मानकों के साथ क्यों नहीं जाते?

चीयर्स & hth।,

0

एक सेगमेंटेशन गलती तब होती है जब कोई प्रक्रिया उस पृष्ठ में किसी पृष्ठ को ओवरराइट करने का प्रयास करती है, जिसका स्वामित्व नहीं है; जब तक आप अंत में एक लंबा सफर तय नहीं करते हैं, तो आप बफर हैं, आप एक सीजी गलती को ट्रिगर नहीं करेंगे।

स्टैक कहीं भी आपके आवेदन के स्वामित्व वाली स्मृति के ब्लॉक में से एक में स्थित है। इस उदाहरण में यदि आप कुछ महत्वपूर्ण ओवरराइट नहीं किया है तो आप भाग्यशाली रहे हैं। आपने शायद कुछ अप्रयुक्त स्मृति को अधिलेखित किया है। यदि आप थोड़ा अधिक दुर्भाग्यपूर्ण थे तो आपने स्टैक पर किसी अन्य फ़ंक्शन के स्टैक फ्रेम को ओवरराइट कर दिया होगा।

3

एक सरणी प्रकार का उपयोग करके, जिसे सी ++ सी से विरासत में मिला है, आप निस्संदेह एक सीमा जांच न करने के लिए कह रहे हैं।

आप कोशिश अगर यह बजाय

void main(int argc, char* argv[]) 
{  
    std::vector<int> arr(3); 

    arr.at(4) = 99; 
} 

आप एक अपवाद फेंका जाएगा।

तो सी ++ एक चेक और अनचेक इंटरफेस दोनों प्रदान करता है। आप जिस पर उपयोग करना चाहते हैं उसे चुनने के लिए आप पर निर्भर है।

1

अपने प्रश्न का उत्तर देने के लिए यह "ज्ञात नहीं" क्यों है: अधिकांश सी कंपाइलर्स संकलन समय पर विश्लेषण नहीं करते हैं कि आप पॉइंटर्स और मेमोरी के साथ क्या कर रहे हैं, और इसलिए कोई भी संकलित समय पर नोटिस नहीं करता है जिसे आपने कुछ खतरनाक लिखा है। रनटाइम पर, कोई नियंत्रित, प्रबंधित वातावरण भी नहीं है जो आपके मेमोरी संदर्भों को बेबीसिट करता है, इसलिए कोई भी आपको उस स्मृति को पढ़ने से रोकता है जिसे आप हकदार नहीं हैं। उस बिंदु पर स्मृति आपको आवंटित की जाती है (क्योंकि यह आपके फ़ंक्शन से बहुत दूर स्टैक का हिस्सा है), इसलिए ओएस को इसके साथ कोई समस्या नहीं है।

यदि आप अपनी याददाश्त तक पहुंचते समय हाथ पकड़ना चाहते हैं, तो आपको जावा या सीएलआई जैसे प्रबंधित वातावरण की आवश्यकता है, जहां आपका पूरा कार्यक्रम दूसरे द्वारा चलाया जाता है, प्रबंधन कार्यक्रम जो उन अपराधों को देखता है।

4

वास्तव में के बारे में जब/जहां एक स्थानीय चर बफर अतिप्रवाह दुर्घटनाओं में कुछ कारकों पर निर्भर करता:

  1. पहले से ही ढेर पर डेटा की मात्रा समय जो बह निकला चर का उपयोग होता है समारोह कहा जाता है पर
  2. कुल

में उमड़ती चर/सरणी में लिखा डेटा की

  • राशि याद रखें कि ढेर हो जाना नीचे की ओर। अर्थात। प्रक्रिया निष्पादन स्टैक के रूप में उपयोग की जाने वाली स्मृति के अंत के करीब एक स्टैकपोइंटर के साथ शुरू होता है। यह आखिरी मैप किए गए शब्द पर शुरू नहीं होता है, और ऐसा इसलिए है क्योंकि सिस्टम का प्रारंभिक कोड सृजन समय पर प्रक्रिया के लिए कुछ प्रकार की "स्टार्टअप जानकारी" पास करने का निर्णय ले सकता है, और अक्सर स्टैक पर ऐसा करता है।

    यह सामान्य विफलता मोड - ओवरफ़्लो कोड वाले फ़ंक्शन से लौटने पर एक क्रैश होता है।

    कुल डेटा की मात्रा ढेर पर एक बफर में लिखा (कॉल/प्रवर्तन कोड/अन्य चर द्वारा) पहले प्रयोग की कुल stackspace की राशि तो आप जो कुछ भी स्मृति का उपयोग पर एक दुर्घटना मिल जाएगा की तुलना में बड़ा है पहले ढेर के शीर्ष (शुरुआत) से परे चलाता है। क्रैकिंग पता केवल एक पृष्ठ सीमा से पहले होगा - SIGSEGV स्टैक के शीर्ष से परे स्मृति तक पहुंचने के कारण, जहां कुछ भी मैप नहीं किया गया है।

    हैं कि कुल इस समय ढेर के अधिक इस्तेमाल किया भाग के आकार से भी कम है, तो यह सिर्फ ठीक काम करते हैं और क्रैश करेंगे बाद में - वास्तव में, प्लेटफार्मों कि ढेर पर वापसी पते की दुकान पर (जो x86/x64 के लिए सच है), आपके फ़ंक्शन से लौटने पर। ऐसा इसलिए है क्योंकि सीपीयू निर्देश ret वास्तव में स्टैक (वापसी पता) से एक शब्द लेता है और वहां निष्पादन को पुनर्निर्देशित करता है। यदि अपेक्षित कोड स्थान के बजाय इस पते में जो भी कचरा है, तो एक अपवाद होता है और आपका प्रोग्राम मर जाता है।

    इसे दर्शाने के लिए: जब main() कहा जाता है, ढेर (एक 32bit 86 यूनिक्स कार्यक्रम पर) इस तरह दिखता है:

    [ esp   ] <return addr to caller> (which exits/terminates process) 
    [ esp + 4  ] argc 
    [ esp + 8  ] argv 
    [ esp + 12  ] envp <third arg to main() on UNIX - environment variables> 
    [ ...   ] 
    [ ...   ] <other things - like actual strings in argv[], envp[] 
    [ END   ] PAGE_SIZE-aligned stack top - unmapped beyond 
    

    main() शुरू होता है, यह विभिन्न प्रयोजनों के लिए ढेर पर अंतरिक्ष आवंटित करेगा जब, के बीच दूसरों को आपके टू-ओवर-ओवरफ्लोर्ड सरणी की मेजबानी करने के लिए। यह बात की तरह लग कर देगा:

    [ esp   ] <current bottom end of stack> 
    [ ...   ] <possibly local vars of main()> 
    [ esp + X  ] arr[0] 
    [ esp + X + 4 ] arr[1] 
    [ esp + X + 8 ] arr[2] 
    [ esp + X + 12 ] <possibly other local vars of main()> 
    [ ...   ] <possibly other things (saved regs)> 
    
    [ old esp  ] <return addr to caller> (which exits/terminates process) 
    [ old esp + 4 ] argc 
    [ old esp + 8 ] argv 
    [ old esp + 12 ] envp <third arg to main() on UNIX - environment variables> 
    [ ...   ] 
    [ ...   ] <other things - like actual strings in argv[], envp[] 
    [ END   ] PAGE_SIZE-aligned stack top - unmapped beyond 
    

    इसका मतलब है आप arr[2] परे खुशी से पहुँच रास्ता कर सकते हैं।

    अलग बफर अतिप्रवाह से उत्पन्न दुर्घटनाओं की एक टाइस्टर के लिए, यह एक प्रयास:

    #include <stdlib.h> 
    #include <stdio.h> 
    
    int main(int argc, char **argv) 
    { 
        int i, arr[3]; 
    
        for (i = 0; i < atoi(argv[1]); i++) 
         arr[i] = i; 
    
        do { 
         printf("argv[%d] = %s\n", argc, argv[argc]); 
        } while (--argc); 
    
        return 0; 
    } 
    

    और देखते हैं जब आप एक छोटे से (जैसे कि, 10) थोड़ा करके बफर अतिप्रवाह कैसे अलग दुर्घटना हो जाएगा , जब आप इसे ढेर के अंत से आगे बहते हैं तो इसकी तुलना में। इसे विभिन्न अनुकूलन स्तरों और विभिन्न कंपाइलरों के साथ आज़माएं। काफी दृष्टांत, क्योंकि यह दोनों दुर्व्यवहार दिखाता है (हमेशा argv[] सही ढंग से प्रिंट नहीं करेगा) साथ ही साथ विभिन्न स्थानों में दुर्घटनाएं, यहां तक ​​कि अंतहीन लूप भी (यदि, उदाहरण के लिए, कंपाइलर i या argc स्टैक में रखता है और कोड इसे ओवरराइट करता है सूचित करते रहना)।

  • संबंधित मुद्दे

     संबंधित मुद्दे