2016-09-03 8 views
18

मैं इस उदाहरण के माध्यम से जा रहा था जिसमें एक मनमानी फ्लोट का प्रतिनिधित्व करने के लिए हेक्स बिट पैटर्न को आउटपुट करने वाला एक फ़ंक्शन है।क्यों एक सूचक को फिर से गिरावट?

void ExamineFloat(float fValue) 
{ 
    printf("%08lx\n", *(unsigned long *)&fValue); 
} 

क्यों fValue का पता, अहस्ताक्षरित लंबे सूचक को डाली, तो भिन्नता ले? क्या वह काम नहीं करता है जो सीधे सीधी कलाकार के बराबर काम करता है?

printf("%08lx\n", (unsigned long)fValue); 

मैंने कोशिश की और जवाब एक जैसा नहीं है, इसलिए उलझन में है।

+9

यह अपरिभाषित व्यवहार है। ऐसा कुछ ऐसा है जो 1 9 8 9 में सी को मानकीकृत करने से पहले किया गया था, और कुछ ने –

उत्तर

27
(unsigned long)fValue 

यह एक unsigned long मूल्य के लिए float मूल्य बदल देता है, "सामान्य गणित रूपांतरण" के अनुसार।

*(unsigned long *)&fValue 

यहाँ इरादा पता जिस पर fValue संग्रहीत किया जाता है लेने के लिए, नाटक वहाँ एक float लेकिन इस पते पर कोई unsigned long नहीं है कि, और फिर उस unsigned long को पढ़ने के लिए है। इसका उद्देश्य बिट पैटर्न की जांच करना है जिसका प्रयोग स्मृति में float को संग्रहीत करने के लिए किया जाता है।

जैसा कि दिखाया गया है, यह अपरिभाषित व्यवहार का कारण बनता है।

कारण: आप ऑब्जेक्ट के प्रकार के लिए "संगत" नहीं होने वाले किसी प्रकार के सूचक के माध्यम से किसी ऑब्जेक्ट तक नहीं पहुंच सकते हैं।"संगत" प्रकार उदाहरण के लिए हैं (unsigned) char और अन्य सभी प्रकार, या संरचनाएं जो प्रारंभिक सदस्यों को साझा करती हैं (यहां सी की बात कर रही हैं)। विस्तृत (सी 11) सूची के लिए §6.5/7 N1570 देखें

समाधान (- - अधिक व्यापक ध्यान दें कि की 'उपयुक्त' मेरी उपयोग अलग है संदर्भित पाठ की तुलना में।): unsigned char * में कास्ट करें, व्यक्ति का उपयोग वस्तु की बाइट्स और एक unsigned long उनमें से बाहर इकट्ठा:

unsigned long pattern = 0; 
unsigned char * access = (unsigned char *)&fValue; 
for (size_t i = 0; i < sizeof(float); ++i) { 
    pattern |= *access; 
    pattern <<= CHAR_BIT; 
    ++access; 
} 

ध्यान दें कि (@CodesInChaos ने बताया के रूप में) ऊपर व्यवहार करता है सबसे महत्वपूर्ण बाइट पहले ("बड़ा endian") के साथ संग्रहीत किया जा रहा के रूप में चल बिन्दु मूल्य । यदि आपका सिस्टम फ़्लोटिंग पॉइंट मानों के लिए एक अलग बाइट ऑर्डर का उपयोग करता है तो आपको उस पर समायोजित करने की आवश्यकता होगी (या unsigned long के बाइट को पुनर्व्यवस्थित करें, जो भी आपके लिए अधिक व्यावहारिक है)।

+2

क्या 'reinterpret_cast (fValue) को C++ में अनुमति/परिभाषित किया जाएगा (निश्चित रूप से आकार के आकार को मानते हुए)? – celtschk

+5

मूल कोड तब तक काम करता है जब तक फ्लोट और पूर्णांक की अंतहीनता समान होती है (यूबी को अनदेखा कर रहा है)। आपका कोड बड़ा-एंडियन मानता है। मैं 'uint32_t' में' memcpy' का उपयोग करता हूं (और मिलान आकार के लिए एक दावा)। – CodesInChaos

+2

@celtschk अगर मैं वास्तव में उस संदर्भ का उपयोग कर रहा हूं तो मुझे आश्चर्य होगा कि सख्त एलियासिंग उल्लंघन के रूप में नहीं गिना जाएगा। - "प्रकार टी 1 की एक लेल्यू अभिव्यक्ति को किसी अन्य प्रकार के टी 2 के संदर्भ में परिवर्तित किया जा सकता है। परिणाम एक लालसा या xvalue मूल वस्तु के रूप में एक ही वस्तु का जिक्र है, लेकिन एक अलग प्रकार के साथ। कोई अस्थायी नहीं बनाया गया है, कोई प्रति नहीं है बनाया गया, कोई कन्स्ट्रक्टर या रूपांतरण फ़ंक्शंस नहीं कहा जाता है। परिणामस्वरूप संदर्भ केवल टाइपिंग एलियासिंग नियमों द्वारा अनुमत होने पर सुरक्षित रूप से एक्सेस किया जा सकता है "[(src)] (http://en.cppreference.com/w/cpp/language/reinterpret_cast) – CodesInChaos

3

सी में टाइपकास्टिंग एक प्रकार का रूपांतरण और मूल्य रूपांतरण दोनों करता है। फ़्लोटिंग पॉइंट → हस्ताक्षरित लंबे रूपांतरण फ़्लोटिंग पॉइंट नंबर के आंशिक भाग को छोटा करता है और मान को किसी हस्ताक्षरित लंबे की संभावित सीमा तक प्रतिबंधित करता है। एक प्रकार के पॉइंटर से दूसरे में कनवर्ट करने के लिए मूल्य में कोई आवश्यक परिवर्तन नहीं है, इसलिए पॉइंटर टाइपकास्ट का उपयोग करना उस प्रस्तुति से जुड़े प्रकार को बदलने के दौरान इन-मेमोरी प्रस्तुति को रखने का एक तरीका है।

इस मामले में, यह फ़्लोटिंग पॉइंट मान के द्विआधारी प्रतिनिधित्व को आउटपुट करने में सक्षम होने का एक तरीका है।

+1

के साथ नहीं रखा है "एक प्रकार के पॉइंटर से दूसरे में कनवर्ट करने के लिए मूल्य में कोई आवश्यक परिवर्तन नहीं है", इसमें कोई बदलाव हो सकता है या नहीं भी हो सकता है मूल्य प्रतिनिधित्व। आधुनिक पीसी पर और आगे भी आमतौर पर नहीं है। लेकिन यह ओपी के मुद्दे के लिए प्रासंगिक नहीं है, मुझे लगता है कि आप कुछ ऐसा कहना चाहते थे, पॉइंटर प्रकार को परिवर्तित करने से पॉइंटर इंगित करने वाली स्मृति की सामग्री को नहीं बदलेगा। –

4

फ़्लोटिंग-पॉइंट मानों में स्मृति प्रस्तुतिकरण हैं: उदाहरण के लिए बाइट्स IEEE 754 का उपयोग करके फ़्लोटिंग-पॉइंट मान का प्रतिनिधित्व कर सकते हैं।

पहली अभिव्यक्ति *(unsigned long *)&fValue इन बाइट्स की व्याख्या करेगा जैसे कि वह प्रतिनिधित्व एक unsigned long मूल्य की था। वास्तव में सी मानक में इसका परिणाम एक अपरिभाषित व्यवहार होता है (तथाकथित "सख्त एलियासिंग नियम" के अनुसार)। व्यावहारिक रूप से, ऐसे उन्मूलन जैसे मुद्दे हैं जिन्हें ध्यान में रखना है।

दूसरी अभिव्यक्ति (unsigned long)fValue सी मानक अनुपालन है।

सी 11 (n1570), § 6.3.1.4 रियल फ्लोटिंग और पूर्णांक

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

4

*(unsigned long *)&fValueunsigned long पर प्रत्यक्ष कलाकार के बराबर नहीं है।

(unsigned long)fValue करने के लिए रूपांतरण एक unsigned long में fValue का मूल्य बदल देता है, एक unsigned long मूल्य के लिए एक float मूल्य के रूपांतरण के लिए सामान्य नियमों का उपयोग कर। unsigned long (उदाहरण के लिए, बिट्स के संदर्भ में) उस मान का प्रतिनिधित्व float में समान मान का प्रतिनिधित्व करने से काफी अलग हो सकता है।

रूपांतरण *(unsigned long *)&fValue औपचारिक रूप से अपरिभाषित व्यवहार है। यह fValue पर कब्जा कर लिया गया स्मृति की व्याख्या करता है जैसे कि यह unsigned long है। व्यावहारिक रूप से (यानी यह अक्सर होता है, भले ही व्यवहार अपरिभाषित है) यह अक्सर fValue से काफी अलग मूल्य प्रदान करेगा।

1

जैसा कि अन्य ने पहले से ही नोट किया है, एक गैर-चार प्रकार के पॉइंटर को एक पॉइंटर को एक अलग गैर-चार प्रकार के लिए कास्टिंग करना और फिर डिफ्रेंसिंग अपरिभाषित व्यवहार है।

printf("%08lx\n", *(unsigned long *)&fValue) अपरिभाषित व्यवहार जरूरी नहीं कि एक प्रोग्राम है जो इस तरह के एक भड़ौआ प्रदर्शन करने के लिए प्रयास करता चल हार्ड ड्राइव विलोपन में परिणाम या नाक राक्षसों (अपरिभाषित व्यवहार के दो पहचान) लोगों को नाक से फूटना कर देगा का आह्वान करते हैं। किसी कंप्यूटर पर जो sizeof(unsigned long)==sizeof(float) और जिस पर दोनों प्रकार के एक ही संरेखण आवश्यकताओं है में, कि printf लगभग निश्चित रूप से एक यह जो सवाल में चल बिन्दु मूल्य की हेक्स प्रतिनिधित्व मुद्रित करने के लिए है करने के लिए, उम्मीद है कि क्या करना होगा।

यह आश्चर्यजनक नहीं होना चाहिए। सी मानक खुले तौर पर भाषा का विस्तार करने के लिए कार्यान्वयन आमंत्रित करता है। इनमें से कई एक्सटेंशन उन क्षेत्रों में हैं जो कड़ाई से बोलते हुए, अपरिभाषित व्यवहार हैं। उदाहरण के लिए, POSIX समारोह dlsym एक void* दिखाए, लेकिन यह फ़ंक्शन आमतौर पर एक वैश्विक चर एक समारोह के बजाय का पता लगाने के लिए प्रयोग किया जाता है। इसका मतलब है कि dlsym द्वारा लौटाए गए शून्य सूचक को फ़ंक्शन पॉइंटर पर डाला जाना चाहिए और फिर फ़ंक्शन को कॉल करने के लिए संदर्भित किया जाना चाहिए। यह स्पष्ट रूप से अपरिभाषित व्यवहार है, लेकिन फिर भी यह किसी भी POSIX अनुपालन मंच पर काम करता है। यह हार्वर्ड आर्किटेक्चर मशीन पर काम नहीं करेगा जिस पर पॉइंटर्स के कार्यों के डेटा के पॉइंटर्स की तुलना में अलग-अलग आकार होते हैं।

इसी तरह, एक अहस्ताक्षरित पूर्णांक के लिए सूचक के लिए एक float के सूचक के कास्टिंग और फिर अपसंदर्भन लगभग किसी भी संकलक के साथ लगभग किसी भी कंप्यूटर पर काम करने के लिए होता है, जिसमें कि अहस्ताक्षरित पूर्णांक के आकार और संरेखण आवश्यकताओं की तरह ही हैं एक float

ने कहा कि unsigned long का उपयोग करके आपको परेशानी हो सकती है। मेरे कंप्यूटर पर, unsigned long 64 बिट लंबा है और इसमें 64 बिट संरेखण आवश्यकताएं हैं। यह एक फ्लोट के साथ संगत नहीं है। uint32_t का उपयोग करना बेहतर होगा - मेरे कंप्यूटर पर, वह है।

typedef struct { 
    float fval; 
    uint32_t ival; 
} float_uint32_t; 

एक float_uint32_t.fval को नियत और से एक्सेस करते समय एक `` अपरिभाषित व्यवहार हुआ करता था float_uint32_t.ival`:


संघ हैक इस गंदगी के आसपास एक तरीका है। यह अब सी में कोई मामला नहीं है। कोई संकलक जिसे मैं यूनियन हैक के लिए नाक राक्षसों को मारने के बारे में जानता हूं। यह सी ++ में यूबी नहीं था। यह अवैध था। सी ++ 11 तक, एक अनुपालन सी ++ संकलक को शिकायत करने के लिए शिकायत करना पड़ा।


इस गंदगी के आसपास किसी भी और भी बेहतर तरीका %a प्रारूप है, जो 1999 के बाद से सी मानक का हिस्सा रहा है उपयोग करने के लिए है:

printf ("%a\n", fValue); 

यह सरल, आसान, पोर्टेबल है, और कोई अपरिभाषित व्यवहार का मौका। यह प्रश्न में डबल परिशुद्धता फ्लोटिंग पॉइंट मान के हेक्साडेसिमल/द्विआधारी प्रतिनिधित्व को प्रिंट करता है। चूंकि printf एक पुरातन कार्य है, सभी float तर्क पर कॉल से पहले double में परिवर्तित हो गए हैं। यह रूपांतरण सी मानक के 1 999 संस्करण के अनुसार सटीक होना चाहिए। कोई भी scanf या उसकी बहनों को कॉल के माध्यम से उस सटीक मूल्य को चुन सकता है।

+0

इस उत्तर को जोड़ने के लिए धन्यवाद, यह चीजों को और भी स्पष्ट करने में मदद करता है! खुश होती है। – bobbay

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