क्या "हमेशा वैरिएबल को इनिशियलाइज़ करना" महत्वपूर्ण बग छिपाना नहीं है?


35

C ++ कोर दिशानिर्देशों में नियम ES.20 है: हमेशा किसी ऑब्जेक्ट को इनिशियलाइज़ करें

प्रयुक्त-पहले-सेट त्रुटियों और उनके संबंधित अपरिभाषित व्यवहार से बचें। जटिल आरंभीकरण की समझ के साथ समस्याओं से बचें। रीफैक्टरिंग को सरल बनाएं।

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

हम कीड़े कैसे स्क्रीन करते हैं? हम परीक्षण लिखते हैं। लेकिन परीक्षण निष्पादन पथ के 100% को कवर नहीं करते हैं, और परीक्षण कभी भी प्रोग्राम इनपुट के 100% को कवर नहीं करते हैं। इससे भी अधिक, यहां तक ​​कि एक परीक्षण एक दोषपूर्ण निष्पादन पथ को कवर करता है - यह अभी भी गुजर सकता है। यह अपरिभाषित व्यवहार है सब के बाद, एक uninitialized चर कुछ मान मान्य हो सकता है।

लेकिन हमारे परीक्षणों के अलावा, हमारे पास कंपाइलर हैं जो 0xCDCDCDCD जैसे कुछ को अनइंस्टाल्यूटेड वैरिएबल में लिख सकते हैं। यह परीक्षणों की पहचान दर में थोड़ा सुधार करता है।
इससे भी बेहतर - एड्रेस सेनिटाइज़र जैसे उपकरण हैं, जो असमान मेमोरी बाइट्स के सभी रीड को पकड़ लेंगे।

और अंत में स्थिर विश्लेषक हैं, जो कार्यक्रम को देख सकते हैं और बता सकते हैं कि उस निष्पादन पथ पर एक रीड-पहले-सेट है।

इसलिए हमारे पास कई शक्तिशाली उपकरण हैं, लेकिन अगर हम वैरिएबल को इनिशियलाइज़ करते हैं - सैनिटाइज़र को कुछ नहीं मिलता है

int bytes_read = 0;
my_read(buffer, &bytes_read); // err_t my_read(buffer_t, int*);
// bytes_read is not changed on read error.
// It's a bug of "my_read", but detection is suppressed by initialization.
buffer.shrink(bytes_read); // Uninitialized bytes_read could be detected here.

// Another bug: use empty buffer after read error.
use(buffer);

एक और नियम है - यदि प्रोग्राम निष्पादन बग का सामना करता है, तो प्रोग्राम को जल्द से जल्द मर जाना चाहिए। इसे जिंदा रखने की जरूरत नहीं है, बस दुर्घटनाग्रस्त हो जाए, क्रैश क्रैश लिख दें, इसे जांच के लिए इंजीनियरों को दें।
आरंभिक चर को अनावश्यक रूप से विपरीत करता है - कार्यक्रम को जीवित रखा जा रहा है, जब यह पहले से ही एक विभाजन दोष प्राप्त करेगा।


10
हालांकि मुझे लगता है कि यह एक अच्छा सवाल है, मैं आपके उदाहरण को नहीं समझता। यदि कोई पठन त्रुटि होती है, और bytes_readपरिवर्तित नहीं होती है (इसलिए शून्य रखी जाती है), तो इसे बग क्यों माना जाता है? यह कार्यक्रम तब भी जारी रह सकता है जब तक कि यह स्पष्ट रूप से अपेक्षित नहीं है bytes_read!=0। तो यह ठीक है शिकायतकर्ताओं शिकायत नहीं है। दूसरी ओर, जब bytes_readपहले से प्रारंभ नहीं किया गया है, कार्यक्रम नहीं एक समझदार तरीके से जारी रखने के लिए सक्षम हो जाएगा, तो आरंभ नहीं bytes_readवास्तव में परिचय एक बग जो वहाँ पहले से नहीं था।
डॉक ब्राउन

2
@ ऐक्सएक्स: भले ही यह एक तीसरी पार्टी है, अगर यह एक बफर के साथ सौदा नहीं करता है तो यह \0छोटी गाड़ी है। यदि यह उस से निपटने के लिए नहीं प्रलेखित है, तो आपका कॉलिंग कोड छोटी गाड़ी है। यदि आप कॉल करने bytes_read==0से पहले जांचने के लिए अपना कॉलिंग कोड ठीक कर लेते हैं, तो आप वापस उसी स्थान पर पहुंच गए हैं जहां से आपने शुरू किया था: यदि आप शुरू नहीं करते हैं तो आपका कोड छोटी गाड़ी bytes_readहै। ( आमतौर पर फ़ंक्शंस किसी त्रुटि के मामले में भी उनके आउट-मापदंडों को भरने के लिए किए जाते हैं : वास्तव में नहीं। बहुत बार आउटपुट या तो अकेले या अपरिभाषित रह जाते हैं।)
Mat

1
क्या कोई कारण है जिससे यह कोड err_tलौटाया गया है my_read()? यदि उदाहरण में कहीं भी बग है, तो यह है।
Blrfl

1
यह आसान है: यदि यह सार्थक है तो केवल वैरिएबल को इनिशियलाइज़ करें। अगर यह नहीं है तो नहीं है। मैं सहमत हो सकता हूं कि ऐसा करने के लिए "डमी" डेटा का उपयोग करना बुरा है, क्योंकि यह बग को छुपाता है।
पीटर बी

1
"एक और नियम है - यदि प्रोग्राम निष्पादन बग का सामना करता है, तो प्रोग्राम को जल्द से जल्द मर जाना चाहिए। इसे जीवित रखने की आवश्यकता नहीं है, बस दुर्घटनाग्रस्त हो जाए, क्रैश क्रैश लिखें, इसे जांच के लिए इंजीनियरों को दें।": कोशिश करें कि उड़ान पर नियंत्रण सॉफ्टवेयर। हवाई जहाज के मलबे से दुर्घटनाग्रस्त डंप को पुनर्प्राप्त करने का सौभाग्य।
जियोर्जियो

जवाबों:


44

आपका तर्क कई खातों पर गलत है:

  1. विभाजन दोष कुछ होने से दूर हैं। एक अपरिष्कृत चर का उपयोग अपरिभाषित व्यवहार में करता है । सेगमेंटेशन दोष एक तरीका है कि इस तरह का व्यवहार खुद को प्रकट कर सकता है, लेकिन सामान्य रूप से चलने के लिए प्रकट होने की संभावना है।
  2. संकलक कभी भी एक परिभाषित पैटर्न (जैसे 0xCD) के साथ असंबद्ध मेमोरी को नहीं भरते हैं। यह कुछ ऐसा है जो कुछ डिबगर्स उन जगहों को खोजने में आपकी सहायता करने के लिए करते हैं, जहां अनइंस्टॉल किए गए चर का उपयोग किया जाता है। यदि आप एक डिबगर के बाहर ऐसा प्रोग्राम चलाते हैं, तो चर में पूरी तरह से यादृच्छिक कचरा शामिल होगा। यह समान रूप से संभावना है कि एक काउंटर जैसा bytes_readमूल्य है 10जैसा कि उसका मूल्य है 0xcdcdcdcd
  3. यहां तक ​​कि अगर आप डिबगर में चल रहे हैं जो एक तय पैटर्न में अनइंस्टॉल की गई मेमोरी सेट करते हैं, तो वे केवल स्टार्टअप पर ऐसा करते हैं। इसका मतलब है कि यह तंत्र केवल स्थैतिक (और संभवतः ढेर-आवंटित) चर के लिए मज़बूती से काम करता है। स्वचालित चर के लिए, जो स्टैक पर आवंटित किए जाते हैं या केवल एक रजिस्टर में रहते हैं, संभावना अधिक है कि चर उस स्थान पर संग्रहीत किया जाता है जो पहले इस्तेमाल किया गया था, इसलिए टेल-स्टोरी मेमोरी पैटर्न पहले से ही ओवरराइट हो गया है।

हमेशा चर को आरंभ करने के मार्गदर्शन के पीछे का विचार इन दो स्थितियों को सक्षम करना है

  1. चर में अपने अस्तित्व की शुरुआत से ही एक उपयोगी मूल्य होता है। यदि आप यह समझते हैं कि केवल एक बार आपको एक चर घोषित करने के लिए मार्गदर्शन के साथ, आप भविष्य के रखरखाव प्रोग्रामर को इसकी घोषणा और पहले असाइनमेंट के बीच एक चर का उपयोग करने के लिए शुरू होने के जाल में गिरने से बच सकते हैं, जहां चर मौजूद होगा, लेकिन यह अनैतिक है।

  2. चर में एक परिभाषित मूल्य होता है जिसे आप बाद में परीक्षण कर सकते हैं, यह बताने के लिए कि क्या किसी फ़ंक्शन my_readने मान को अपडेट किया है। आरंभीकरण के बिना, आप नहीं बता सकते कि क्या bytes_readवास्तव में एक वैध मूल्य है, क्योंकि आप नहीं जान सकते कि यह किस मूल्य के साथ शुरू हुआ था।


8
1) यह सभी संभावनाओं के बारे में है, जैसे 1% बनाम 99%। 2 और 3) वीसी ++ इस तरह के इनिशियलाइज़ेशन कोड को स्थानीय चर के लिए भी तैयार करता है। 3) स्थैतिक (वैश्विक) चर हमेशा
0.22 के

5
@ एबिक्स: 1) मेरे अनुभव में, संभावना ~ 80% है "कोई तुरंत स्पष्ट व्यवहार अंतर नहीं", 10% "गलत काम करता है", 10% "सेगफॉल्ट"। (2) और (3) के रूप में: वीसी ++ यह केवल डिबग बिल्ड में करता है। उस पर भरोसा करना बहुत बुरा विचार है क्योंकि यह चुनिंदा रिलीज रिलीज को तोड़ता है और आपके बहुत सारे परीक्षण में दिखाई नहीं देता है।
क्रिश्चियन आइचिंगर

8
मुझे लगता है कि "मार्गदर्शन के पीछे विचार" इस ​​उत्तर का सबसे महत्वपूर्ण हिस्सा है। मार्गदर्शन बिल्कुल नहीं बता रहा है कि आप के साथ हर चर घोषणा का पालन करें = 0;। सलाह का आशय उस बिंदु पर चर घोषित करना है जहां आपके पास इसके लिए एक उपयोगी मूल्य होगा, और तुरंत इस मान को असाइन करें। यह ES21 और ES22 के बाद के नियमों में स्पष्ट रूप से स्पष्ट किया गया है। उन तीनों को एक साथ काम करने के रूप में समझा जाना चाहिए; व्यक्तिगत असंबंधित नियमों के रूप में नहीं।
ग्रैंडऑनर

1
@GrandOpener बिल्कुल यदि उस बिंदु पर असाइन करने के लिए कोई सार्थक मूल्य नहीं है जहां चर घोषित किया जाता है, तो चर का दायरा शायद गलत है।
केविन क्रुमविडे

5
"कम्पाइलर कभी नहीं भरते" क्या ऐसा हमेशा नहीं होना चाहिए ?
कोडइन्चोस

25

आपने लिखा "यह नियम बग्स को खोजने में मदद नहीं करता है, यह केवल उन्हें छुपाता है" - ठीक है, नियम का लक्ष्य बगों को खोजने में मदद करना नहीं है, बल्कि उनसे बचना है। और जब एक बग से बचा जाता है, तो कुछ भी छिपा नहीं है।

अपने उदाहरण के संदर्भ में इस समस्या का समाधान करें: मान लें कि my_readफ़ंक्शन के पास bytes_readसभी परिस्थितियों में आरंभ करने के लिए लिखित अनुबंध है, लेकिन यह त्रुटि के मामले में नहीं है, इसलिए यह दोषपूर्ण है, कम से कम, इस मामले के लिए। आपका इरादा bytes_readपहले पैरामीटर को शुरू न करके उस बग को दिखाने के लिए रन टाइम वातावरण का उपयोग करना है । जब तक आप जानते हैं कि जगह में एक सैनिटाइजर मौजूद है, तब तक इस तरह के बग का पता लगाने का एक संभावित तरीका है। बग को ठीक करने के लिए, my_readआंतरिक रूप से फ़ंक्शन को बदलना होगा ।

लेकिन एक अलग दृष्टिकोण है, जो कम से कम समान रूप से मान्य है: दोषपूर्ण व्यवहार केवल पहले से शुरू नहीं होने के संयोजन से निकलता है bytes_read, औरmy_read बाद में कॉल करना (उम्मीद के साथ उसके बाद bytes_readशुरू होता है)। यह एक ऐसी स्थिति है जो वास्तविक दुनिया के घटकों में अक्सर होती है जब किसी फ़ंक्शन की लिखित कल्पना my_read100% स्पष्ट नहीं होती है, या त्रुटि के मामले में व्यवहार के बारे में भी गलत है। हालाँकि, जब तक bytes_readकॉल से पहले शून्य को आरंभीकृत किया जाता है, प्रोग्राम उसी तरह व्यवहार करता है जैसे कि इनिशियलाइज़ेशन अंदर किया गया था my_read, इसलिए यह सही ढंग से व्यवहार करता है, इस संयोजन में प्रोग्राम में कोई बग नहीं है।

तो मेरी सिफारिश है कि इस प्रकार है: गैर-प्रारंभिक दृष्टिकोण का उपयोग केवल अगर

  • यदि कोई फ़ंक्शन या कोड ब्लॉक किसी विशिष्ट पैरामीटर को प्रारंभ करता है तो आप परीक्षण करना चाहते हैं
  • आप 100% सुनिश्चित हैं कि फंक्शन में फंक्शन में एक कॉन्ट्रैक्ट होता है, जहाँ यह निश्चित रूप से गलत है कि उस पैरामीटर के मान को असाइन नहीं किया जाना चाहिए
  • आप 100% सुनिश्चित हैं कि पर्यावरण इसे पकड़ सकता है

ये ऐसी स्थितियां हैं जो आप आमतौर पर परीक्षण कोड में व्यवस्थित कर सकते हैं एक विशिष्ट टूलींग वातावरण के लिए हैं।

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


4
यह ठीक वैसा ही है जैसा मैं पढ़ते समय सोच रहा था। यह गलीचा के नीचे चीजों को नहीं झाड़ रहा है, यह उन्हें कूड़ेदान में फेंक रहा है!
corsiKa

22

हमेशा अपने चर को इनिशियलाइज़ करें

जिन स्थितियों पर आप विचार कर रहे हैं उनके बीच का अंतर यह है कि आरम्भिक स्थिति के बिना मामला अपरिभाषित व्यवहार का परिणाम देता है , जबकि जिस मामले में आपको आरंभ करने में समय लगता है वह एक अच्छी तरह से परिभाषित और नियतात्मक बनाता है। बग । मैं जोर नहीं दे सकता कि ये दोनों मामले कितने अलग हैं।

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

ठीक है, मैं शब्द इंजेक्शन के साथ बंद कर दूंगा। मुझे लगता है आपको बात समझ में आ गयी है ;-)

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

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

पूरी टीम को रोक दिया गया हो सकता है और 2 महीने का बेहतर हिस्सा बिताया हो और पूरे सिमुलेशन कोडबेस को लागू करने और सुविधाओं को लागू करने के बजाय अनइंस्टॉल किए गए मूल्य त्रुटियों को ठीक करते हुए। कहने की जरूरत नहीं है, कर्मचारी ने "मैंने आपको ऐसा कहा था" को छोड़ दिया और अन्य डेवलपर्स को समझने में मदद करने में सीधे चला गया कि असमान मूल्य क्या हैं। इस घटना के तुरंत बाद, अजीब तरह से कोडिंग मानकों को बदल दिया गया, जिससे डेवलपर्स को हमेशा अपने चरों को शुरू करने के लिए प्रोत्साहित किया गया।

और यह चेतावनी शॉट है। यह वह गोली है जो आपकी नाक के पार होती है। वास्तविक मुद्दा तो दूर की बात आप कल्पना से भी कहीं अधिक कपटी हैं।

एक असंवैधानिक मूल्य का उपयोग करना "अपरिभाषित व्यवहार" है (जैसे कुछ कोने के मामलों को छोड़कर char)। अपरिभाषित व्यवहार (या संक्षेप में यूबी) आपके लिए इतना पागलपनपूर्ण और पूरी तरह से बुरा है, कि आपको कभी भी यह नहीं मानना ​​चाहिए कि यह विकल्प से बेहतर है। कभी-कभी आप यह पहचान सकते हैं कि आपका विशेष संकलक यूबी को परिभाषित करता है, और फिर इसका उपयोग करने के लिए सुरक्षित है, लेकिन अन्यथा, अपरिभाषित व्यवहार "किसी भी व्यवहार को कंपाइलर जैसा महसूस करता है।" यह ऐसा कुछ कर सकता है जिसे आप "समझदार" कहेंगे जैसे कि अनिर्दिष्ट मूल्य हो। यह अमान्य ऑपकोड का उत्सर्जन कर सकता है, संभवतः आपके कार्यक्रम को स्वयं भ्रष्ट कर सकता है। यह संकलन समय पर चेतावनी को ट्रिगर कर सकता है, या संकलक इसे एक त्रुटि भी समझ सकता है ।

या यह कुछ भी नहीं कर सकता है

UB के लिए कोयले की खदान में मेरा कैनरी SQL इंजन का एक मामला है जिसके बारे में मैंने पढ़ा है। मुझे इसे लिंक न करने के लिए क्षमा करें, मैं फिर से लेख खोजने में विफल रहा हूं। SQL इंजन में एक बफ़र ओवररन समस्या थी जब आपने किसी फ़ंक्शन को बड़ा बफ़र आकार दिया, लेकिन केवल डेबियन के किसी विशेष संस्करण पर। बग को सावधानी से लॉग इन किया गया, और पता लगाया गया। मज़ेदार हिस्सा था: बफर ओवररन की जाँच की गई थी । जगह-जगह बफर ओवररन को संभालने के लिए कोड था। यह कुछ इस तरह देखा गया:

// move the pointers properly to copy data into a ring buffer.
char* putIntoRingBuffer(char* begin, char* end, char* get, char*put, char* newData, unsigned int dataLength)
{
    // If dataLength is very large, we might overflow the pointer
    // arithmetic, and end up with some very small pointer number,
    // causing us to fail to realize we were trying to write past the
    // end.  Check this before we continue
    if (put + dataLength < put)
    {
        RaiseError("Buffer overflow risk detected");
        return 0;
    }
    ...
    // typical ring-buffer pointer manipulation followed...
}

मैंने अपने प्रतिपादन में और टिप्पणियाँ जोड़ी हैं, लेकिन विचार समान है। यदि put + dataLengthचारों ओर लपेटता है, तो यह putसूचक की तुलना में छोटा होगा (उनके पास यह सुनिश्चित करने के लिए संकलित समय की जांच थी कि जिज्ञासु के लिए अहस्ताक्षरित int एक सूचक का आकार था)। यदि ऐसा होता है, तो हमें पता है कि मानक रिंग बफ़र एल्गोरिदम इस अतिप्रवाह से भ्रमित हो सकते हैं, इसलिए हम 0. या हम लौटते हैं ?

जैसा कि यह पता चला है, बिंदुओं पर अतिप्रवाह C ++ में अपरिभाषित है। क्योंकि अधिकांश कंपाइलर पॉइंटर्स को पूर्णांक के रूप में मान रहे हैं, हम विशिष्ट पूर्णांक अतिप्रवाह व्यवहारों के साथ समाप्त होते हैं, जो कि हम चाहते हैं कि व्यवहार हो। बहरहाल, यह है अपरिभाषित व्यवहार अर्थ संकलक करने के लिए अनुमति दी है, कुछ भी यह चाहता है।

इस बग के मामले में, डेबियन हुआ जीसीसी का एक नया संस्करण है कि अन्य प्रमुख लिनक्स जायके से कोई भी उनके उत्पादन विज्ञप्ति में करने के लिए अद्यतन किया था उपयोग करने के लिए चुनने के लिए। जीसीसी के इस नए संस्करण में एक अधिक आक्रामक मृत-कोड अनुकूलक था। संकलक ने अपरिभाषित व्यवहार को देखा, और निर्णय लिया कि ifकथन का परिणाम "जो भी कोड अनुकूलन को सर्वोत्तम बनाता है," वह होगा, जो यूबी का बिल्कुल कानूनी अनुवाद था। तदनुसार, यह धारणा बनी कि चूंकि यूबी पॉइंटर ओवरफ्लो के बिना ptr+dataLengthनीचे कभी नहीं हो सकता है ptr, ifबयान कभी भी ट्रिगर नहीं होगा, और बफर ओवररन चेक को अनुकूलित करेगा

"Sane" यूबी का उपयोग वास्तव में एक प्रमुख एसक्यूएल उत्पाद का कारण बनता है जिसमें बफर ओवररन का शोषण होता है जिससे बचने के लिए उसने कोड लिखा था!

अपरिभाषित व्यवहार पर कभी भी भरोसा न करें। कभी।


अनिर्धारित व्यवहार पर एक बहुत ही मनोरंजक पढ़ने के लिए, software.intel.com/en-us/blogs/2013/01/06/… एक आश्चर्यजनक अच्छी तरह से लिखा गया पोस्ट है कि यह कितना बुरा हो सकता है। हालाँकि, वह विशेष पोस्ट परमाणु परिचालनों पर है, जो अधिकांश के लिए बहुत ही भ्रामक हैं, इसलिए मैं इसे यूबी के लिए प्राइमर के रूप में सिफारिश करने से बचता हूं और यह कैसे गलत हो सकता है।
कॉर्ट अमोन

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

@supercat यह एक अच्छी सुविधा होगी, यह मानते हुए कि आप उन प्लेटफार्मों को लक्षित कर रहे हैं जहां यह एक वैध समाधान है। ज्ञात मुद्दों के उदाहरणों में से एक मेमोरी पैटर्न बनाने की क्षमता है जो न केवल मेमोरी प्रकार के लिए अमान्य हैं, बल्कि सामान्य साधनों के माध्यम से प्राप्त करना असंभव है। boolएक उत्कृष्ट उदाहरण है जहां स्पष्ट समस्याएं हैं, लेकिन वे कहीं और दिखाते हैं जब तक आप अनुमान नहीं लगाते हैं कि आप x86 या एआरएम या एमआइपी जैसे बहुत उपयोगी प्लेटफॉर्म पर काम कर रहे हैं, जहां इन सभी समस्याओं को ओपोड समय पर हल किया जाना है।
कॉर्ट अमोन

उस मामले पर विचार करें जहां एक ऑप्टिमाइज़र यह साबित कर सकता है कि switchपूर्णांक अंकगणित के आकार के कारण, एक के लिए उपयोग किया जाने वाला मान 8 से कम है, इसलिए वे तेजी से निर्देशों का उपयोग कर सकते हैं जो माना जाता है कि "बड़े" मूल्य का कोई जोखिम नहीं था। अनिर्दिष्ट मूल्य (जो संकलक के नियमों का उपयोग करके कभी भी निर्माण नहीं किया जा सकता है) प्रकट होता है, कुछ अप्रत्याशित करता है, और अचानक आपके पास एक कूद तालिका के अंत में बड़े पैमाने पर कूद होता है। यहां अनिर्दिष्ट परिणामों की अनुमति देने का मतलब है कि कार्यक्रम के हर स्विच स्टेटमेंट में इन मामलों का समर्थन करने के लिए अतिरिक्त जाल होना चाहिए जो "कभी भी घटित नहीं हो सकते।"
कॉर्ट अमोन

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

5

मैं ज्यादातर एक कार्यात्मक प्रोग्रामिंग भाषा में काम करता हूं जहां आपको चर को फिर से असाइन करने की अनुमति नहीं है। कभी। यह बगों के इस वर्ग को पूरी तरह से समाप्त कर देता है। यह पहली बार में एक विशाल प्रतिबंध की तरह लग रहा था, लेकिन यह आपको अपने कोड को इस तरह से संरचना करने के लिए मजबूर करता है जो आपके द्वारा नए डेटा को जानने के क्रम के अनुरूप होता है, जो आपके कोड को सरल बनाने और बनाए रखने में आसान बनाता है।

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

सी-स्टाइल एपीआई के साथ आपका उदाहरण थोड़ा और मुश्किल है। उन मामलों में, जब मैं कंपाइलर को शिकायत करने से रोकने के लिए शून्य का उपयोग करता हूं, लेकिन my_readयूनिट परीक्षणों में एक बार , मैं यह सुनिश्चित करने के लिए किसी और चीज की शुरुआत करूंगा कि त्रुटि स्थिति ठीक से काम करती है। आपको हर उपयोग पर हर संभव त्रुटि स्थिति का परीक्षण करने की आवश्यकता नहीं है।


5

नहीं, यह बग को छिपाता नहीं है। इसके बजाय यह व्यवहार को एक तरह से नियतात्मक बनाता है जैसे कि यदि कोई उपयोगकर्ता किसी त्रुटि का सामना करता है, तो एक डेवलपर इसे पुन: पेश कर सकता है।


1
और -1 के साथ आरंभ करना वास्तव में सार्थक हो सकता है। जहां "int bytes_read = 0" खराब है, क्योंकि आप वास्तव में 0 बाइट्स पढ़ सकते हैं, -1 के साथ इसे शुरू करने से यह स्पष्ट हो जाता है कि बाइट्स पढ़ने का कोई प्रयास सफल नहीं हुआ है, और आप इसके लिए परीक्षण कर सकते हैं।
पीटर बी

4

TL; DR: इस प्रोग्राम को सही बनाने के दो तरीके हैं, अपने वैरिएबल को इनिशियलाइज़ करना और प्रेयर करना। केवल एक ही परिणाम लगातार देता है।


इससे पहले कि मैं आपके प्रश्न का उत्तर दे सकूं , मुझे पहले यह बताना होगा कि अपरिभाषित व्यवहार का क्या अर्थ है। वास्तव में, मैं एक संकलक लेखक को इस काम के थोक करने देता हूँ:

यदि आप उन लेखों को पढ़ने के लिए तैयार नहीं हैं, तो एक TL; DR है:

अनिर्धारित व्यवहार डेवलपर और संकलक के बीच एक सामाजिक अनुबंध है; संकलक अंधे विश्वास के साथ मानता है कि उसका उपयोगकर्ता कभी भी, अपरिभाषित व्यवहार पर भरोसा नहीं करेगा।

"आपकी नाक से उड़ने वाले दानव" का आदर्श वाक्य दुर्भाग्य से, इस तथ्य के निहितार्थों को बताने में विफल रहा है। जबकि यह साबित करने के लिए कि कुछ भी हो सकता है, यह इतना अविश्वसनीय था कि इसे ज्यादातर बंद कर दिया गया था।

हालांकि, सच्चाई यह है कि अपरिभाषित व्यवहार संकलन को प्रभावित करता है, इससे पहले कि आप कार्यक्रम का उपयोग करने का प्रयास भी करें (एक वाद-विवाद के भीतर या नहीं) और इसके व्यवहार को पूरी तरह से बदल सकते हैं।

मुझे इसका उदाहरण हड़ताली से ऊपर के भाग 2 में मिलता है:

void contains_null_check(int *P) {
  int dead = *P;
  if (P == 0)
    return;
  *P = 4;
}

में बदल जाता है:

void contains_null_check(int *P) {
  *P = 4;
}

क्योंकि यह स्पष्ट है कि Pनहीं किया जा सकता 0क्योंकि यह जाँच किए जाने से पहले dereferenced है।


यह आपके उदाहरण पर कैसे लागू होता है?

int bytes_read = 0;
my_read(buffer, &bytes_read); // err_t my_read(buffer_t, int*);
// bytes_read is not changed on read error.
// It's a bug of "my_read", but detection is suppressed by initialization.
buffer.shrink(bytes_read); // Uninitialized bytes_read could be detected here.

ठीक है, आपने यह मानने की सामान्य गलती की है कि अपरिभाषित व्यवहार एक रन-टाइम त्रुटि का कारण होगा। यह नहीं हो सकता है।

आइए हम कल्पना करें कि परिभाषा इस प्रकार my_readहै:

err_t my_read(buffer_t buffer, int* bytes_read) {
    err_t result = {};
    int blocks_read = 0;
    if (!(result = low_level_read(buffer, &blocks_read))) { return result; }
    *bytes_read = blocks_read * BLOCK_SIZE;
    return result;
}

और इनलाइनिंग के साथ एक अच्छे संकलक की अपेक्षा के अनुसार आगे बढ़ें:

int bytes_read; // UNINITIALIZED

// start inlining my_read

err_t result = {};
int blocks_read = 0;
if (!(result = low_level_read(buffer, &blocks_read))) {
    // nothing
} else {
    bytes_read = blocks_reads * BLOCK_SIZE;
}

// end of inlining my_read

buffer.shrink(bytes_read);

फिर, एक अच्छे संकलक की अपेक्षा के अनुसार, हम बेकार शाखाओं का अनुकूलन करते हैं:

  1. किसी भी चर का इस्तेमाल अनैतिक तरीके से नहीं किया जाना चाहिए
  2. bytes_readयदि resultइसका उपयोग नहीं किया गया, तो इसका उपयोग अनैतिक तरीके से किया जाएगा0
  3. डेवलपर वादा कर रहा है कि resultकभी नहीं होगा 0!

तो resultकभी नहीं है 0:

int bytes_read; // UNINITIALIZED
err_t result = {};
int blocks_read = 0;
result = low_level_read(buffer, &blocks_read);

bytes_read = blocks_reads * BLOCK_SIZE;
buffer.shrink(bytes_read);

ओह, resultकभी उपयोग नहीं किया जाता है:

int bytes_read; // UNINITIALIZED
int blocks_read = 0;
low_level_read(buffer, &blocks_read);

bytes_read = blocks_reads * BLOCK_SIZE;
buffer.shrink(bytes_read);

ओह, हम की घोषणा को स्थगित कर सकते हैं bytes_read:

int blocks_read = 0;
low_level_read(buffer, &blocks_read);

int bytes_read = blocks_reads * BLOCK_SIZE;
buffer.shrink(bytes_read);

और यहाँ हम मूल के परिवर्तन की सख्ती से पुष्टि कर रहे हैं, और कोई भी डिबगर एक असंबद्ध चर को नहीं फँसाएगा क्योंकि कोई भी नहीं है।

मैंने उस सड़क को नीचे कर दिया है, जब अपेक्षित व्यवहार और असेंबली मेल नहीं खाते हैं तो इस मुद्दे को समझना वास्तव में कोई मज़ा नहीं है।


कभी-कभी मुझे लगता है कि यूबी पथ निष्पादित करने पर कंपाइलरों को स्रोत फ़ाइलों को हटाने का कार्यक्रम मिलना चाहिए। प्रोग्रामर तब सीखेंगे कि यूबी का उनके अंतिम उपयोगकर्ता के लिए क्या मतलब है ....
मैट्नज़

1

आइए अपने उदाहरण कोड पर करीब से नज़र डालें:

int bytes_read = 0;
my_read(buffer, &bytes_read); // err_t my_read(buffer_t, int*);
// bytes_read is not changed on read error.
// It's a bug of "my_read", but detection is suppressed by initialization.
buffer.shrink(bytes_read); // Uninitialized bytes_read could be detected here.

// Another bug: use empty buffer after read error.
use(buffer);

यह एक अच्छा उदाहरण है। यदि हम इस तरह से एक त्रुटि का अनुमान लगाते हैं, तो हम लाइन को सम्मिलित कर सकते हैं assert(bytes_read > 0);और इस बग को रनटाइम पर पकड़ सकते हैं , जो एक असिंचित चर के साथ संभव नहीं है।

लेकिन मान लें कि हम नहीं करते हैं, और हम फ़ंक्शन के अंदर एक त्रुटि पाते हैं use(buffer)। हम डिबगर में प्रोग्राम को लोड करते हैं, बैकट्रेस की जांच करते हैं, और पता लगाते हैं कि यह इस कोड से कहा गया था। तो हम इस स्निपेट के शीर्ष पर एक ब्रेकपॉइंट डालते हैं, फिर से चलाते हैं, और बग को पुन: पेश करते हैं। हम इसे पकड़ने की कोशिश कर रहे हैं।

यदि हमने आरंभ नहीं किया है bytes_read, तो इसमें कचरा है। यह जरूरी नहीं कि हर बार एक ही कचरा हो। हम लाइन से आगे बढ़ते हैं my_read(buffer, &bytes_read);। अब, यदि यह पहले की तुलना में एक अलग मूल्य है, तो हम अपने बग को पुन: पेश करने में सक्षम नहीं हो सकते हैं! यह अगली बार, उसी इनपुट पर, पूर्ण दुर्घटना द्वारा काम कर सकता है। यदि यह लगातार शून्य है, तो हमें लगातार व्यवहार मिलता है।

हम मूल्य की जांच करते हैं, शायद एक ही रन में एक बैकट्रेस पर भी। यदि यह शून्य है, तो हम देख सकते हैं कि कुछ गलत है; bytes_readसफलता पर शून्य नहीं होना चाहिए। (या अगर यह हो सकता है, तो हम इसे -1 में इनिशियलाइज़ करना चाहते हैं।) हम शायद यहाँ बग पकड़ सकते हैं। यदि bytes_readएक प्रशंसनीय मूल्य है, हालांकि, यह सिर्फ गलत होने के लिए होता है, तो क्या हम इसे एक नज़र में देखेंगे?

यह विशेष रूप से संकेत देने वालों का सच है: एक NULL पॉइंटर हमेशा डिबगर में स्पष्ट होगा, बहुत आसानी से परीक्षण किया जा सकता है, और आधुनिक हार्डवेयर पर सेगफॉल्ट करना चाहिए यदि हम इसे डीरेंशन करने का प्रयास करते हैं। एक कचरा पॉइंटर बाद में अप्रचलित स्मृति-भ्रष्टाचार बग पैदा कर सकता है, और ये डीबग करना लगभग असंभव हैं।


1

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

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

ओपी से असहमत एकमात्र जगह मैं बहुत अंत में हूं, जहां वह कहता है "जब यह पहले से ही एक विभाजन दोष प्राप्त करेगा।" वास्तव में, एक uninitialized चर मज़बूती से एक विभाजन दोष उत्पन्न नहीं करेगा। इसके बजाय, मैं कहूंगा कि आपको स्थैतिक विश्लेषण उपकरणों का उपयोग करना चाहिए जो आपको कार्यक्रम को निष्पादित करने के प्रयास तक भी नहीं जाने देंगे।


0

आपके प्रश्न के उत्तर को विभिन्न प्रकार के चरों में तोड़ दिया जाना चाहिए जो किसी प्रोग्राम के अंदर दिखाई देते हैं:


स्थानीय चर

आमतौर पर घोषणा उस स्थान पर सही होनी चाहिए जहां चर पहले अपना मूल्य प्राप्त करता है। पुरानी शैली C की तरह चर को पहले से न समझें:

//Bad: predeclared variables
int foo = 0;
double bar = 0.0;
long* baz = NULL;

bar = getBar();
foo = (int)bar;
baz = malloc(foo);


//Correct: declaration and initialization at the same place
double bar = getBar();
int foo = (int)bar;
long* baz = malloc(foo);

यह आरंभीकरण की आवश्यकता के 99% को हटा देता है, चर का अपना अंतिम मूल्य सही है। कुछ अपवाद ऐसे हैं जहाँ आरंभीकरण कुछ शर्त पर निर्भर करता है:

Base* ptr;
if(foo()) {
    ptr = new Derived1();
} else {
    ptr = new Derived2();
}

मेरा मानना ​​है कि इन मामलों को इस तरह लिखना एक अच्छा विचार है:

Base* ptr = nullptr;
if(foo()) {
    ptr = new Derived1();
} else {
    ptr = new Derived2();
}
assert(ptr);

अर्थात। स्पष्ट रूप से जोर देते हैं कि आपके चर का कुछ समझदार आरंभ किया जाता है।


सदस्य चर

यहाँ मैं सहमत हूँ कि अन्य उत्तरदाताओं ने क्या कहा: इन्हें हमेशा निर्माणकर्ताओं / आरंभीक सूचियों द्वारा आरंभ किया जाना चाहिए। अन्यथा आप अपने सदस्यों के बीच निरंतरता सुनिश्चित करने के लिए कठोर हैं। और अगर आपके पास उन सदस्यों का एक सेट है जो सभी मामलों में आरंभीकरण की आवश्यकता नहीं समझते हैं, तो अपनी कक्षा को फिर से कायम करें, उन सदस्यों को एक व्युत्पन्न वर्ग में जोड़ दें जहां उन्हें हमेशा ज़रूरत होती है।


बफ़र

यह वह जगह है जहां मैं अन्य उत्तरों से असहमत हूं। जब लोग चर को शुरू करने के बारे में धार्मिक जाते हैं, तो वे अक्सर इस तरह के बफ़र्स को शुरू करते हैं:

char buffer[30];
memset(buffer, 0, sizeof(buffer));

char* buffer2 = calloc(30);

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

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


सार्वत्रिक चर

आपके पास ऐसे वैश्विक चर नहीं हो सकते हैं जो आरंभिक नहीं हैं (कम से कम C / C ++ आदि में), इसलिए सुनिश्चित करें कि यह आरंभ वही है जो आप चाहते हैं।


ध्यान रखें कि आप टर्नरी ऑपरेटर के साथ सशर्त इनिशियलाइज़ेशन लिख सकते हैं, जैसे Base& b = foo() ? new Derived1 : new Derived2;
डेविस्लोरल डेक्स

@ लॉरेहेड जो साधारण मामलों के लिए काम कर सकता है, लेकिन यह अधिक जटिल लोगों के लिए काम नहीं करेगा: आप ऐसा नहीं करना चाहते हैं यदि आपके पास तीन या अधिक मामले हैं, और आपके निर्माता केवल पठनीयता के लिए तीन या अधिक तर्क लेते हैं, कारणों। और यह भी किसी भी गणना पर विचार नहीं किया जा सकता है जिसे करने की आवश्यकता हो सकती है, जैसे कि लूप में आरंभीकरण की एक शाखा के लिए तर्क की खोज करना।
सेमीस्टर

अधिक जटिल मामलों के लिए, आप फैक्टरी फ़ंक्शन में आरंभीकरण कोड लपेट सकते हैं Base &b = base_factory(which);:। यह सबसे उपयोगी है यदि आपको कोड को एक से अधिक बार कॉल करने की आवश्यकता है या यदि यह आपको परिणाम को स्थिर बनाता है।
डेविस्लर

@ लॉरहेड सच है, और निश्चित रूप से जाने का तरीका यदि आवश्यक तर्क सरल नहीं है। फिर भी, मुझे विश्वास है कि बीच में एक छोटा ग्रे क्षेत्र है जहां आरंभीकरण ?:एक पीआईटीए है, और एक कारखाना फ़ंक्शन अभी भी ओवरकिल है। ये मामले कम और दूर के हैं, लेकिन वे मौजूद हैं।
सेमीस्टर

-2

सही संकलक विकल्प सेट के साथ एक सभ्य सी, सी ++ या ऑब्जेक्टिव-सी संकलक आपको संकलन समय पर बताएगा कि क्या कोई चर इसके मूल्य निर्धारित होने से पहले उपयोग किया जाता है। चूंकि इन भाषाओं में एक असिंचित चर के मान का उपयोग करना अपरिभाषित व्यवहार है, "इससे पहले कि आप उपयोग के लिए एक मूल्य निर्धारित करें" संकेत नहीं है, या एक दिशानिर्देश, या अच्छा अभ्यास नहीं है, यह एक 100% आवश्यकता है; अन्यथा आपका कार्यक्रम बिल्कुल टूट गया है। अन्य भाषाओं में, जावा और स्विफ्ट की तरह, कंपाइलर आपको एक वैरिएबल का उपयोग करने से पहले इसे शुरू करने की अनुमति नहीं देगा।

"आरंभीकरण" और "मूल्य निर्धारित करें" के बीच एक तार्किक अंतर है। अगर मैं डॉलर और यूरो के बीच रूपांतरण दर खोजना चाहता हूं, और "डबल रेट = 0.0;" तब चर का एक मान सेट होता है, लेकिन यह आरंभिक नहीं होता है। यहां संग्रहीत 0.0 का सही परिणाम के साथ कुछ भी नहीं करना है। इस स्थिति में, यदि बग के कारण आप कभी सही रूपांतरण दर जमा नहीं करते हैं, तो संकलक के पास आपको बताने का मौका नहीं होता है। यदि आपने अभी लिखा है "दोहरी दर;" और कभी भी एक सार्थक रूपांतरण दर संग्रहीत नहीं की गई, संकलनकर्ता आपको बताएगा।

इसलिए: केवल एक चर को इनिशियलाइज़ न करें क्योंकि कंपाइलर आपको बताता है कि इसे इनिशियलाइज़ किए बिना उपयोग किया जाता है। यह एक बग को छिपा रहा है। असली समस्या यह है कि आप एक चर का उपयोग कर रहे हैं जिसे आप उपयोग नहीं कर रहे हैं, या यह कि एक कोड पथ पर आपने कोई मान सेट नहीं किया है। समस्या को ठीक करें, इसे छिपाएं नहीं।

केवल एक चर को इनिशियलाइज़ न करें क्योंकि कंपाइलर आपको बता सकता है कि इसे इनिशियलाइज़ किए बिना उपयोग किया जाता है। फिर, आप समस्याओं को छिपा रहे हैं।

उपयोग के करीब वैरिएबल की घोषणा करें। यह उन अवसरों में सुधार करता है जिन्हें आप घोषणा के बिंदु पर एक सार्थक मूल्य के साथ शुरू कर सकते हैं।

वैरिएबल को फिर से उपयोग करने से बचें। जब आप एक चर का पुन: उपयोग करते हैं, तो जब आप इसे दूसरे उद्देश्य के लिए उपयोग करते हैं, तो यह सबसे बेकार मूल्य के लिए आरंभीकृत होता है।

यह टिप्पणी की गई है कि कुछ कंपाइलरों में गलत निगेटिव होते हैं, और यह कि प्रारंभिक जांच के लिए रुकने की समस्या के बराबर है। दोनों अभ्यास में अप्रासंगिक हैं। यदि एक संकलक, जैसा कि उद्धृत किया गया है, बग के रिपोर्ट किए जाने के दस साल बाद एक असंबद्ध चर का उपयोग नहीं कर सकता है, तो यह एक वैकल्पिक संकलक की तलाश करने का समय है। जावा इसे दो बार लागू करता है; संकलक में एक बार, सत्यापनकर्ता में एक बार, बिना किसी समस्या के। हॉल्टिंग समस्या के आस-पास पहुंचने का आसान तरीका यह नहीं है कि प्रयोग करने से पहले एक वैरिएबल को इनिशियलाइज़ किया जाए, बल्कि यह कि इसे एक तरह से इनिशियलाइज़ करने से पहले इनिशियलाइज़ किया जाए, जिसे एक सिंपल और तेज़ एल्गोरिथम द्वारा चेक किया जा सकता है।


यह सतही रूप से अच्छा लगता है, लेकिन अनइंस्टाल्यूटेड-वैल्यू चेतावनियों की सटीकता पर बहुत अधिक निर्भर करता है। इनको पूरी तरह से सही करना हॉल्टिंग समस्या के बराबर है, और उत्पादन संकलक झूठी नकारात्मक का सामना कर सकते हैं (अर्थात जब वे एक असमान परिवर्तन का निदान नहीं करते हैं तो उन्हें होना चाहिए); उदाहरण के लिए जीसीसी बग 18501 देखें , जो अब दस वर्षों से अधिक समय तक अप्रकाशित रहा है।
zwol

जीसीसी के बारे में आप क्या कहते हैं बाकी अप्रासंगिक है।
gnasher729

यह gcc के बारे में दुखद है, लेकिन अगर आपको समझ में नहीं आता कि बाकी प्रासंगिक क्यों है तो आपको खुद को शिक्षित करने की आवश्यकता है।
zwol
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.