.NET सी # कंपाइलर (.NET 4.0) fixed
कथन को एक असाधारण तरीके से संकलित करता है।एमएसएफटी सी # एक फिक्स्ड "पॉइंटर क्षय के सरणी" और "पहले तत्व का पता" अलग-अलग क्यों संकलित करता है?
यहां एक छोटा लेकिन पूरा कार्यक्रम है जो आपको दिखाने के लिए है कि मैं किस बारे में बात कर रहा हूं।
using System;
public static class FixedExample {
public static void Main() {
byte [] nonempty = new byte[1] {42};
byte [] empty = new byte[0];
Good(nonempty);
Bad(nonempty);
try {
Good(empty);
} catch (Exception e){
Console.WriteLine(e.ToString());
/* continue with next example */
}
Console.WriteLine();
try {
Bad(empty);
} catch (Exception e){
Console.WriteLine(e.ToString());
/* continue with next example */
}
}
public static void Good(byte[] buffer) {
unsafe {
fixed (byte * p = &buffer[0]) {
Console.WriteLine(*p);
}
}
}
public static void Bad(byte[] buffer) {
unsafe {
fixed (byte * p = buffer) {
Console.WriteLine(*p);
}
}
}
}
के साथ संकलित "Csc.exe FixedExample.cs/असुरक्षित/ओ +" यदि आप के साथ पालन करना चाहते हैं।
अच्छा()
.maxstack 2
.locals init (uint8& pinned V_0)
IL_0000: ldarg.0
IL_0001: ldc.i4.0
IL_0002: ldelema [mscorlib]System.Byte
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: conv.i
IL_000a: ldind.u1
IL_000b: call void [mscorlib]System.Console::WriteLine(int32)
IL_0010: ldc.i4.0
IL_0011: conv.u
IL_0012: stloc.0
IL_0013: ret
यहाँ विधि Bad
के लिए उत्पन्न आईएल है::
बुरा()
यहाँ विधि Good
के लिए उत्पन्न आईएल है
.locals init (uint8& pinned V_0, uint8[] V_1)
IL_0000: ldarg.0
IL_0001: dup
IL_0002: stloc.1
IL_0003: brfalse.s IL_000a
IL_0005: ldloc.1
IL_0006: ldlen
IL_0007: conv.i4
IL_0008: brtrue.s IL_000f
IL_000a: ldc.i4.0
IL_000b: conv.u
IL_000c: stloc.0
IL_000d: br.s IL_0017
IL_000f: ldloc.1
IL_0010: ldc.i4.0
IL_0011: ldelema [mscorlib]System.Byte
IL_0016: stloc.0
IL_0017: ldloc.0
IL_0018: conv.i
IL_0019: ldind.u1
IL_001a: call void [mscorlib]System.Console::WriteLine(int32)
IL_001f: ldc.i4.0
IL_0020: conv.u
IL_0021: stloc.0
IL_0022: ret
यहाँ Good
करता है:
- बफर के पते प्राप्त करें [0]।
- उस पते को अस्वीकार करें।
- उस संदर्भित मान के साथ लिखें WriteLine।
यहाँ 'Bad` करता है:
- तो बफर रिक्त है, गोटो 3.
- तो buffer.Length = 0, गोटो 5.
- स्टोर मान 0 स्थानीय में! स्लॉट 0,
- गोटो 6.
- बफर का पता प्राप्त करें [0]।
- उस पते को अस्वीकार करें (स्थानीय स्लॉट 0 में, जो अब 0 या बफर हो सकता है)।
- उस संदर्भित मान के साथ लिखें WriteLine।
जब buffer
दोनों गैर-शून्य और खाली नहीं हैं, तो ये दो कार्य एक ही काम करते हैं। ध्यान दें कि Bad
WriteLine
फ़ंक्शन कॉल पर जाने से पहले बस कुछ हुप्स के माध्यम से कूदता है।
जब buffer
रिक्त है, Good
निश्चित सूचक declarator (byte * p = &buffer[0]
) में एक NullReferenceException
फेंकता है। संभवतः यह एक प्रबंधित सरणी को ठीक करने के लिए वांछित व्यवहार है, क्योंकि सामान्य रूप से के अंदर कोई भी ऑपरेशन फिक्स्ड-स्टेटमेंट ऑब्जेक्ट की वैधता पर निर्भर करेगा। अन्यथा यह कोड fixed
ब्लॉक के अंदर क्यों होगा? जब Good
एक शून्य संदर्भ पारित किया गया है, तो यह fixed
ब्लॉक की शुरुआत में तुरंत विफल रहता है, जो एक प्रासंगिक और सूचनात्मक स्टैक ट्रेस प्रदान करता है। डेवलपर इसे देखेगा और महसूस करेगा कि उसे इसका उपयोग करने से पहले buffer
को सत्यापित करना चाहिए, या शायद उसके तर्क को null
से buffer
पर गलत तरीके से असाइन किया जाना चाहिए।किसी भी तरह से, fixed
ब्लॉक को null
प्रबंधित सरणी के साथ स्पष्ट रूप से दर्ज करना वांछनीय नहीं है।
Bad
इस मामले को अलग-अलग, यहां तक कि अवांछनीय रूप से संभालता है। आप देख सकते हैं कि Bad
वास्तव में p
को अस्वीकार नहीं किया गया है जब तक अपवाद फेंक नहीं है। यह के चौराहे के रास्ते में को उसी स्थानीय स्लॉट में p
रखता है, फिर बाद में अपवाद फेंकता है जब fixed
ब्लॉक स्टेटमेंट अव्यवस्था p
।
null
हैंडलिंग इस तरह ऑब्जेक्ट मॉडल को सी # संगत में रखने का लाभ है। यही है, fixed
ब्लॉक के अंदर, p
अभी भी "एक प्रबंधित सरणी के सूचक" के रूप में अर्थात् इलाज किया जाता है, जो शून्य नहीं होने पर समस्याएं उत्पन्न करता है (या जब तक) इसे अस्वीकार नहीं किया जाता है। संगति सभी अच्छी और अच्छी है, लेकिन समस्या यह है कि पी एक प्रबंधित सरणी पर सूचक नहीं है। यह buffer
के पहले तत्व के लिए एक सूचक है, और जिसने इस कोड को लिखा है (Bad
) इस अर्थपूर्ण अर्थ की व्याख्या करेगा। आपको से buffer
का आकार नहीं मिल सकता है, और आप p.ToString()
पर कॉल नहीं कर सकते हैं, तो इसका इलाज क्यों करें क्योंकि यह एक वस्तु थी? ऐसे मामलों में जहां buffer
शून्य है, स्पष्ट रूप से कोडिंग गलती है, और मेरा मानना है कि Bad
विधि के अंदर फिक्स्ड-पॉइंटर घोषणाकर्ता पर अपवाद फेंक देगा, तो यह बहुत उपयोगी होगा।
तो ऐसा लगता है कि Good
से बेहतर null
संभालता है। खाली बफर के बारे में क्या?
buffer
लंबाई 0 होता है तो Good
निश्चित सूचक declarator पर IndexOutOfRangeException
फेंकता है। यह बाउंड सरणी पहुंच से निपटने के लिए एक पूरी तरह से उचित तरीका लगता है। आखिरकार, कोड &buffer[0]
को &(buffer[0])
जैसा ही माना जाना चाहिए, जो स्पष्ट रूप से IndexOutOfRangeException
फेंकना चाहिए।
Bad
इस मामले को अलग-अलग संभालता है, और फिर अनचाहे रूप से। जैसे ही buffer
null
थे, buffer.Length == 0
, Bad
p
को अपवाद नहीं फेंक दिया गया है, और उस समय यह NullReferenceException फेंकता है, इंडेक्सऑटऑफेंजेंज अपवाद नहीं! यदि p
कभी अस्वीकृत नहीं किया गया है, तो कोड एक अपवाद भी नहीं फेंकता है। दोबारा, ऐसा लगता है कि यहां विचार p
"एक प्रबंधित सरणी के सूचक" का अर्थपूर्ण अर्थ देना है। फिर भी, मुझे नहीं लगता कि इस कोड को लिखने वाला कोई भी व्यक्ति p
इस तरह से सोचता है। फिक्स्ड-पॉइंटर घोषणाकर्ता में IndexOutOfRangeException
फेंकने पर कोड अधिक उपयोगी होगा, इस प्रकार डेवलपर को सूचित किया गया है कि सरणी पास की गई सरणी खाली थी, और null
नहीं।
ऐसा लगता है कि fixed(byte * p = buffer)
को उसी कोड पर संकलित किया जाना चाहिए जैसा fixed (byte * p = &buffer[0])
था। यह भी ध्यान दें कि buffer
किसी भी मनमाने ढंग से अभिव्यक्ति हो सकता था, यह प्रकार (byte[]
) संकलन समय पर जाना जाता है और इसलिए Good
में कोड किसी भी मनमाने ढंग से अभिव्यक्ति के लिए काम करेगा।
संपादित
वास्तव में, देखा कि Bad
के कार्यान्वयन वास्तव में त्रुटि buffer[0]
दो बार पर जाँच करता है। यह स्पष्ट रूप से विधि की शुरुआत में करता है, और फिर यह ldelema
निर्देश पर फिर से निहित करता है।
तो हम कि Good
और Bad
शब्दार्थ अलग हैं देखते हैं। Bad
लंबा, शायद धीमा है, और निश्चित रूप से हमें वांछनीय अपवाद नहीं देता है जब हमारे कोड में बग होती है, और कुछ मामलों में इसके बाद भी बहुत कुछ विफल हो जाती है।
उत्सुक लोगों के लिए, कल्पना (सी # 4.0) की धारा 18.6 का कहना है कि व्यवहार "कार्यान्वयन से परिभाषित" इन विफलता दोनों मामलों में है:
एक निश्चित सूचक-प्रारंभकर्ता से एक हो सकता निम्नलिखित:
• टोकन "&" एक परिवर्तनीय-प्रकार (§5.3.3) के बाद एक अप्रबंधित प्रकार टी के एक परिवर्तनीय चर (§18.3) के बाद, टी प्रकार * सूचक रूप से सूचक रूप से परिवर्तनीय है निश्चित कथन में दिया गया प्रकार। इस मामले में, प्रारंभकर्ता निर्दिष्ट चर के पते की गणना करता है, और चर को निश्चित कथन की अवधि के लिए एक निश्चित पते पर रहने की गारंटी दी जाती है।
• एक अप्रबंधित प्रकार टी के तत्वों के साथ एक सरणी प्रकार की अभिव्यक्ति, बशर्ते प्रकार टी * निश्चित कथन में दिए गए सूचक प्रकार के लिए पूरी तरह परिवर्तनीय है। इस मामले में, प्रारंभकर्ता सरणी में पहले तत्व के पते की गणना करता है, और पूरे सरणी को निश्चित कथन की अवधि के लिए एक निश्चित पते पर रहने की गारंटी है। निश्चित कथन का व्यवहार कार्यान्वयन-परिभाषित है यदि सरणी अभिव्यक्ति शून्य है या यदि सरणी में शून्य तत्व हैं।
... अन्य मामलों ...
अंतिम बिंदु, MSDN documentation पता चलता है कि दो "बराबर" कर रहे हैं:
// निम्न दो कार्य बराबर हैं ...
तय (डबल * पी = आगमन) {/ ... /}
तय (डबल * पी = & आगमन [0]) {/ ... /}
दो "बराबर" होना चाहिए रहे हैं, तो क्यों पूर्व बयान के लिए अलग त्रुटि से निपटने के अर्थ विज्ञान का उपयोग ?
यह भी प्रतीत होता है कि अतिरिक्त प्रयास को Bad
में उत्पन्न कोड पथ लिखने के लिए रखा गया था। Good
में संकलित कोड सभी विफलता मामलों के लिए ठीक काम करता है, और गैर-विफलता मामलों में Bad
में कोड के समान है। Good
के लिए जेनरेट किए गए सरल कोड का उपयोग करने के बजाय नए कोड पथ क्यों लागू करें?
इस तरह इसे क्यों लागू किया गया है?
@pst मैंने 'SoWhat' को हटा दिया और इसके स्पष्टीकरण के लिए संक्षिप्तता। फिक्स्ड। –
क्या आपने जांच की है कि सी # spec 'arr [0] 'के बारे में क्या कहता है? मुझे पूरा यकीन है कि 'arr [0]' अपवाद फेंकता है जब किसी भी संदर्भ में 'arr' शून्य या खाली होता है, चाहे उसके आस-पास के किसी भी 'निश्चित' कथन के बावजूद। – hvd
@ एचवीडी हां यही कारण है कि 'निश्चित (बाइट * पी = बफर)' को केवल 'निश्चित (बाइट * पी = और बफर [0]) के रूप में माना जाना चाहिए। बाद में अधिक स्पष्ट रूप से जांचने में त्रुटि का ख्याल रखता है। मैं इसे प्रश्न में जोड़ दूंगा। –