टाइपिंग पाइंट थीम पर भिन्नता: इन-प्लेस ट्रिवियल कंस्ट्रक्शन


9

मुझे पता है कि यह एक बहुत ही सामान्य विषय है, लेकिन जितना विशिष्ट यूबी को ढूंढना आसान है, मुझे अब तक यह संस्करण नहीं मिला।

इसलिए, मैं डेटा की वास्तविक प्रतिलिपि से बचने के दौरान पिक्सेल वस्तुओं को औपचारिक रूप से पेश करने की कोशिश कर रहा हूं।

क्या यह मान्य है?

struct Pixel {
    uint8_t red;
    uint8_t green;
    uint8_t blue;
    uint8_t alpha;
};

static_assert(std::is_trivial_v<Pixel>);

Pixel* promote(std::byte* data, std::size_t count)
{
    Pixel * const result = reinterpret_cast<Pixel*>(data);
    while (count-- > 0) {
        new (data) Pixel{
            std::to_integer<uint8_t>(data[0]),
            std::to_integer<uint8_t>(data[1]),
            std::to_integer<uint8_t>(data[2]),
            std::to_integer<uint8_t>(data[3])
        };
        data += sizeof(Pixel);
    }
    return result; // throw in a std::launder? I believe it is not mandatory here.
}

अपेक्षित उपयोग पैटर्न, भारी सरलीकृत:

std::byte * buffer = getSomeImageData();
auto pixels = promote(buffer, 800*600);
// manipulate pixel data

अधिक विशेष रूप से:

  • क्या इस कोड में अच्छी तरह से परिभाषित व्यवहार है?
  • यदि हाँ, तो क्या यह लौटे हुए पॉइंटर का उपयोग करने के लिए सुरक्षित है?
  • यदि हाँ, तो इसे और किस Pixelप्रकार से बढ़ाया जा सकता है? (is_trivial प्रतिबंध को आराम? केवल 3 घटकों के साथ पिक्सेल?)।

क्लैंग और जीसीसी दोनों पूरे पाश को कुछ भी नहीं करने के लिए अनुकूलित करते हैं, जो कि मैं चाहता हूं। अब, मैं जानना चाहता हूं कि यह कुछ सी ++ नियमों का उल्लंघन करता है या नहीं।

यदि आप इसके साथ खेलना चाहते हैं तो Godbolt लिंक

(ध्यान दें: मैंने c ++ 17 के बावजूद टैग नहीं किया था std::byte, क्योंकि प्रश्न का उपयोग करता है char)


2
लेकिन सन्निहित Pixelनए रखा अभी भी Pixelएस की एक सरणी नहीं है ।
Jarod42

1
@spectras हालांकि एक सरणी नहीं बनाता है। आपके पास बस एक-दूसरे के बगल में पिक्सेल वस्तुओं का एक गुच्छा है। यह एक सरणी से अलग है।
नेथनऑलिवर

1
तो नहीं तुम कहाँ हो pixels[some_index]या *(pixels + something)? वह यूबी होगा।
नाथनऑलिवर

1
प्रासंगिक अनुभाग यहां है और कुंजी वाक्यांश है यदि P किसी सरणी ऑब्जेक्ट x के सरणी तत्व i की ओर इंगित करता है । यहाँ pixels(P) सरणी ऑब्जेक्ट के लिए पॉइंटर नहीं है, बल्कि सिंगल के लिए एक पॉइंटर है Pixel। इसका मतलब है कि आप केवल pixels[0]कानूनी तौर पर पहुंच सकते हैं ।
नेथनऑलिवर

3
आप wg21.link/P0593 पढ़ना चाहते हैं ।
परमानंद

जवाबों:


3

यह promoteएक सरणी के रूप में परिणाम का उपयोग करने के लिए अपरिभाषित व्यवहार है । यदि हम देखते हैं [expr.add] /4.2 हमारे पास

अन्यथा, अगर Pएक सरणी तत्व को अंक iएक सरणी वस्तु केx साथ nतत्वों ([dcl.array]), भाव P + Jऔर J + P(जहां Jमहत्व है j) (संभवतः-काल्पनिक) सरणी तत्व को इंगित i+jकी xअगर 0≤i+j≤nऔर अभिव्यक्ति P - Jके लिए अंक ( संभवतः-काल्पनिक) सरणी तत्व i−jकी xअगर 0≤i−j≤n

हम देखते हैं कि यह वास्तव में एक सरणी वस्तु को इंगित करने के लिए सूचक की आवश्यकता है। आप वास्तव में एक सरणी वस्तु नहीं है, हालांकि। आपके पास एक एकल के लिए एक संकेतक है Pixelजो बस Pixelsसन्निहित स्मृति में इसका पालन करने के लिए होता है । इसका मतलब है कि एकमात्र तत्व जिसे आप वास्तव में एक्सेस कर सकते हैं वह पहला तत्व है। किसी अन्य चीज़ तक पहुँचने की कोशिश करना अपरिभाषित व्यवहार होगा क्योंकि आप पॉइंटर के लिए मान्य डोमेन के अंत में हैं।


यह जल्दी पता लगाने के लिए धन्यवाद। मुझे लगता है कि बजाय मैं एक पुनरावृत्ति करता हूँ। एक विचार के रूप में, इसका मतलब यह भी है कि &somevector[0] + 1यूबी (ठीक है, मेरा मतलब है, परिणामी सूचक का उपयोग करना होगा)।
स्पेक्टर्स

@spectras यह वास्तव में ठीक है। आप हमेशा एक वस्तु से एक को सूचक प्राप्त कर सकते हैं। आप बस उस सूचक को निष्क्रिय नहीं कर सकते, भले ही वहां कोई वैध वस्तु हो।
नाथनऑलिवर

हां, मैंने खुद को स्पष्ट करने के लिए टिप्पणी को संपादित किया, मेरा मतलब था कि परिणामी सूचक को डीफ्रेंसिंग करना :) पुष्टि करने के लिए धन्यवाद।
स्पेक्टर्स

@spectras कोई समस्या नहीं। C ++ का यह भाग बहुत कठिन हो सकता है। हालांकि हार्डवेयर वह करेगा जो हम उसे करना चाहते हैं, लेकिन वास्तव में वह नहीं है जो कोडिंग करना चाहते थे। हम C ++ सार मशीन के लिए कोडिंग कर रहे हैं और यह एक प्रेरक मशीन है;) उम्मीद है कि P0593 को अपनाया जाएगा और यह बहुत आसान हो जाएगा।
नाथनऑलिवर

1
@spectras नहीं, क्योंकि एक std वेक्टर को एक सरणी युक्त के रूप में परिभाषित किया गया है, और आप सरणी तत्वों के बीच सूचक अंकगणित कर सकते हैं। UB में चलने के बिना, दुख की बात है कि C ++ में ही std वेक्टर को लागू करने का कोई तरीका नहीं है।
यक्क - एडम नेवरामॉन्ट

1

आपके पास पहले से ही दिए गए पॉइंटर के सीमित उपयोग के बारे में एक उत्तर है, लेकिन मैं यह जोड़ना चाहता हूं कि मुझे भी लगता है कि आपको std::launderपहले पहुंचने में सक्षम होना चाहिए Pixel:

reinterpret_castपहले किसी भी किया जाता है Pixelवस्तु बनाई गई है (यह मानते हुए आप में ऐसा नहीं है getSomeImageData)। इसलिए reinterpret_castपॉइंटर वैल्यू को नहीं बदलेगा। परिणामी पॉइंटर अभी भी std::byteफ़ंक्शन में दिए गए सरणी के पहले तत्व को इंगित करेगा ।

जब आप Pixelऑब्जेक्ट बनाते हैं, तो वे सरणी के भीतर नेस्टेड होने जा रहे हैं std::byteऔर std::byteएरे ऑब्जेक्ट के लिए स्टोरेज प्रदान करेगा Pixel

ऐसे मामले हैं जहां भंडारण का पुन: उपयोग पुरानी वस्तु के लिए एक संकेतक का कारण बनता है जो नई वस्तु को स्वचालित रूप से इंगित करता है। लेकिन यह वह नहीं है जो यहां हो रहा है, इसलिए resultअभी भी std::byteवस्तु को इंगित करेगा , वस्तु को नहीं Pixel। मुझे लगता है कि वह इसका उपयोग कर रहा है जैसे कि यह किसी Pixelवस्तु की ओर इशारा कर रहा है तकनीकी रूप से अपरिभाषित व्यवहार करने वाला है।

मुझे लगता है कि यह अभी भी है, भले ही आप ऑब्जेक्ट reinterpret_castबनाने के Pixelबाद करते हैं, क्योंकि Pixelऑब्जेक्ट और std::byteइसके लिए स्टोरेज प्रदान करता है, पॉइंटर-इंटरकनेक्टेबल नहीं हैं । तो फिर भी पॉइंटर पॉइंट को इंगित करता रहेगा std::byte, Pixelऑब्जेक्ट को नहीं ।

यदि आपने प्लेसमेंट-नए में से एक के परिणाम से लौटने के लिए पॉइंटर प्राप्त किया है, तो सब कुछ ठीक होना चाहिए, जहां तक ​​उस विशिष्ट Pixelवस्तु तक पहुंच का संबंध है।


इसके अलावा, आपको यह सुनिश्चित करने की आवश्यकता है कि std::byteसूचक उपयुक्त रूप से गठबंधन किया गया है Pixelऔर यह कि सरणी वास्तव में काफी बड़ी है। जहां तक ​​मुझे याद है कि मानक को वास्तव में आवश्यकता नहीं है कि Pixelउसके पास एक ही संरेखण हो std::byteया उसके पास पैडिंग न हो।


इसके अलावा, इसमें से कोई भी Pixelतुच्छ या वास्तव में इसके किसी अन्य गुण पर निर्भर नहीं करता है। सब कुछ उसी तरह का व्यवहार करेगा जब तक कि std::byteसरणी पर्याप्त आकार की हो और Pixelवस्तुओं के लिए उपयुक्त रूप से संरेखित हो ।


मेरा मानना ​​है कि यह सही है। यहां तक ​​कि अगर सरणी चीज़ (अनिमिबिलिटी std::vector) भी कोई समस्या नहीं थी, तो आपको std::launderकिसी भी प्लेसमेंट- newएड के एक्सेस से पहले परिणाम की आवश्यकता होगी Pixel। अभी के अनुसार, std::launderयहाँ यूबी है, क्योंकि आसन्न Pixelएस लॉन्ड्र्ड पॉइंटर से उपलब्ध होगा ।
फ्यूरिश

@Fureeish मुझे यकीन नहीं है कि std::launderअगर resultलौटने से पहले आवेदन किया जाता है तो यूबी क्यों होगा । समीपवर्ती ईल.इस / सी ++ ड्राफ्ट / डिप्र.लंडर .4 की मेरी समझ से जा रहे लॉन्ड्र्ड पॉइंटर के माध्यम से Pixel" पहुंच योग्य " नहीं है । और यहां तक ​​कि मैं यह नहीं देख पाया कि यह यूबी कैसे है, क्योंकि मूल सूचक से संपूर्ण मूल सरणी पहुंच योग्य है। std::byte
अखरोट

लेकिन अगला पॉइंटर Pixelसे पहुंच योग्य नहीं होगा std::byte, लेकिन यह launderएड पॉइंटर से है। मेरा मानना ​​है कि यह यहां प्रासंगिक है। मैं सही होने पर खुश हूं, हालांकि।
फ्यूरिश

@Fureeish मैं जो भी उदाहरण देता हूं, उसमें से कोई भी यहां नहीं बता सकता और आवश्यकता की परिभाषा भी मानक के समान ही कहता है। रीचैबिलिटी को बाइट्स के भंडारण के संदर्भ में परिभाषित किया गया है, न कि वस्तुओं पर। अगले पर कब्जा कर लिया बाइट Pixelमुझे मूल सूचक से पहुंच से बाहर लगता है, क्योंकि मूल सूचक std::byteसरणी के एक तत्व को इंगित करता है जिसमें बाइट्स बनाने के लिए भंडारण होता Pixelहै " या तुरंत संलग्न सरणी के भीतर जिसमें Z है तत्व "स्थिति लागू होती है (जहां Zहै Y, अर्थात std::byteतत्व ही)।
अखरोट

मुझे लगता है कि भंडारण की बाइट्स जो कि अगली Pixelऑक्यूपेंसी लॉन्ड्र्ड पॉइंटर के माध्यम से नहीं पहुंच पाती हैं, क्योंकि पॉइंट-टू Pixelऑब्जेक्ट एरे ऑब्जेक्ट का एलिमेंट नहीं है और यह किसी अन्य संबंधित ऑब्जेक्ट के साथ पॉइंटर-इंटरकनेक्टेबल भी नहीं है। लेकिन मैं std::launderउस गहराई में पहली बार इस विस्तार के बारे में भी सोच रहा हूं । मैं इस बारे में 100% निश्चित नहीं हूं।
अखरोट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.