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




debugging undefined-behavior (12)

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

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

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

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

.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

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

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

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

#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;
}

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

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

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

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


बग कोड के इन टुकड़ों के बीच है:

int array[10],i;

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

array[i]=0;

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

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

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


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

int array[10],i;

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

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


आपके पास बाध्य उल्लंघन है, और गैर-समाप्ति प्लेटफ़ॉर्म पर, मेरा मानना ​​है कि आप अनजाने में लूप के अंत में शून्य पर सेट कर रहे हैं, ताकि यह फिर से शुरू हो जाए।

array[10] अमान्य है; इसमें 10 तत्व, array[0] array[9] माध्यम से array[9] , और array[10] 11 वीं है। आपके लूप को 10 से पहले रोकने के लिए लिखा जाना चाहिए, निम्नानुसार है:

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

जहां array[10] भूमि कार्यान्वयन-परिभाषित है, और आश्चर्यजनक रूप से, आपके दो प्लेटफ़ॉर्म पर, यह i , जो कि प्लेटफ़ॉर्म स्पष्ट रूप से array बाद सीधे बाहर निकलते हैं। i शून्य पर सेट है और लूप हमेशा के लिए जारी है। आपके अन्य प्लेटफार्मों के लिए, i array से पहले स्थित हो सकता i , या array बाद कुछ पैडिंग हो सकती है।


जब आप पिछले i==9 i9 को पुन: सक्रिय करते हैं तो आप 'सरणी आइटम' को शून्य निर्दिष्ट करते हैं जो वास्तव में सरणी के पीछे स्थित होते हैं, इसलिए आप कुछ अन्य डेटा को ओवरराइटिग कर रहे हैं। शायद आप i वैरिएबल को ओवरराइट करते हैं, जो a[] बाद स्थित है। इस तरह आप बस i variable को शून्य पर रीसेट करते हैं और इस प्रकार लूप को पुनरारंभ करते हैं।

यदि आप लूप में मुद्रित करते हैं तो आप स्वयं को खोज सकते हैं:

      printf("test i=%d\n", i);

बस के बजाय

      printf("test \n");

बेशक यह परिणाम आपके चर के लिए स्मृति आवंटन पर दृढ़ता से निर्भर करता है, जो बदले में एक कंपाइलर और इसकी सेटिंग्स पर निर्भर करता है, इसलिए यह आम तौर पर अनिर्धारित व्यवहार होता है - यही कारण है कि विभिन्न मशीनों या विभिन्न ऑपरेटिंग सिस्टम या विभिन्न कंपाइलरों पर परिणाम अलग-अलग हो सकते हैं।


आप 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++)

मैं कुछ ऐसा सुझाव दूंगा जो मैंने ऊपर पाया है:

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

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

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


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

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

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

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

इन कोडिंग कथन को अपने कोड में जोड़ने का प्रयास करें। आपको ध्यान देना चाहिए कि पुनरावृत्ति 10 या 11 पर, array[i] 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; 
} 

यह array[10] पर अपरिभाषित है, और पहले वर्णित अपरिभाषित व्यवहार देता है। इसके बारे में कुछ इस तरह सोचें:

मेरे किराने की गाड़ी में मेरे पास 10 आइटम हैं। वो हैं:

0: अनाज का एक बॉक्स
1: रोटी
2: दूध
3: पाई
चार अंडे
5: केक
6: 2 लीटर सोडा
7: सलाद
8: बर्गर
9: आइस क्रीम

cart[10] अपरिभाषित है, और कुछ कंपाइलरों में सीमा अपवाद से बाहर हो सकता है। लेकिन, बहुत स्पष्ट रूप से नहीं करते हैं। स्पष्ट 11 वें आइटम वास्तव में कार्ट में नहीं है 11 वां आइटम इंगित कर रहा है, जिसे मैं कॉल करने जा रहा हूं, एक "poltergeist आइटम।" यह कभी अस्तित्व में नहीं था, लेकिन यह वहां था।

कुछ कंपेलर i array[10] या array[11] या यहां तक ​​कि array[-1] का एक सूचकांक देता है जो आपके आरंभिक / घोषणा विवरण के कारण होता है। कुछ कंपाइलर्स इस प्रकार की व्याख्या करते हैं:

  • " array[10] लिए int 10 ब्लॉक आवंटित करें array[10] और एक और int ब्लॉक। इसे आसान बनाने के लिए, उन्हें एक दूसरे के बगल में रखें।"
  • पहले की तरह ही, लेकिन इसे एक स्थान या दो दूर ले जाएं, ताकि array[10] i इंगित न करे।
  • पहले जैसा ही करें, लेकिन array[-1] पर आवंटित करें array[-1] (क्योंकि सरणी का एक सूचकांक नकारात्मक नहीं हो सकता है, या नहीं होना चाहिए), या इसे पूरी तरह से अलग स्थान पर आवंटित करें क्योंकि ओएस इसे संभाल सकता है, और यह सुरक्षित है।

कुछ कंपाइलर चीजों को तेज़ी से जाना चाहते हैं, और कुछ कंपाइलर्स सुरक्षा पसंद करते हैं। यह सब संदर्भ के बारे में है। अगर मैं प्राचीन ब्रेव ओएस (मूल फोन का ओएस) के लिए एक ऐप विकसित कर रहा था, उदाहरण के लिए, यह सुरक्षा की परवाह नहीं करेगा। अगर मैं एक आईफोन 6 के लिए विकास कर रहा था, तो इससे कोई फर्क नहीं पड़ता कि इससे कोई फर्क नहीं पड़ता, इसलिए मुझे सुरक्षा पर जोर देना होगा। (गंभीरता से, क्या आपने ऐप्पल के ऐप स्टोर दिशानिर्देश पढ़े हैं, या स्विफ्ट और स्विफ्ट 2.0 के विकास पर पढ़ा है?)


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


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

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

int array[10]

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

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

एक्सेस array[10] यहां, स्थिति को i < 10 बदलें


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

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







buffer-overflow