c - ड्रेफ्रेंसिंग प्रकार-दंडित सूचक सख्त-एलियासिंग नियमों को तोड़ देगा




optimization gcc (5)

असल में आप जीसीसी के संदेश को उस लड़के के रूप में पढ़ सकते हैं जिसे आप परेशानी की तलाश में हैं, ऐसा न कहें कि मैंने आपको चेतावनी नहीं दी है

एक तीन बाइट वर्ण सरणी को एक int में कास्टिंग करना सबसे खराब चीजों में से एक है जिसे मैंने कभी देखा है। आम तौर पर आपके int में कम से कम 4 बाइट होते हैं। तो चौथे के लिए (और अधिक int अगर व्यापक है) तो आपको यादृच्छिक डेटा मिलता है। और फिर आप इसे सब एक double डाल दिया।

बस उसमें से कोई भी नहीं। एलआईसीई समस्या जो जीसीसी के बारे में चेतावनी देती है वह आप जो कर रहे हैं उसके मुकाबले निर्दोष है।

मैंने बड़े कार्यक्रम के हिस्से के रूप में फाइलों से डेटा पढ़ने के लिए कोड के निम्नलिखित भाग का उपयोग किया।

double data_read(FILE *stream,int code) {
        char data[8];
        switch(code) {
        case 0x08:
            return (unsigned char)fgetc(stream);
        case 0x09:
            return (signed char)fgetc(stream);
        case 0x0b:
            data[1] = fgetc(stream);
            data[0] = fgetc(stream);
            return *(short*)data;
        case 0x0c:
            for(int i=3;i>=0;i--)
                data[i] = fgetc(stream);
            return *(int*)data;
        case 0x0d:
            for(int i=3;i>=0;i--)
                data[i] = fgetc(stream);
            return *(float*)data;
        case 0x0e:
            for(int i=7;i>=0;i--)
                data[i] = fgetc(stream);
            return *(double*)data;
        }
        die("data read failed");
        return 1;
    }

अब मुझे warning: dereferencing type-punned pointer will break strict-aliasing rules का उपयोग करने के लिए कहा गया है और मुझे निम्नलिखित gcc चेतावनी मिलती है: warning: dereferencing type-punned pointer will break strict-aliasing rules

Googleing मुझे दो ऑर्थोगोनल उत्तर मिले:

बनाम

अंत में मैं चेतावनियों को अनदेखा नहीं करना चाहता हूं। आप क्या सुझाव देंगे?

[अपडेट] मैंने वास्तविक समारोह के साथ खिलौना उदाहरण को प्रतिस्थापित किया।


ऐसा लगता है कि आप वास्तव में फ़्रेड का उपयोग करना चाहते हैं:

int data;
fread(&data, sizeof(data), 1, stream);

उस ने कहा, यदि आप वर्ण पढ़ने के मार्ग पर जाना चाहते हैं, तो उन्हें एक int के रूप में दोबारा परिभाषित करना, सी (लेकिन सी ++ में नहीं ) में इसे करने का सुरक्षित तरीका संघ का उपयोग करना है:

union
{
    char theChars[4];
    int theInt;
} myunion;

for(int i=0; i<4; i++)
    myunion.theChars[i] = fgetc(stream);
return myunion.theInt;

मुझे यकीन नहीं है कि आपके मूल कोड में data की लंबाई क्यों है 3. मुझे लगता है कि आप 4 बाइट चाहते थे; कम से कम मुझे किसी भी सिस्टम के बारे में पता नहीं है जहां एक int 3 बाइट्स है।

ध्यान दें कि आपका कोड और मेरा दोनों गैर-पोर्टेबल हैं।

संपादित करें: यदि आप फ़ाइल से विभिन्न लंबाई की चींटियों को पढ़ना चाहते हैं, तो पोर्टेबल रूप से, इस तरह कुछ कोशिश करें:

unsigned result=0;
for(int i=0; i<4; i++)
    result = (result << 8) | fgetc(stream);

(नोट: एक वास्तविक कार्यक्रम में, आप अतिरिक्त रूप से ईएफओ के खिलाफ fgetc () के वापसी मूल्य का परीक्षण करना चाहते हैं।)

यह सिस्टम के अंतहीनता के बावजूद , थोड़ा-एंडियन प्रारूप में फ़ाइल से 4-बाइट को बिना हस्ताक्षरित पढ़ता है। यह किसी भी सिस्टम पर काम करना चाहिए जहां एक हस्ताक्षरित कम से कम 4 बाइट्स है।

यदि आप एंडियन-तटस्थ होना चाहते हैं, तो पॉइंटर्स या यूनियनों का उपयोग न करें; इसके बजाय बिट-शिफ्ट का उपयोग करें।


संघ का उपयोग करना यहां करने के लिए सही बात नहीं है। यूनियन के एक अवांछित सदस्य से पढ़ना अनिर्धारित है - यानी संकलक अनुकूलन करने के लिए स्वतंत्र है जो आपके कोड को तोड़ देगा (जैसे लिखने को अनुकूलित करना)।


समस्या तब होती है क्योंकि आप double* माध्यम से एक char-array का उपयोग करते हैं:

char data[8];
...
return *(double*)data;

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

यदि संकलक जानता है कि आपका *(double*) किसी भी तरह से data[] साथ ओवरलैप नहीं कर सकता है data[] , यह आपके कोड को पुन: व्यवस्थित करने जैसी सभी प्रकार की चीजों की अनुमति है:

return *(double*)data;
for(int i=7;i>=0;i--)
    data[i] = fgetc(stream);

लूप सबसे अधिक संभावना अनुकूलित है और आप बस के साथ समाप्त होता है:

return *(double*)data;

जो आपके डेटा को छोड़ देता है [] अनियमित। इस विशेष मामले में संकलक यह देख सकता है कि आपके पॉइंटर्स ओवरलैप हो जाते हैं, लेकिन अगर आपने इसे char* data घोषित कर दिया है, तो यह बग दे सकता था।

लेकिन, सख्त-एलियासिंग नियम कहता है कि एक char * और शून्य * किसी भी प्रकार पर इंगित कर सकता है। तो आप इसे फिर से लिख सकते हैं:

double data;
...
*(((char*)&data) + i) = fgetc(stream);
...
return data;

सख्त एलियासिंग चेतावनियां समझने या ठीक करने के लिए वास्तव में महत्वपूर्ण हैं। वे ऐसे प्रकार की बग का कारण बनते हैं जो घर में पुन: उत्पन्न करना असंभव हैं क्योंकि वे केवल एक विशेष मशीन पर एक विशेष ऑपरेटिंग सिस्टम पर और एक विशेष मशीन पर और केवल एक वर्ष में एक विशेष कंपाइलर पर होते हैं।


स्पष्ट रूप से मानक आकार (int *) को आकार (int *) से भिन्न होने की अनुमति देता है, इसलिए जब आप सीधे कलाकार का प्रयास करते हैं तो जीसीसी शिकायत करता है। शून्य * एक छोटा सा विशेष है जिसमें सबकुछ शून्य से आगे और आगे परिवर्तित किया जा सकता है। प्रैक्टिस में मुझे कई आर्किटेक्चर / कंपाइलर नहीं पता हैं, जहां एक पॉइंटर हमेशा सभी प्रकार के लिए समान नहीं होता है, लेकिन जीसीसी एक चेतावनी को छोड़ने का अधिकार है, भले ही यह परेशान हो।

मुझे लगता है कि सुरक्षित तरीका होगा

int i, *p = &i;
char *q = (char*)&p[0];

या

char *q = (char*)(void*)p;

आप यह भी कोशिश कर सकते हैं और देख सकते हैं कि आपको क्या मिलता है:

char *q = reinterpret_cast<char*>(p);




strict-aliasing