क्या सही पते के साथ एक सूचक है और C ++ 17 के बाद भी हमेशा एक वैध सूचक है?


84

( इस सवाल और जवाब के संदर्भ में ।)

C ++ 17 मानक से पहले, निम्नलिखित वाक्य को [basic.compound] / 3 में शामिल किया गया था :

यदि टाइप A का ऑब्जेक्ट पता A पर स्थित है, तो टाइप Cv T * का पॉइंटर जिसका मान A पता है, उस ऑब्जेक्ट को इंगित करने के लिए कहा जाता है, भले ही मान कैसे प्राप्त किया गया हो।

लेकिन C ++ 17 के बाद से, इस वाक्य को हटा दिया गया है

उदाहरण के लिए, मेरा मानना ​​है कि इस वाक्य ने इस उदाहरण कोड को परिभाषित किया है, और C ++ 17 के बाद से यह अपरिभाषित व्यवहार है:

 alignas(int) unsigned char buffer[2*sizeof(int)];
 auto p1=new(buffer) int{};
 auto p2=new(p1+1) int{};
 *(p1+1)=10;

C ++ 17 से पहले, p1+1पते को *p2सही प्रकार से रखता है , इसलिए *(p1+1)एक संकेतक है *p2। C ++ 17 p1+1में एक पॉइंटर पास्ट-द-एंड है , इसलिए यह ऑब्जेक्ट के लिए पॉइंटर नहीं है और मेरा मानना ​​है कि यह डीरेफेरेंसेबल नहीं है।

क्या यह मानक के इस संशोधन की व्याख्या है या अन्य नियम हैं जो उद्धृत वाक्य के विलोपन की भरपाई करते हैं?


नोट: वहाँ [basic.stc.dynamic.safety] में सूचक उद्गम और [util.dynamic.safety] के बारे में नए / अपडेट नियम हैं
एम एम

@MM केवल सख्त पॉइंटर सुरक्षा के साथ कार्यान्वयन पर निर्भर करता है, जो एक खाली सेट है (प्रायोगिक त्रुटि के भीतर)।
टीसी

4
उद्धृत कथन वास्तव में व्यवहार में कभी भी सत्य नहीं रहा है। दिया int a, b = 0;, *(&a + 1) = 1;अगर आप जाँच भी नहीं कर सकते &a + 1 == &b। यदि आप किसी ऑब्जेक्ट को एक वैध सूचक प्राप्त कर सकते हैं, तो केवल उसके पते का अनुमान लगा सकते हैं, फिर भी रजिस्टरों में स्थानीय चर को स्टोर करना समस्याग्रस्त हो जाता है।
टीसी

@TC 1) आपने अपना पता लेने के बाद किस कंपाइलर को रेग में एक वर्जन दिया है? 2) आप इसे मापने के बिना किसी पते का सही अनुमान कैसे लगा सकते हैं?
जिज्ञासु

@curiousguy वास्तव में यही कारण है कि किसी वस्तु को प्राप्त होने वाले पते के लिए अन्य साधनों (जैसे, अनुमान) के द्वारा प्राप्त की गई संख्या को केवल समस्याग्रस्त किया जाता है: यह उस वस्तु को नष्ट कर देता है लेकिन संकलक को इसकी जानकारी नहीं होती है। यदि आप, इसके विपरीत, वस्तु का पता लेते हैं, जैसा कि आप कहते हैं: संकलक को चेतावनी दी गई है और तदनुसार सिंक करता है।
पीटर -

जवाबों:


45

क्या यह मानक के इस संशोधन की व्याख्या है या अन्य नियम हैं जो इस उद्धृत वाक्य को हटाने की भरपाई करते हैं?

हां, यह व्याख्या सही है। अंत में एक सूचक पिछले सूचक को उस पते पर इंगित करने के लिए बस परिवर्तनीय नहीं है।

नया [basic.compound] / 3 कहता है:

पॉइंटर प्रकार का प्रत्येक मान निम्न में से एक है:
(3.1) किसी ऑब्जेक्ट या फ़ंक्शन के लिए एक पॉइंटर (पॉइंटर को ऑब्जेक्ट या फ़ंक्शन को इंगित करने के लिए कहा जाता है), या
(3.2) एक पॉइंटर एक वस्तु के अंत में अतीत होता है ([expr) .add]), या

वे परस्पर अनन्य हैं। p1+1एक सिरा अतीत का सूचक है, न कि किसी वस्तु का सूचक। नहीं करने के लिए पर एक आकार -1 सरणी के p1+1काल्पनिक x[1]की ओर इशारा p1करता है p2। वे दो वस्तुएं सूचक-परस्पर नहीं हैं।

हमारे पास गैर-मानक नोट भी हैं:

[नोट: किसी ऑब्जेक्ट के अंत में एक पॉइंटर अतीत ([expr.add]) को उस प्रकार के ऑब्जेक्ट के असंबंधित ऑब्जेक्ट को इंगित करने के लिए नहीं माना जाता है जो उस पते पर स्थित हो सकता है। [...]

जो इरादे को स्पष्ट करता है।


जैसा कि टीसी कई टिप्पणियों में बताता है ( विशेष रूप से यह एक ), यह वास्तव में समस्या का एक विशेष मामला है std::vectorजो लागू करने की कोशिश कर रहा है - जो कि [v.data(), v.data() + v.size())एक वैध सीमा होने की आवश्यकता है और फिर vectorभी एक सरणी ऑब्जेक्ट नहीं बनाता है, इसलिए केवल परिभाषित सूचक अंकगणित वेक्टर में किसी भी दिए गए ऑब्जेक्ट से अपने काल्पनिक एक-आकार के सरणी के अतीत-अंत तक जा रहा होगा। और अधिक संसाधन, CWG 2182 , इस std चर्चा , और इस विषय पर एक पेपर के दो संशोधन देखें: P0593R0 और P0593R1 (धारा 1.3 विशेष रूप से)।


3
यह उदाहरण मूल रूप से ज्ञात " vectorकार्यान्वयन की समस्या" का एक विशेष मामला है । +1।
TC

2
@ सामान्य मामला C ++ 03 के बाद से अस्तित्व में है। मूल कारण सूचक अंकगणितीय अपेक्षा के अनुरूप काम नहीं कर रहा है क्योंकि आपके पास सरणी वस्तु नहीं है।
TC

1
@ मुझे लगता है कि एकमात्र समस्या सूचक अंकगणित पर प्रतिबंध से आई है। क्या यह वाक्य विलोपन एक नई समस्या नहीं है? क्या पूर्व-C ++ 17 में भी कोड उदाहरण UB है?
ओलिव

1
@Oliv यदि पॉइंटर अंकगणित को ठीक किया जाता है, तो आपका p1+1अब एक पिछला-एंड पॉइंटर नहीं होगा और पिछले-एंड पॉइंटर्स के बारे में पूरी चर्चा मूट है। आपका विशेष दो-तत्व विशेष मामला यूबी प्री -17 नहीं हो सकता है, लेकिन यह भी बहुत दिलचस्प नहीं है।
TC

5
@TC क्या आप मुझे इस बात की ओर संकेत कर सकते हैं कि मैं इस "वेक्टर कार्यान्वयन समस्या" पर पढ़ सकता हूं?
सरगुई

8

आपके उदाहरण में, *(p1 + 1) = 10;यूबी होना चाहिए, क्योंकि यह आकार के सरणी के अंत में एक है । 1. लेकिन हम यहां एक बहुत ही विशेष मामले में हैं, क्योंकि सरणी को गतिशील रूप से एक बड़े चार्ट में बनाया गया था।

गतिशील वस्तु निर्माण का वर्णन 4.5 सी C ++ ऑब्जेक्ट मॉडल [intro.object] में किया गया है, C ++ मानक के n4659 ड्राफ्ट का is3:

3 यदि एक पूर्ण ऑब्जेक्ट बनाया गया है (8.3.4) स्टोरेज में एक अन्य ऑब्जेक्ट ई के प्रकार के साथ जुड़ा हुआ है "एन अहस्ताक्षरित चार का सरणी" या "एन एसटी की सरणी :: बाइट" (21.2.1) प्रकार, वह सरणी भंडारण प्रदान करता है यदि बनाई गई वस्तु के लिए:
(3.1) - ई का जीवनकाल शुरू हो गया है और समाप्त नहीं हुआ है, और
(3.2) - नई वस्तु के लिए भंडारण पूरी तरह से ई के भीतर फिट बैठता है, और
(3.3) - कोई भी छोटी वस्तु नहीं है जो इन्हें संतुष्ट करती है बाधाओं।

3.3 के बजाय अस्पष्ट लगता है, लेकिन नीचे दिए गए उदाहरण इरादे को और अधिक स्पष्ट करते हैं:

struct A { unsigned char a[32]; };
struct B { unsigned char b[16]; };
A a;
B *b = new (a.a + 8) B; // a.a provides storage for *b
int *p = new (b->b + 4) int; // b->b provides storage for *p
// a.a does not provide storage for *p (directly),
// but *p is nested within a (see below)

इसलिए उदाहरण में, bufferसरणी दोनों के लिए भंडारण प्रदान करता है*p1 और *p2

निम्नलिखित पैराग्राफ साबित करते हैं कि दोनों के लिए पूर्ण वस्तु है *p1और *p2है buffer:

4 एक वस्तु को किसी अन्य वस्तु b के भीतर घोंसला बनाया जाता है यदि:
(4.1) - a, b का उप-बिंदु है, या
(4.2) - b, a, या
(4.3) के लिए भंडारण प्रदान करता है - एक वस्तु c मौजूद है जहाँ a, c के भीतर nested है , और c, b के भीतर नेस्टेड है।

5 प्रत्येक ऑब्जेक्ट x के लिए, x के पूर्ण ऑब्जेक्ट नामक कुछ ऑब्जेक्ट है, जो निम्नानुसार है:
(5.1) - यदि x एक पूर्ण ऑब्जेक्ट है, तो x का पूर्ण ऑब्जेक्ट स्वयं है।
(५.२) - अन्यथा, x का पूरा ऑब्जेक्ट (यूनिक) ऑब्जेक्ट का पूरा ऑब्जेक्ट है जिसमें x है।

एक बार यह स्थापित हो जाने के बाद, C ++ 17 के लिए ड्राफ्ट n4659 का अन्य प्रासंगिक हिस्सा [basic.coumpound] mine3 (मेरा जोर दें):

3 ... पॉइंटर प्रकार का प्रत्येक मान निम्न में से एक है:
(3.1) - किसी ऑब्जेक्ट या फ़ंक्शन के लिए एक पॉइंटर (पॉइंटर को ऑब्जेक्ट या फ़ंक्शन को इंगित करने के लिए कहा जाता है), या
(3.2) - एक सूचक पिछले छोर पर एक ऑब्जेक्ट (8.7), या
(3.3) - उस प्रकार के लिए अशक्त सूचक मान (7.11), या
(3.4) - एक अमान्य सूचक मान।

एक सूचक प्रकार के या किसी वस्तु के अंत अतीत एक सूचक है कि मान स्मृति (4.4) में पहली बाइट के पते का प्रतिनिधित्व वस्तु के कब्जे में भंडारण के अंत के बाद स्मृति में या पहले बाइट वस्तु के कब्जे में , क्रमशः। [नोट: किसी वस्तु (not.ter) के अंत में एक संकेतक पिछले एक असंबंधित को इंगित करने के लिए नहीं माना जाता हैऑब्जेक्ट का प्रकार जो उस पते पर स्थित हो सकता है। एक पॉइंटर मान अमान्य हो जाता है जब यह स्टोरेज जो नोट करता है वह अपनी भंडारण अवधि के अंत तक पहुंचता है; देखें 6.7। -एंड नोट] सूचक अंकगणितीय (8.7) और तुलना (8.9, 8.10) के प्रयोजनों के लिए, एन तत्वों के एक एक्स के अंतिम तत्व के अंत में एक सूचक को एक काल्पनिक एक्स के लिए एक सूचक के बराबर माना जाता है [ n]। सूचक प्रकारों का मूल्य प्रतिनिधित्व कार्यान्वयन-परिभाषित है। लेआउट-संगत प्रकारों के संकेत समान मान प्रतिनिधित्व और संरेखण आवश्यकताएं (6.11) होंगे ...

टिप्पणी अंत अतीत एक संकेतक ... यहाँ लागू नहीं होता क्योंकि वस्तुओं के द्वारा की ओर इशारा किया p1और p2और नहीं असंबंधित , लेकिन एक ही पूरा वस्तु में नेस्टेड रहते हैं, तो सूचक arithmetics ऑब्जेक्ट के अंदर इस अर्थ में कि भंडारण प्रदान करते हैं: p2 - p1परिभाषित किया गया है और है (&buffer[sizeof(int)] - buffer]) / sizeof(int)वह 1 है।

तो p1 + 1 है करने के लिए एक सूचक *p2है, और *(p1 + 1) = 10;परिभाषित व्यवहार है और का मान सेट *p2


मैंने C4 एनेक्स को C ++ 14 और वर्तमान (C ++ 17) मानकों के बीच संगतता पर भी पढ़ा है। गतिशील रूप से एकल वर्ण सरणी में बनाई गई वस्तुओं के बीच सूचक अंकगणित का उपयोग करने की संभावना को हटाने से एक महत्वपूर्ण बदलाव होगा कि IMHO को वहां उद्धृत किया जाना चाहिए, क्योंकि यह आमतौर पर उपयोग की जाने वाली विशेषता है। जैसा कि इसके बारे में कुछ भी संगतता पृष्ठों में मौजूद नहीं है, मुझे लगता है कि यह पुष्टि करता है कि यह इसे मना करने के लिए मानक का इरादा नहीं था।

विशेष रूप से, यह उस वर्ग के ऑब्जेक्ट के सामान्य गतिशील निर्माण को हरा देगा जिसमें कोई डिफ़ॉल्ट निर्माता नहीं है:

class T {
    ...
    public T(U initialization) {
        ...
    }
};
...
unsigned char *mem = new unsigned char[N * sizeof(T)];
T * arr = reinterpret_cast<T*>(mem); // See the array as an array of N T
for (i=0; i<N; i++) {
    U u(...);
    new(arr + i) T(u);
}

arr फिर एक सरणी के पहले तत्व के लिए एक संकेतक के रूप में इस्तेमाल किया जा सकता है ...


अहा, इसलिए दुनिया पागल नहीं हुई है। +1
स्टोरीटेलर - Unslander मोनिका

@StoryTeller: मुझे भी उम्मीद है। इसके अलावा संगतता अनुभाग में इसके बारे में एक शब्द भी नहीं। लेकिन ऐसा लगता है कि विपरीत राय के यहाँ अधिक प्रतिष्ठा है ...
सर्ज बलेस्टा

2
आप एक शब्द "असंबंधित", को एक गैर-मानक नोट में जब्त कर रहे हैं, और यह अर्थ देते हैं कि यह सहन नहीं कर सकता है, [expr.add] में नियम के विपरीत नियमों में सूचक अंकगणित को नियंत्रित करता है। अनुलग्नक सी में कुछ भी नहीं है क्योंकि सामान्य-मामले के सूचक अंकगणित ने कभी भी किसी मानक में काम नहीं किया है। तोड़ने के लिए कुछ भी नहीं है।
TC

3
@TC: Google इस "वेक्टर क्रियान्वयन समस्या" पर किसी भी जानकारी का पता लगाने में बहुत ही बेकार है, क्या आप मदद कर सकते हैं?
Matthieu M.

6
@MatthieuM। देखें मुख्य मुद्दा 2182 , इस एसटीडी-चर्चा धागा, P0593R0 , और P0593R1 (विशेष रूप से खंड 1.3) । मूल समस्या यह है कि vectorकोई सरणी ऑब्जेक्ट नहीं बनाता (और नहीं कर सकता), लेकिन एक इंटरफ़ेस है जो उपयोगकर्ता को एक सूचक प्राप्त करने की अनुमति देता है जो सूचक अंकगणितीय (जो केवल सरणी ऑब्जेक्ट्स में पॉइंटर्स के लिए परिभाषित किया गया है) का समर्थन करता है।
टीसी

1

यहाँ दिए गए उत्तरों पर विस्तार करना एक उदाहरण है कि मेरा मानना ​​है कि संशोधित शब्दांकन को छोड़कर:

चेतावनी: अपरिभाषित व्यवहार

#include <iostream>
int main() {
    int A[1]{7};
    int B[1]{10};
    bool same{(B)==(A+1)};

    std::cout<<B<< ' '<< A <<' '<<sizeof(*A)<<'\n';
    std::cout<<(same?"same":"not same")<<'\n';
    std::cout<<*(A+1)<<'\n';//!!!!!  
    return 0;
}

पूरी तरह से कार्यान्वयन निर्भर (और नाजुक) कारणों से इस कार्यक्रम का संभावित उत्पादन है:

0x7fff1e4f2a64 0x7fff1e4f2a60 4
same
10

उस आउटपुट से पता चलता है कि दो सरणियों (उस स्थिति में) को स्मृति में संग्रहीत किया जाता है जैसे कि 'पहले का एक अंत' Aहोता है, जिसमें पहले तत्व के पते का मान होता है B

संशोधित विनिर्देश यह सुनिश्चित कर रहा है कि परवाह किए बिना A+1कभी भी वैध सूचक नहीं है B। पुराना वाक्यांश 'इसकी परवाह किए बिना कि मान कैसे प्राप्त किया जाता है' कहता है कि यदि 'A + 1' 'B [0] को इंगित करता है, तो यह' B [0] का एक वैध सूचक है। यह अच्छा नहीं हो सकता है और निश्चित रूप से कभी इरादा नहीं है।


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

@Persixty तो एक पॉइंटर ऑब्जेक्ट का मान ऑब्जेक्ट के बाइट्स द्वारा निर्धारित किया जाता है, और कुछ नहीं। तो एक ही अवस्था वाली दो वस्तुएं एक ही वस्तु की ओर इशारा करती हैं। यदि एक वैध है, तो दूसरा भी है। इसलिए आम आर्किटेक्चर पर, जहां एक पॉइंटर वैल्यू को एक संख्या के रूप में दर्शाया जाता है, दो समान मान वाले पॉइंट्स समान ऑब्जेक्ट्स की ओर इशारा करते हैं और अंत में एक समान अन्य ऑब्जेक्ट्स।
जिज्ञासु

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

प्रश्न में गैर-मानक नोट हमें 'वन-पास्ट-द-एंड' के बारे में सोचना चाहता है क्योंकि किसी भी चीज़ की ओर इशारा नहीं करता है। हम दोनों जानते हैं कि व्यवहार में कुछ अच्छी तरह से इंगित किया जा सकता है और व्यवहार में यह संभव हो सकता है। लेकिन वह (मानक के अनुसार) एक वैध कार्यक्रम नहीं है। हम एक ऐसे कार्यान्वयन की कल्पना कर सकते हैं जो जानता है कि एक अंकगणित अंकगणित-से-अतीत के अंत तक प्राप्त किया गया था और यदि अपवादित हो तो अपवाद छोड़ देता है। जबकि मुझे पता है कि प्लेटफॉर्म का पता है जो ऐसा करता है। मुझे लगता है कि मानक इसे बाहर नहीं करना चाहता।
पर्साल्टी जूल

@ Curiousguy इसके अलावा, मुझे यकीन नहीं है कि संभावित मूल्यों को मानने से आपका क्या मतलब है। C ++ द्वारा परिभाषित एक तुच्छ प्रकार की यह आवश्यक विशेषता नहीं है।
Persixty
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.