क्या एक सूचक को 0-आकार के गतिशील सरणी में अपरिभाषित किया गया है?


34

AFAIK, हालांकि हम 0-स्थैतिक-स्मृति सरणी नहीं बना सकते हैं, लेकिन हम इसे गतिशील लोगों के साथ कर सकते हैं:

int a[0]{}; // Compile-time error
int* p = new int[0]; // Is well-defined

जैसा कि मैंने पढ़ा है, pएक-पास्ट-एंड तत्व की तरह काम करता है। मैं उस पते को प्रिंट कर सकता हूं जो pइंगित करता है।

if(p)
    cout << p << endl;
  • यद्यपि मुझे यकीन है कि हम उस सूचक (भूतकाल-अंतिम-तत्व) को नहीं टाल सकते हैं क्योंकि हम पुनरावृत्तियों (भूत-अंतिम तत्व) के साथ नहीं कर सकते हैं, लेकिन जो मुझे यकीन नहीं है कि क्या उस सूचक को बढ़ाना है p? क्या पुनरावृत्तियों के साथ अपरिभाषित व्यवहार (UB) है?

    p++; // UB?

4
यूबी "... किसी भी अन्य स्थितियों (जो एक सूचक को उत्पन्न करने का प्रयास करता है जो एक ही सरणी के एक तत्व या अंत के एक बिंदु पर इंगित नहीं करता है) अपरिभाषित व्यवहार को आमंत्रित करता है ...." से: en.cppreference.com / डब्ल्यू / सीपीपी / भाषा /
संचालक_अर्हता

3
खैर, यह std::vectorइसमें 0 आइटम के साथ समान है। begin()यह पहले से ही बराबर है end()इसलिए आप एक पुनरावृत्ति वृद्धि नहीं कर सकते हैं जो शुरुआत में इंगित कर रहा है।
फिल 1970

1
@PeterMortensen मुझे लगता है कि आपके संपादन ने अंतिम वाक्य का अर्थ बदल दिया है ("मुझे क्या यकीन है -> मुझे यकीन नहीं है कि क्यों"), क्या आप दोहरी जांच कर सकते हैं?
फैबियो का कहना है कि

@PeterMortensen: आपके द्वारा संपादित किया गया अंतिम पैराग्राफ थोड़ा कम पठनीय बन गया है।
इटाची उचिवा

जवाबों:


32

सरणियों के तत्वों के संकेत एक वैध तत्व को इंगित करने की अनुमति दी जाती है, या अंत में एक अतीत। यदि आप एक पॉइंटर को इस तरह से बढ़ाते हैं जो अंत में एक से अधिक अतीत में जाता है, तो व्यवहार अपरिभाषित है।

आपके 0-आकार के सरणी के लिए, pपहले से ही एक अतीत को इंगित कर रहा है, इसलिए इसे बढ़ाने की अनुमति नहीं है।

+ऑपरेटर के बारे में C ++ 17 8.7 / 4 देखें ( ++उसी पर प्रतिबंध है):

च अभिव्यक्ति Pतत्व को अंक x[i]एक सरणी वस्तु के xn तत्वों, भाव के साथ P + Jऔर J + P(जहां Jमहत्व है j) बिंदु (संभवतः-काल्पनिक) तत्व के लिए x[i+j]करता है, तो 0≤i + j≤n; अन्यथा, व्यवहार अपरिभाषित है।


2
तो केवल मामले x[i]के रूप में ही है x[i + j]तब होता है जब दोनों iऔर jमान 0 है?
रामी येन

8
@RamiYen x[i]वही तत्व है जैसे x[i+j]कि j==0
२१

1
उह, मुझे C ++ शब्दार्थ ... +1 के "गोधूलि क्षेत्र" से नफरत है।
8::३37 में epoklum

4
@ einpoklum-reinstateMonica: वास्तव में कोई गोधूलि क्षेत्र नहीं है। यह सिर्फ C ++ N = 0 केस के लिए भी सुसंगत है। एन तत्वों की एक सरणी के लिए, एन + 1 वैध सूचक मान हैं क्योंकि आप सरणी के पीछे इंगित कर सकते हैं। इसका मतलब है कि आप सरणी की शुरुआत में शुरू कर सकते हैं और अंत तक पहुंचने के लिए पॉइंटर एन बार बढ़ा सकते हैं।
MSLERS

1
@MaximEgorushkin मेरा उत्तर उस भाषा के बारे में है जो वर्तमान में भाषा अनुमति देती है। इसके बारे में चर्चा करना चाहेंगे कि आप इसके बजाय अनुमति देना चाहते हैं।
२५ बजे इंटरजेड

2

मुझे लगता है कि आपके पास पहले से ही जवाब है; यदि आप थोड़ा गहराई से देखते हैं: आपने कहा है कि एक बंद चलने वाले itter को बढ़ाना UB है इस प्रकार: यह उत्तर क्या है जो एक पुनरावृत्त है?

इटिअटर सिर्फ एक ऑब्जेक्ट है जिसमें एक पॉइंटर है और इंक्रीमेंट है कि इट्रेटर वास्तव में पॉइंटर को बढ़ाता है। इस प्रकार कई पहलुओं में एक सूचक के संदर्भ में एक पुनरावृत्ति संभाला जाता है।

int arr [] = {0,1,2,3,4,5,6,7,8,9};

int * p = arrest; // p गिरफ्तारी में पहले तत्व को इंगित करता है

++ पी; // p अंक गिरफ्तार करने के लिए [1]

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

int * e = & arrest [10]; // पॉइंटर गिरफ्तारी के अंतिम तत्व से ठीक पहले

यहाँ हमने सबस्क्रिप्ट ऑपरेटर का उपयोग एक नोक्सिस्टिंग तत्व को इंडेक्स करने के लिए किया; गिरफ्तारी में दस तत्व होते हैं, इसलिए गिरफ्तारी में अंतिम तत्व सूचकांक स्थिति 9 पर है। इस तत्व के साथ केवल एक चीज जो हम कर सकते हैं वह है इसका पता, जिसे हम ई को इनिशियलाइज़ करने के लिए करते हैं। ऑफ-द-एंड इटेरेटर (, 3.4.1, पृष्ठ 106) की तरह, ऑफ-द-एंड पॉइंटर एक तत्व को इंगित नहीं करता है। परिणामस्वरूप, हम एक बंद-सूचक को अनुमेय या बढ़ा नहीं सकते हैं।

यह लिपमैन द्वारा सी ++ प्राइमर 5 संस्करण से है।

तो यह यूबी नहीं है।


-4

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

अंतरजाल द्वारा दिया गया मानक उद्धरण एक अच्छा है, जो यूबी को दर्शाता है, लेकिन यह मेरी राय में केवल दूसरी सबसे अच्छी हिट है, क्योंकि यह सूचक-सूचक अंकगणितीय (मजेदार रूप से, एक स्पष्ट रूप से यूबी है, जबकि दूसरा नहीं है)। सीधे सवाल में ऑपरेशन के साथ एक पैराग्राफ है:

[expr.post.incr] / [expr.pre.incr]
ऑपरेंड पूरी तरह से परिभाषित ऑब्जेक्ट प्रकार के लिए [...] या एक सूचक होगा।

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

[basic.compound] 3एक सूचक किस प्रकार का हो सकता है, इस बारे में एक बयान देता है, और अन्य तीन में से कोई भी नहीं होने के कारण, आपके ऑपरेशन का परिणाम स्पष्ट रूप से 3.4 के तहत गिर जाएगा: अमान्य सूचक
हालांकि यह नहीं कहता है कि आपको अमान्य सूचक रखने की अनुमति नहीं है। इसके विपरीत, यह कुछ बहुत ही सामान्य, सामान्य स्थितियों (जैसे भंडारण अवधि का अंत) को सूचीबद्ध करता है जहां संकेत नियमित रूप से अमान्य हो जाते हैं। तो यह स्पष्ट रूप से एक स्वीकार्य बात है। और सचमुच में:

[basic.stc] 4
एक अवैध पॉइंटर मान के माध्यम से अप्रत्यक्ष और एक अमान्य सूचक को एक डिक्लेरेशन फ़ंक्शन में पास करने से अपरिभाषित व्यवहार होता है। अमान्य पॉइंटर मान के किसी अन्य उपयोग में कार्यान्वयन-परिभाषित व्यवहार होता है।

हम वहां "कोई अन्य" कर रहे हैं, इसलिए यह अपरिभाषित व्यवहार नहीं है, लेकिन कार्यान्वयन-परिभाषित है, इस प्रकार आम तौर पर स्वीकार्य है (जब तक कि कार्यान्वयन स्पष्ट रूप से कुछ अलग नहीं कहता है)।

दुर्भाग्य से, यह कहानी का अंत नहीं है। हालांकि शुद्ध परिणाम यहाँ से किसी भी अधिक नहीं बदलता है, यह अधिक भ्रमित हो जाता है, अब आप "पॉइंटर" के लिए खोज करते हैं:

[basic.compound]
ऑब्जेक्ट पॉइंटर प्रकार का एक मान्य मान मेमोरी में बाइट या अशक्त पॉइंटर के पते को दर्शाता है । यदि टाइप ए की एक वस्तु एक पते पर स्थित है [...] तो उस वस्तु को इंगित करने के लिए कहा जाता है, भले ही मूल्य कैसे प्राप्त किया गया हो
[नोट: उदाहरण के लिए, एक सरणी के अंत में पिछले एक पते को उस सरणी के तत्व प्रकार के असंबंधित ऑब्जेक्ट को इंगित करने के लिए माना जाएगा जो उस पते पर स्थित हो सकता है। [...]]।

पढ़ें: ठीक है, कौन परवाह करता है! जब तक कोई पॉइंटर मेमोरी में कहीं इंगित करता है , मैं अच्छा हूं?

[basic.stc.dynamic.safety] एक पॉइंटर मान एक सुरक्षित रूप से व्युत्पन्न पॉइंटर [ब्ला ब्ला] है

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

एक कार्यान्वयन में आराम सूचक सुरक्षा हो सकती है, जिस स्थिति में सूचक मान की वैधता इस बात पर निर्भर नहीं करती है कि यह सुरक्षित रूप से व्युत्पन्न सूचक मान है या नहीं।

ओह, तो यह बात नहीं हो सकती है, बस मैंने जो सोचा था। लेकिन रुकिए ... "हो सकता है" नहीं? इसका मतलब है, यह भी हो सकता है । मुझे कैसे पता चलेगा?

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

रुको, तो यह भी संभव है कि मुझे declare_reachable()हर सूचक पर कॉल करने की आवश्यकता है ? मुझे कैसे पता चलेगा?

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

[expr.reinterpret.cast] 5
इंटीग्रल टाइप या एन्यूमरेशन टाइप का मान स्पष्ट रूप से पॉइंटर में बदला जा सकता है। एक पॉइंटर पर्याप्त आकार के पूर्णांक में परिवर्तित हो जाता है [...] और उसी पॉइंटर प्रकार पर वापस जाता है [...] मूल मूल्य; संकेत और पूर्णांक के बीच मैपिंग अन्यथा कार्यान्वयन-परिभाषित हैं।

कैच

पॉइंटर्स केवल साधारण पूर्णांक हैं, केवल आप उन्हें पॉइंटर्स के रूप में उपयोग करने के लिए होते हैं। ओह अगर केवल यह सच था!
दुर्भाग्य से, ऐसे आर्किटेक्चर मौजूद हैं जहां यह बिल्कुल सच नहीं है, और केवल एक अमान्य पॉइंटर उत्पन्न करना (इसे डीरफ्रेंसिंग नहीं करना है, बस इसे पॉइंटर रजिस्टर में रखना) एक जाल का कारण होगा।

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

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


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

5
आपके पास "ठीक है, कौन परवाह करता है?" के साथ समान तर्क दोष हैं! जब तक कि कोई सूचक स्मृति में कहीं इंगित करता है, तो मैं अच्छा हूं। " नहीं। आपको सभी नियमों का पालन करना होगा। "एक सरणी के अंत में दूसरे सरणी की शुरुआत" के बारे में कठिन भाषा बस स्मृति को आकस्मिक रूप से आवंटित करने के लिए कार्यान्वयन की अनुमति देती है ; यह आवंटन के बीच मुक्त स्थान रखने की जरूरत नहीं है। इसका मतलब है कि आपके कोड में समान मूल्य ए हो सकता है दोनों एक सरणी वस्तु के अंत और दूसरे की शुरुआत के रूप में हो सकते हैं।
13

1
"ट्रैप" कुछ ऐसा नहीं है जिसे "कार्यान्वयन परिभाषित" व्यवहार द्वारा वर्णित किया जा सकता है। ध्यान दें कि संजय ने +ऑपरेटर (जिसमें से ++प्रवाह होता है) पर प्रतिबंध पाया है, जिसका अर्थ है कि "एक-के-बाद-अंत" के बाद इंगित करना अपरिभाषित है।
मार्टिन बोनर

1
@PeterCordes: कृपया बेसिक .stc, पैराग्राफ 4 पढ़ें । यह कहता है "अप्रत्यक्ष [...] अपरिभाषित व्यवहार। अमान्य सूचक मान के किसी अन्य उपयोग में कार्यान्वयन-परिभाषित व्यवहार होता है" । मैं उस शब्द का दूसरे अर्थ के लिए उपयोग करके लोगों को भ्रमित नहीं कर रहा हूं। यह सटीक शब्द है। यह अपरिभाषित व्यवहार नहीं है।
डेमोन

2
यह मुश्किल से संभव है कि आपने पोस्ट-इन्क्रीमेंट के लिए एक खामी पाई हो, लेकिन पोस्ट-इन्क्रीमेंट क्या करता है, इस पर आप पूरा खंड नहीं उद्धृत करते हैं। मैं अभी उस पर गौर नहीं करने जा रहा हूं। सहमत है कि अगर वहाँ एक है, यह अनायास ही है। वैसे भी, जितना अच्छा होगा अगर आईएसओ सी ++ फ्लैट मेमोरी मॉडल के लिए अधिक चीजों को परिभाषित करता है, @MaximEgorushkin, मनमाना सामान की अनुमति नहीं देने के लिए अन्य कारण (जैसे पॉइंटर रैप-अराउंड) हैं। 64-बिट x86 में सूचक तुलना पर हस्ताक्षर किए जाने या अहस्ताक्षरित होने
पीटर कोर्ड्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.