12

ऐसे मामले क्या हैं जहां reinterpret_castchar* (या char[N]) अनिर्धारित व्यवहार है, और यह कब परिभाषित व्यवहार है? इस प्रश्न का उत्तर देने के लिए मुझे अंगूठे का नियम क्या उपयोग करना चाहिए?reinterpret_cast, char *, और अपरिभाषित व्यवहार

alignas(int) char data[sizeof(int)]; 
int *myInt = new (data) int;   // OK 
*myInt = 34;       // OK 
int i = *reinterpret_cast<int*>(data); // <== UB! have to use std::launder 

लेकिन क्या बिंदु पर हम एक char सरणी पर एक reinterpret_cast कर सकते हैं और यह नहीं व्यवहार अपरिभाषित जा चुके हैं:


हम निम्नलिखित, this question से सीखा के रूप में अपरिभाषित व्यवहार है?

  1. नहीं new, बस reinterpret_cast:: यहाँ कुछ सरल उदाहरण हैं

    alignas(int) char data[sizeof(int)]; 
    *reinterpret_cast<int*>(data) = 42; // is the first cast write UB? 
    int i = *reinterpret_cast<int*>(data); // how about a read? 
    *reinterpret_cast<int*>(data) = 4;  // how about the second write? 
    int j = *reinterpret_cast<int*>(data); // or the second read? 
    

    कब होता int शुरू करने के लिए जीवन भर? क्या यह data की घोषणा के साथ है? यदि हां, तो data का जीवनकाल कब समाप्त होता है?

  2. क्या होगा अगर data एक सूचक थे?

    char* data_ptr = new char[sizeof(int)]; 
    *reinterpret_cast<int*>(data_ptr) = 4;  // is this UB? 
    int i = *reinterpret_cast<int*>(data_ptr); // how about the read? 
    
  3. क्या होगा यदि मैं बस तार पर structs प्राप्त कर रहा हूँ और सशर्त क्या पहले बाइट है के आधार पर उन्हें कास्ट करना चाहते हैं?

    // bunch of handle functions that do stuff with the members of these types 
    void handle(MsgType1 const&); 
    void handle(MsgTypeF const&); 
    
    char buffer[100]; 
    ::recv(some_socket, buffer, 100) 
    
    switch (buffer[0]) { 
    case '1': 
        handle(*reinterpret_cast<MsgType1*>(buffer)); // is this UB? 
        break; 
    case 'F': 
        handle(*reinterpret_cast<MsgTypeF*>(buffer)); 
        break; 
    // ... 
    } 
    

इन मामलों यूबी के किसी भी कर रहे हैं? क्या वे सभी हैं? क्या इस प्रश्न का उत्तर सी ++ 11 से सी ++ 1z के बीच बदलता है?

+0

** (1) ** मेरे लिए मान्य दिखता है। दोनों बयानों में, एक नई 'int' ऑब्जेक्ट को स्वीकार किया जाता है और एक मान असाइन किया जाता है। * पढ़ना * वह मान वह जगह है जहां चीजें बालों वाली हो रही हैं। ** (2) ** के साथ ही (मानते हुए 'आकार (int) == 4')। ** (3) ** मुझे यूबी की तरह दिखता है। –

+0

@ इगोरटैंडनिक ने कुछ पढ़ने के साथ प्रश्नों को फिसल दिया, और 'आकार (int)' के बारे में धारणा से छुटकारा पा लिया, धन्यवाद। – Barry

+1

अब ** (1) ** और ** (2) ** यूबी को लिंक किए गए प्रश्न के समान आधार पर प्रदर्शित करने लगते हैं। पहली कास्ट से पॉइंटर को सहेजकर और बाद के सभी लिखने और पढ़ने के लिए इसका उपयोग करके बचाया जाना आसान होगा। –

उत्तर

3

यहाँ खेलने में दो नियम हैं:

  1. [basic.lval]/8, उर्फ, सख्त अलियासिंग नियम: सीधे शब्दों में, आप एक सूचक/संदर्भ के लिए के माध्यम से एक वस्तु उपयोग नहीं कर सकते गलत प्रकार

  2. [base.life]/8: बस डालें, अगर आप किसी भिन्न प्रकार के ऑब्जेक्ट के लिए स्टोरेज का पुन: उपयोग करते हैं, तो आप पॉइंटर्स को पुराने ऑब्जेक्ट्स को पहले लॉन्डर किए बिना उपयोग नहीं कर सकते हैं।

ये नियम "एक स्मृति स्थान" या "भंडारण के एक क्षेत्र" और "एक वस्तु" के बीच एक अंतर बनाने का एक महत्वपूर्ण हिस्सा हैं।

अपने कोड उदाहरण के

सभी एक ही समस्या का शिकार होने का:

alignas(int) char data[sizeof(int)]; 

उस प्रकार char[sizeof(int)] की एक वस्तु बनाता है: वे वस्तु आप के लिए उन्हें डाली नहीं कर रहे हैं। वह ऑब्जेक्ट int है। इसलिए, आप इसे एक्सेस नहीं कर सकते हैं जैसे कि यह था। इससे कोई फर्क नहीं पड़ता कि यह एक पढ़ा या लिखना है; आप अभी भी यूबी को उकसाते हैं।

इसी

:

char* data_ptr = new char[sizeof(int)]; 

भी प्रकार char[sizeof(int)] की एक वस्तु बन जाता है।

char buffer[100]; 

इस प्रकार char[100] की एक वस्तु बनाता है। वह वस्तु न तो MsgType1 है और न ही है। तो आप इसे एक्सेस नहीं कर सकते जैसे कि यह भी था।

ध्यान दें कि यूबी यहां है जब आप Msg* प्रकारों में से एक के रूप में बफर तक पहुंचते हैं, न कि जब आप पहली बाइट की जांच करते हैं। यदि आपके सभी Msg* प्रकारों को तुलनीय रूप से कॉपी करने योग्य हैं, तो यह पहले बाइट को पढ़ने के लिए पूरी तरह से स्वीकार्य है, फिर बफर को उचित प्रकार के ऑब्जेक्ट में कॉपी करें।

switch (buffer[0]) { 
case '1': 
    { 
     MsgType1 msg; 
     memcpy(&msg, buffer, sizeof(MsgType1); 
     handle(msg); 
    } 
    break; 
case 'F': 
    { 
     MsgTypeF msg; 
     memcpy(&msg, buffer, sizeof(MsgTypeF); 
     handle(msg); 
    } 
    break; 
// ... 
} 

ध्यान दें कि हम क्या भाषा राज्यों व्यवहार अपरिभाषित हो जाएगा के बारे में बात कर रहे हैं। बाधाएं अच्छी हैं कि इनमें से किसी के साथ संकलक ठीक होगा।

क्या इस प्रश्न का उत्तर सी ++ 11 से सी ++ 1z के बीच बदलता है?

वहाँ कुछ महत्वपूर्ण नियम किया गया है स्पष्टीकरण सी ++ 11 (विशेष रूप से [basic.life]) के बाद से। लेकिन नियमों के पीछे इरादा नहीं बदला है।

+0

मेरी 'char' सरणी को घोषित नहीं करता है, संभवतः कुछ अभी तक रिक्त-प्रारंभिक प्रकार' टी' के लिए संग्रहण प्राप्त करने का गठन नहीं करता है? उस अर्थ में, 'लांडर' की स्वस्थ छिड़काव सबकुछ अच्छी तरह से परिभाषित नहीं करेगी? – Barry

+0

@ बैरी: [यह नहीं है कि 'std :: launder' क्या है] (http://stackoverflow.com/a/39382728/734069)। यदि आप किसी पुराने ऑब्जेक्ट के ऑब्जेक्ट में किसी ऑब्जेक्ट का जीवनकाल प्रारंभ करते हैं, तो यह आपको पॉइंटर से पुराने ऑब्जेक्ट में पॉइंटर प्राप्त करने देता है। यह कुछ भी जीवन भर शुरू नहीं करता है। "* मेरे चार सरणी को घोषित नहीं करना संभवतः कुछ अभी तक निर्विवाद-प्रारंभिक प्रकार टी के लिए भंडारण प्राप्त करने का गठन नहीं करता है? *" उस तर्क से, * कोई ऑब्जेक्ट * एक "अभी तक निर्विवाद-प्रारंभिक प्रकार हो सकता है टी "। आखिरकार, किसी ऑब्जेक्ट में स्टोरेज होता है। 'char [X]' किसी भी अन्य वस्तु के रूप में एक वस्तु है। –

+0

लेकिन जब वह [मूलभूत जीवन] कहता है कि किसी ऑब्जेक्ट का जीवनकाल शुरू होता है - जब संग्रहण प्राप्त होता है। दिया गया 'चार बफ [4]; int * i = new (buf) int; ', 'int' के जीवनकाल को 'i'' से इंगित करता है? – Barry

संबंधित मुद्दे