c - यह कुछ प्लेटफार्मों पर पाश से बाहर निकलने के लिए क्यों है और दूसरों पर नहीं?




debugging undefined-behavior (10)

उबंटू 14.04 पर चलने वाले मेरे लैपटॉप पर, यह कोड नहीं टूटता है यह पूरा होने तक चलता है। मेरे स्कूल के कंप्यूटर पर CentOS 6.6 चल रहा है, यह भी ठीक चलता है। विंडोज 8.1 पर, लूप कभी समाप्त नहीं होता है।

जब मैं लूप के for सशर्त को संपादित करता हूं तो और अधिक अजीब बात है: i <= 11 , कोड केवल मेरे लैपटॉप पर उबंटू चलाने पर समाप्त हो जाता है। CentOS और Windows कभी समाप्त नहीं होता है।

आपने सिर्फ मेमोरी स्टॉम्पिंग की खोज की है। आप इसके बारे में और अधिक यहाँ पढ़ सकते हैं: "मेमोरी स्टॉम्प" क्या है?

जब आप int array[10],i; आवंटित करते हैं int array[10],i; , वे चर स्मृति में जाते हैं (विशेष रूप से, उन्हें स्टैक पर आवंटित किया जाता है, जो कि फ़ंक्शन से जुड़ा मेमोरी का एक ब्लॉक है)। array[] और i शायद स्मृति में एक दूसरे से सटे हुए हैं। ऐसा लगता है कि विंडोज 8.1 पर, i array[10] पर स्थित i । CentOS पर, i array[11] पर स्थित i । और उबंटू पर, यह न तो स्पॉट में है (शायद यह array[-1] ?)।

इन डिबगिंग स्टेटमेंट्स को अपने कोड में जोड़ने का प्रयास करें। आपको ध्यान देना चाहिए कि पुनरावृति 10 या 11 पर, array[i] बिंदुओं पर।

#include <stdio.h>

int main() 
{ 
  int array[10],i; 

  printf ("array: %p, &i: %p\n", array, &i); 
  printf ("i is offset %d from array\n", &i - array);

  for (i = 0; i <=11 ; i++) 
  { 
    printf ("%d: Writing 0 to address %p\n", i, &array[i]); 
    array[i]=0; /*code should never terminate*/ 
  } 
  return 0; 
} 

मैंने हाल ही में सी सीखना शुरू किया है और मैं विषय के रूप में सी के साथ एक कक्षा ले रहा हूं। मैं वर्तमान में लूप्स के साथ खेल रहा हूं और मैं कुछ अजीब व्यवहार में चल रहा हूं, जो मुझे नहीं पता कि मैं कैसे समझाऊं।

#include <stdio.h>

int main()
{
  int array[10],i;

  for (i = 0; i <=10 ; i++)
  {
    array[i]=0; /*code should never terminate*/
    printf("test \n");

  }
  printf("%d \n", sizeof(array)/sizeof(int));
  return 0;
}

मेरे लैपटॉप पर Ubuntu 14.04 चल रहा है, यह कोड नहीं टूटता है। यह पूरा करने के लिए चलाता है। मेरे स्कूल के कंप्यूटर पर CentOS 6.6 चल रहा है, यह भी ठीक चलता है। विंडोज 8.1 पर, लूप कभी समाप्त नहीं होता है।

इससे भी अधिक विचित्र बात यह है कि जब मैं लूप की स्थिति को संपादित करता हूं: i <= 11 , कोड केवल मेरे लैपटॉप पर उबंटू चल रहा है। यह कभी भी CentOS और Windows में समाप्त नहीं होता है।

क्या कोई समझा सकता है कि मेमोरी में क्या हो रहा है और एक ही कोड चलाने वाले विभिन्न OS अलग परिणाम क्यों देते हैं?

संपादित करें: मुझे पता है कि लूप सीमा से बाहर चला जाता है। मैं इसे जानबूझकर कर रहा हूं। मैं अभी यह पता नहीं लगा सकता कि विभिन्न ओएस और कंप्यूटरों में व्यवहार कैसे भिन्न हो सकता है।


आप int array[10] घोषित करते हैं int array[10] इसका मतलब है कि array में इंडेक्स 0 से 9 (कुल 10 पूर्णांक तत्व इसे पकड़ सकते हैं)। लेकिन निम्नलिखित लूप,

for (i = 0; i <=10 ; i++)

लूप 0 से 10 अर्थ 11 समय है। इसलिए जब i = 10 यह बफर को ओवरफ्लो करेगा और अनिर्धारित व्यवहार का कारण होगा।

तो यह प्रयास करें:

for (i = 0; i < 10 ; i++)

या,

for (i = 0; i <= 9 ; i++)

इस संभावना से परे कि स्मृति रखी जा सकती है ताकि a[10] लिखने का प्रयास वास्तव में i अधिलेखित कर दे, यह भी संभव होगा कि एक अनुकूलन संकलक यह निर्धारित कर सके कि लूप परीक्षण दस से अधिक i मान के साथ नहीं पहुँचा जा सकता है। बिना कोड वाले पहले गैर-मौजूद एरेस एलीमेंट तक पहुँचा a[10]

चूंकि उस तत्व तक पहुंचने का प्रयास अपरिभाषित व्यवहार होगा, इसलिए कंपाइलर के पास इस बात के संबंध में कोई बाध्यता नहीं होगी कि प्रोग्राम उस बिंदु पर क्या कर सकता है। अधिक विशेष रूप से, चूंकि संकलक के पास किसी भी मामले में लूप इंडेक्स की जांच करने के लिए कोड उत्पन्न करने की कोई बाध्यता नहीं होगी, जहां यह दस से अधिक हो सकता है, यह कोड को उत्पन्न करने के लिए कोई दायित्व नहीं होगा; इसके बजाय यह मान सकते हैं कि <=10 परीक्षण हमेशा सही होगा। ध्यान दें कि यह तब भी सही होगा जब कोड लिखने के बजाय a[10] पढ़ेगा।


खैर, सी संकलक पारंपरिक रूप से सीमा की जांच नहीं करता है। यदि आप अपनी प्रक्रिया के लिए "संबंधित" नहीं होते हैं, तो आप एक विभाजन दोष प्राप्त कर सकते हैं। हालांकि, स्थानीय चर को स्टैक पर आवंटित किया जाता है और जिस तरह से मेमोरी आवंटित की जाती है, उसके आधार पर, सरणी ( array[10] ) से परे का क्षेत्र प्रक्रिया 'मेमोरी सेगमेंट' से संबंधित हो सकता है। इस प्रकार, कोई विभाजन दोष नहीं फेंका गया है और यही वह है जो आप अनुभव करते हैं। जैसा कि अन्य ने बताया है, यह C में अपरिभाषित व्यवहार है और आपके कोड को अनियमित माना जा सकता है। चूंकि आप C सीख रहे हैं, आप अपने कोड में सीमा के लिए जाँच करने की आदत से बेहतर हैं।


जब से आपने आकार 10 की एक सरणी बनाई है, लूप की स्थिति निम्नानुसार होनी चाहिए:

int array[10],i;

for (i = 0; i <10 ; i++)
{

वर्तमान में आप array[10] का उपयोग करते हुए मेमोरी से अ-असाइन किए गए स्थान तक पहुँचने का प्रयास कर रहे हैं और यह अपरिभाषित व्यवहार का कारण बन रहा है । अपरिभाषित व्यवहार का मतलब है कि आपका कार्यक्रम अनिर्धारित फैशन का व्यवहार करेगा, इसलिए यह प्रत्येक निष्पादन में विभिन्न आउटपुट दे सकता है।


जावा के विपरीत, C सरणी सीमा की जांच नहीं करता है, अर्थात, कोई ArrayIndexOutOfBoundsException नहीं है, यह सुनिश्चित करने का कार्य है कि सरणी इंडेक्स मान्य है जो प्रोग्रामर पर छोड़ दिया गया है। इस उद्देश्य को करने से अपरिभाषित व्यवहार होता है, कुछ भी हो सकता है।

एक सरणी के लिए:

int array[10]

इंडेक्स 0 से 9 की रेंज में ही मान्य हैं। हालाँकि, आप इसके लिए प्रयास कर रहे हैं:

for (i = 0; i <=10 ; i++)

ऐक्सेस array[10] यहाँ, कंडीशन को i < 10 बदलें


बग कोड के इन टुकड़ों के बीच स्थित है:

int array[10],i;

for (i = 0; i <=10 ; i++)

array[i]=0;

चूंकि array केवल 10 तत्व हैं, अंतिम पुनरावृत्ति array[10] = 0; एक बफर अतिप्रवाह है। बफ़र ओवरफ्लो UNDEFINED BEHAVIOR हैं , जिसका अर्थ है कि वे आपकी हार्ड ड्राइव को प्रारूपित कर सकते हैं या राक्षसों को आपकी नाक से बाहर निकालने का कारण बन सकते हैं।

सभी स्टैक वेरिएबल्स को एक दूसरे के समीप रखना काफी आम है। अगर i उस स्थान पर स्थित i , जहां पर array[10] लिखता है, तो यूबी i 0 से रीसेट कर देगा, इस प्रकार से अनियंत्रित लूप के लिए अग्रणी होगा।

ठीक करने के लिए, लूप की स्थिति को i < 10 बदलें।


मैं कुछ सुझाव दूंगा जो मुझे ऊपर मिले।

सरणी प्रदान करने का प्रयास करें [i] = 20;

मुझे लगता है कि यह कोड को हर जगह समाप्त कर देना चाहिए .. (आपने मुझे रखा है <= 10 या ll)

यदि यह चलता है तो आप दृढ़ता से यह तय कर सकते हैं कि यहाँ पहले से ही दिए गए उत्तर सही हैं [स्मृति से संबंधित उत्तर पूर्व के लिए स्टंपिंग]।


यहां दो चीजें गलत हैं। इंट मैं वास्तव में एक सरणी तत्व, सरणी [10] है, जैसा कि स्टैक पर देखा गया है। क्योंकि आपने अनुक्रमण को वास्तव में सरणी बनाने की अनुमति दी है [10] = 0, लूप इंडेक्स, i, कभी भी 10 से अधिक नहीं होगा। इसे for(i=0; i<10; i+=1) बनाएं।

i ++ है, जैसा कि K&R इसे कहते हैं, 'खराब शैली'। यह मुझे i के आकार से बढ़ाता है, न कि 1. i ++ सूचक गणित के लिए है और i + = 1 बीजगणित के लिए है। जबकि यह संकलक पर निर्भर करता है, यह पोर्टेबिलिटी के लिए एक अच्छा सम्मेलन नहीं है।


लूप का अंतिम भाग क्या होना चाहिए, आप array[10] लिखते हैं, लेकिन सरणी में केवल 10 तत्व हैं, 0 के माध्यम से गिने गए। 9 सी भाषा विनिर्देश कहता है कि यह "अपरिभाषित व्यवहार" है। व्यवहार में इसका मतलब यह है कि आपका कार्यक्रम स्मृति के अंतःकृत टुकड़े को लिखने का प्रयास करेगा जो स्मृति में array तुरंत बाद निहित है। तब क्या होता है यह इस बात पर निर्भर करता है कि वास्तव में क्या है, वहां झूठ बोलते हैं, और यह न केवल ऑपरेटिंग सिस्टम पर, बल्कि कंपाइलर पर, कंपाइलर विकल्पों (जैसे अनुकूलन सेटिंग्स) पर, प्रोसेसर आर्किटेक्चर पर, आसपास के कोड पर निर्भर करता है। , आदि यह निष्पादन से लेकर निष्पादन तक भी भिन्न हो सकते हैं, जैसे पता स्थान यादृच्छिकरण के कारण (शायद यह खिलौना उदाहरण पर नहीं है, लेकिन वास्तविक जीवन में ऐसा होता है)। कुछ संभावनाओं में शामिल हैं:

  • स्थान का उपयोग नहीं किया गया था। पाश सामान्य रूप से समाप्त हो जाता है।
  • स्थान का उपयोग उस चीज के लिए किया गया था जिसका मान 0 हुआ। लूप सामान्य रूप से समाप्त हो जाता है।
  • स्थान में फ़ंक्शन का पता पता था। लूप सामान्य रूप से समाप्त हो जाता है, लेकिन फिर प्रोग्राम क्रैश हो जाता है क्योंकि यह पता 0 पर कूदने की कोशिश करता है।
  • स्थान में चर i । लूप कभी समाप्त नहीं होता क्योंकि i 0 पर पुनरारंभ करता i
  • स्थान में कुछ अन्य चर हैं। लूप सामान्य रूप से समाप्त हो जाता है, लेकिन तब "दिलचस्प" चीजें होती हैं।
  • स्थान एक अमान्य मेमोरी एड्रेस है, उदाहरण के लिए क्योंकि वर्चुअल मेमोरी पेज के अंत में array सही है और अगला पेज मैप नहीं किया गया है।
  • catb.org/jargon/html/N/nasal-demons.html । सौभाग्य से अधिकांश कंप्यूटरों में अपेक्षित हार्डवेयर की कमी होती है।

आपने विंडोज पर जो देखा वह यह था कि कंपाइलर ने चर को मेमोरी में सरणी के तुरंत बाद रखने का फैसला किया, इसलिए array[10] = 0 ने i असाइन करना समाप्त कर दिया। उबंटू और सेंटोस पर, कंपाइलर ने i वहां जगह नहीं दी। लगभग सभी सी कार्यान्वयन स्मृति में, मेमोरी स्टैक पर , एक प्रमुख अपवाद के साथ समूह स्थानीय चर करते हैं: कुछ स्थानीय चर पूरी तरह से registers में रखे जा सकते हैं। भले ही चर स्टैक पर हो, चर का क्रम संकलक द्वारा निर्धारित किया जाता है, और यह न केवल स्रोत फ़ाइल में आदेश पर निर्भर हो सकता है, बल्कि उनके प्रकारों पर (मेमोरी को संरेखित करने के लिए बर्बाद होने से बचने के लिए जो छेद छोड़ देगा) उनके नाम पर, संकलक के आंतरिक डेटा संरचना आदि में उपयोग किए गए कुछ हैश मूल्य पर।

यदि आप यह जानना चाहते हैं कि आपके कंपाइलर ने क्या करने का फैसला किया है, तो आप इसे कोडांतरक कोड दिखाने के लिए कह सकते हैं। ओह, और कोडांतरक को समझना सीखें (इसे लिखना आसान है)। जीसीसी (और कुछ अन्य संकलक, विशेष रूप से यूनिक्स दुनिया में) के साथ, बाइनरी के बजाय कोडांतरक कोड का उत्पादन करने के लिए विकल्प -S पास करते हैं। उदाहरण के लिए, अनुकूलन विकल्प -O0 (नो ऑप्टिमाइज़ेशन) के साथ GCC के साथ संकलन से पाश के लिए कोडांतरक स्निपेट यहाँ है, टिप्पणियों के साथ मैन्युअल रूप से जोड़ा गया:

.L3:
    movl    -52(%rbp), %eax           ; load i to register eax
    cltq
    movl    $0, -48(%rbp,%rax,4)      ; set array[i] to 0
    movl    $.LC0, %edi
    call    puts                      ; printf of a constant string was optimized to puts
    addl    $1, -52(%rbp)             ; add 1 to i
.L2:
    cmpl    $10, -52(%rbp)            ; compare i to 10
    jle     .L3

यहाँ वैरिएबल के शीर्ष के नीचे चर i 52 बाइट्स है, जबकि व्यूह ढेर के शीर्ष से 48 बाइट्स शुरू होता है। इसलिए यह संकलक i सरणी से ठीक पहले रखा गया है; यदि आप array[-1] पर लिखने के लिए हुआ है, तो आप i अधिलेखित कर देंगे। यदि आप array[i]=0 से array[9-i]=0 बदलते हैं, तो आपको इन विशेष संकलक विकल्पों के साथ इस विशेष मंच पर एक अनंत लूप मिलेगा।

अब gcc -O1 साथ अपने प्रोग्राम को संकलित करते हैं।

    movl    $11, %ebx
.L3:
    movl    $.LC0, %edi
    call    puts
    subl    $1, %ebx
    jne     .L3

वह छोटा है! संकलक ने केवल i लिए एक स्टैक स्थान आवंटित करने से मना नहीं किया है - यह केवल रजिस्टर ebx में संग्रहीत है - लेकिन यह array लिए किसी भी मेमोरी को आवंटित करने, या इसके तत्वों को सेट करने के लिए कोड उत्पन्न करने के लिए परेशान नहीं है, क्योंकि इसने देखा कि कोई नहीं तत्वों का कभी उपयोग किया जाता है।

इस उदाहरण को और अधिक कहने के लिए, आइए यह सुनिश्चित करें कि संकलक को कुछ ऐसा प्रदान करने के लिए जो इसे दूर करने में सक्षम नहीं है, प्रदान करके सरणी असाइन किया गया है। ऐसा करने का एक आसान तरीका है कि किसी अन्य फ़ाइल से सरणी का उपयोग करना - अलग संकलन के कारण, कंपाइलर को यह पता नहीं होता है कि दूसरी फ़ाइल में क्या होता है (जब तक कि यह लिंक समय पर अनुकूलन नहीं करता है, जो gcc -O0 या gcc -O1 नहीं करता है )। एक स्रोत फ़ाइल use_array.c युक्त बनाएँ

void use_array(int *array) {}

और अपने स्रोत कोड को बदल दें

#include <stdio.h>
void use_array(int *array);

int main()
{
  int array[10],i;

  for (i = 0; i <=10 ; i++)
  {
    array[i]=0; /*code should never terminate*/
    printf("test \n");

  }
  printf("%zd \n", sizeof(array)/sizeof(int));
  use_array(array);
  return 0;
}

संकलन

gcc -c use_array.c
gcc -O1 -S -o with_use_array1.c with_use_array.c use_array.o

इस बार कोड कोड इस तरह दिखता है:

    movq    %rsp, %rbx
    leaq    44(%rsp), %rbp
.L3:
    movl    $0, (%rbx)
    movl    $.LC0, %edi
    call    puts
    addq    $4, %rbx
    cmpq    %rbp, %rbx
    jne     .L3

अब सरणी स्टैक पर है, ऊपर से 44 बाइट्स। मुझे क्या? यह कहीं भी दिखाई नहीं देता है! लेकिन लूप काउंटर को रजिस्टर rbx में रखा जाता है। यह वास्तव में i नहीं है, लेकिन array[i] का पता array[i] । संकलक ने फैसला किया है कि चूंकि i के मूल्य का सीधे उपयोग नहीं किया गया था, इसलिए लूप के प्रत्येक रन के दौरान 0 को स्टोर करने के लिए गणना करने के लिए अंकगणितीय प्रदर्शन करने का कोई मतलब नहीं था। इसके बजाय वह पता लूप वेरिएबल है, और सीमाओं को निर्धारित करने के लिए अंकगणितीय संकलन समय पर आंशिक रूप से किया गया था (44 पुनरावृत्ति के लिए 4 बाइट्स प्रति सरणी तत्व से 11 पुनरावृत्तियों को गुणा करें) और आंशिक रूप से रन टाइम पर लेकिन एक बार और सभी के लिए लूप शुरू होने से पहले ( प्रारंभिक मूल्य प्राप्त करने के लिए एक घटाव प्रदर्शन)।

यहां तक ​​कि इस बहुत ही सरल उदाहरण पर, हमने देखा है कि कंपाइलर विकल्प (अनुकूलन को चालू करें) या कुछ मामूली ( array[i] को array[9-i] ) में बदल रहा है या यहां तक ​​कि स्पष्ट रूप से असंबंधित कुछ बदल रहा है (कॉल का उपयोग करने के लिए use_array ) कंपाइलर द्वारा निष्पादित निष्पादन योग्य कार्यक्रम क्या करता है, इसके बारे में एक महत्वपूर्ण अंतर हो सकता है। कंपाइलर ऑप्टिमाइज़ेशन बहुत सारी चीजें कर सकते हैं जो अपरिभाषित व्यवहार को लागू करने वाले कार्यक्रमों पर एकतरफा दिखाई दे सकते हैं । इसीलिए अपरिभाषित व्यवहार को पूरी तरह से अपरिभाषित छोड़ दिया जाता है। जब आप वास्तविक दुनिया के कार्यक्रमों में कभी-कभी पटरियों से थोड़ा सा विचलन करते हैं, तो कोड के बारे में और अनुभवी प्रोग्रामर के लिए भी क्या करना चाहिए, इस संबंध को समझना बहुत कठिन हो सकता है।







buffer-overflow