लूप के अंतिम रन में क्या होना चाहिए, आप लिखते हैं array[10]
, लेकिन सरणी में केवल 10 तत्व हैं, 0 के माध्यम से 0 गिने। सी भाषा विनिर्देश का कहना है कि यह "अपरिभाषित व्यवहार" है। व्यवहार में इसका मतलब यह है कि आपका कार्यक्रम int
स्मृति के तुरंत बाद निहित स्मृति के टुकड़े को लिखने का प्रयास करेगा array
। तब क्या होता है, इस पर निर्भर करता है, वास्तव में, वहां झूठ बोलते हैं, और यह न केवल ऑपरेटिंग सिस्टम पर निर्भर करता है, बल्कि संकलक पर, कंपाइलर विकल्प (जैसे अनुकूलन सेटिंग्स) पर, प्रोसेसर आर्किटेक्चर पर, आसपास के कोड पर भी निर्भर करता है , आदि यह निष्पादन से लेकर निष्पादन तक भी भिन्न हो सकते हैं, जैसे पता स्थान यादृच्छिकरण के कारण (शायद यह खिलौना उदाहरण पर नहीं है, लेकिन वास्तविक जीवन में ऐसा होता है)। कुछ संभावनाओं में शामिल हैं:
- स्थान का उपयोग नहीं किया गया था। पाश सामान्य रूप से समाप्त हो जाता है।
- स्थान का उपयोग उस चीज के लिए किया गया था जिसका मान 0 हुआ। लूप सामान्य रूप से समाप्त हो गया।
- स्थान में फ़ंक्शन का पता पता था। लूप सामान्य रूप से समाप्त हो जाता है, लेकिन फिर प्रोग्राम क्रैश हो जाता है क्योंकि यह पता 0 पर कूदने की कोशिश करता है।
- स्थान चर है
i
। लूप कभी समाप्त नहीं होता है क्योंकि i
0 पर पुनरारंभ होता है।
- स्थान में कुछ अन्य चर हैं। लूप सामान्य रूप से समाप्त हो जाता है, लेकिन तब "दिलचस्प" चीजें होती हैं।
- स्थान एक अमान्य मेमोरी एड्रेस है, उदाहरण के लिए क्योंकि
array
वर्चुअल मेमोरी पेज के अंत में सही है और अगला पेज मैप नहीं किया गया है।
- आपके नाक से दानव उड़ जाते हैं । सौभाग्य से अधिकांश कंप्यूटरों में अपेक्षित हार्डवेयर की कमी होती है।
आपने विंडोज पर जो देखा, वह यह था कि संकलक ने चर i
को मेमोरी में सरणी के तुरंत बाद रखने का निर्णय लिया , इसलिए उसे array[10] = 0
असाइन करना समाप्त कर दिया i
। उबंटू और सेंटोस पर, कंपाइलर i
वहां नहीं लगा। लगभग सभी सी कार्यान्वयन स्मृति में, मेमोरी स्टैक पर , एक प्रमुख अपवाद के साथ समूह स्थानीय चर करते हैं: कुछ स्थानीय चर पूरी तरह से रजिस्टरों में रखे जा सकते हैं । भले ही चर स्टैक पर है, चर का क्रम संकलक द्वारा निर्धारित किया जाता है, और यह न केवल स्रोत फ़ाइल में आदेश पर निर्भर हो सकता है, बल्कि उनके प्रकारों पर भी (मेमोरी को संरेखित करने के लिए बर्बादी से बचने के लिए जो छेद छोड़ देगा) उनके नाम पर, संकलक के आंतरिक डेटा संरचना आदि में उपयोग किए गए कुछ हैश मूल्य पर।
यदि आप यह जानना चाहते हैं कि आपके कंपाइलर ने क्या करने का फैसला किया है, तो आप इसे कोडांतरक कोड दिखाने के लिए कह सकते हैं। ओह, और कोडांतरक को समझना सीखें (इसे लिखना आसान है)। जीसीसी (और कुछ अन्य संकलक, विशेष रूप से यूनिक्स दुनिया में) के साथ, -S
बाइनरी के बजाय कोडांतरक कोड का उत्पादन करने का विकल्प पास करते हैं । उदाहरण के लिए, यहां GCC के साथ संकलन विकल्प -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
सरणी से ठीक पहले रखा गया होता है ; i
यदि आप लिखने के लिए हुआ है तो आप ओवरराइट करेंगे array[-1]
। यदि आप बदलते 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 बाइट्स। किस बारे में i
? यह कहीं भी दिखाई नहीं देता है! लेकिन लूप काउंटर को रजिस्टर में रखा जाता है rbx
। यह ठीक नहीं है i
, लेकिन का पता है array[i]
। संकलक ने फैसला किया है कि चूंकि मूल्य का i
सीधे उपयोग नहीं किया गया था, इसलिए यह गणना करने के लिए अंकगणितीय प्रदर्शन करने का कोई मतलब नहीं था कि लूप के प्रत्येक रन के दौरान 0 को कहां संग्रहीत किया जाए। इसके बजाय वह पता लूप वेरिएबल है, और सीमाओं को निर्धारित करने के लिए अंकगणितीय संकलन समय पर आंशिक रूप से किया गया (11 पुनरावृत्तियों को 4 बाइट्स प्रति सरणी तत्व 44 प्राप्त करने के लिए) और आंशिक रूप से रन टाइम पर लेकिन एक बार और सभी के लिए लूप शुरू होने से पहले ( प्रारंभिक मूल्य प्राप्त करने के लिए एक घटाव प्रदर्शन)।
यहां तक कि इस बहुत ही सरल उदाहरण पर, हमने देखा है कि कंपाइलर ऑप्शंस (ऑप्टिमाइज़ेशन चालू करें) या कुछ माइनर (इन ) array[i]
को array[9-i]
बदलने या यहां तक कि जाहिरा तौर पर असंबद्ध (कॉल को जोड़ने use_array
) में कुछ भी बदलने से निष्पादन योग्य प्रोग्राम क्या हुआ, इससे काफी फर्क पड़ सकता है। संकलक द्वारा करता है। कंपाइलर ऑप्टिमाइजेशन बहुत सारी चीजें कर सकते हैं जो अपरिभाषित व्यवहार को लागू करने वाले कार्यक्रमों पर एकतरफा दिखाई दे सकते हैं । इसीलिए अपरिभाषित व्यवहार को पूरी तरह से अपरिभाषित छोड़ दिया जाता है। जब आप वास्तविक दुनिया के कार्यक्रमों में पटरियों से कभी थोड़ा विचलित होते हैं, तो कोड के बारे में और यह क्या किया जाना चाहिए, अनुभवी प्रोग्रामर के बीच के रिश्ते को समझना बहुत मुश्किल हो सकता है।