2011-08-18 8 views
23

मैंने इस सहयोगी को एक सहयोगी के साथ किया था, और यह दिलचस्प साबित हुआ। हम निम्नलिखित पॉड वर्ग है कहनाइस पीओडी संरचना को बेस क्लास के रूप में उपयोग करना खतरनाक क्यों हो सकता है?

struct A { 
    void clear() { memset(this, 0, sizeof(A)); } 

    int age; 
    char type; 
}; 

clear सभी सदस्यों स्पष्ट करने के लिए, 0 (बाइट वार) के लिए सेटिंग का इरादा है। यदि हम बेस क्लास के रूप में A का उपयोग करते हैं तो क्या गलत हो सकता है? यहाँ बग के लिए एक सूक्ष्म स्रोत है।

+0

मुझे आशा है कि आप हमें अपना उत्तर, जोहान्स प्रदान करेंगे। –

+0

@Fred उत्तर पहले से ही नीचे दिया गया है। इसलिए मुझे "खुलासा" करने की कोई ज़रूरत नहीं है। –

उत्तर

17

कंपाइलर ए के लिए पैडिंग बाइट जोड़ने की संभावना है इसलिए sizeof(A)char type (पैडिंग के अंत तक) तक फैला हुआ है। हालांकि विरासत के मामले में संकलक गद्देदार बाइट्स नहीं जोड़ सकता है। तो memset पर कॉल उपclass के हिस्से को ओवरराइट करेगा।

+0

@ लचियन मैं आपकी बात समझ नहीं पा रहा हूं? मुझे लगता है कि 'sizeof (int) + sizeof (char) = 5' जो 32-बिट आर्किटेक्चर पर गठबंधन नहीं है .. – StackedCrooked

+0

@Luchian: आपको लगता है कि' sizeof (ए) 'क्या होगा? जवाब आपको आश्चर्य में डाल सकता है। –

+0

@ फ्रेड लार्सन आकार (ए) अपने प्लेटफ़ॉर्म पर आकार (int) पर निर्भर करता है। और फिर, इस मामले में, यह आकार (int) + sizeof (char) है। यदि सदस्य आदेश उलटा हुआ था, तो यह आकार (char) + int_padding - 1 + sizeof (int) होगा। –

4

अन्य नोट्स के अलावा, sizeof एक संकलन-समय ऑपरेटर है, इसलिए clear() व्युत्पन्न कक्षाओं द्वारा जोड़े गए किसी भी सदस्य को शून्य नहीं करेगा (जैसा कि पैडिंग अजीबता के कारण उल्लेख किया गया है)।

इस बारे में वास्तव में "सूक्ष्म" कुछ भी नहीं है; memset सी ++ में उपयोग करने के लिए एक भयानक बात है। दुर्लभ मामलों में जहां आप वास्तव में शून्य के साथ मेमोरी भर सकते हैं और सन व्यवहार की उम्मीद कर सकते हैं, और आपको वास्तव में शून्य, और के साथ मेमोरी भरने की आवश्यकता है प्रारंभिक सूची के माध्यम से सबकुछ प्रारंभ करना सभ्य तरीका किसी भी तरह अस्वीकार्य है, उपयोग इसके बजाय std::fill

+0

I यह नहीं देखते कि 'std :: fill' इस विशेष मामले में कैसे मदद करेगा - आप किस इटरेटर का उपयोग करेंगे? – Voo

+0

'char * start = reinterpret_cast (यह); char * end = start + sizeof (this); std :: भरें (शुरू करें, अंत, 0); 'मुझे लगता है कि बेवकूफ अनुवाद होगा, लेकिन यह वास्तव में एक ही समस्या है। ईमानदारी से मैं संभवतः केवल प्राचीन प्रकार के लिए ऑटो-शून्य-प्रारंभिक रैपर बनाउंगा और इसके बजाय उनके द्वारा structs लिखना होगा। लेकिन मुद्दा यह है कि 'std :: fill' स्मृति भरने के लिए एक अधिक परिष्कृत उपकरण है और हमें सभ्य कंपाइलर के साथ कुछ भी लागत नहीं है, ऐसे मामलों में जहां सादा पुराना' स्मृति 'काम करेगा। –

+0

@ करल: "* ईमानदारी से मैं संभवत: केवल आदिम प्रकारों के लिए ऑटो-शून्य-प्रारंभिक रैपर बनाउंगा *" देखें [बूस्ट.वैल्यूइनिलाइज्ड] (http://www.boost.org/doc/libs/release/libs/utility /value_init.htm)। – ildjarn

2

सिद्धांत रूप में, कंपाइलर आधार वर्गों को अलग-अलग रख सकता है। सी ++ 03 §10 अनुच्छेद 5 कहते हैं:

एक आधार वर्ग subobject एक लेआउट (3.7) एक ही प्रकार के एक सबसे व्युत्पन्न वस्तु के लेआउट से अलग हो सकता है।

StackedCrooked mentioned रूप में, यह संकलक आधार वर्ग A के अंत तक गद्दी जोड़ने जब यह अपने आप ही वस्तु के रूप में मौजूद है द्वारा हो सकता है, लेकिन जब यह एक आधार वर्ग है संकलक कि गद्दी नहीं जोड़ सकते हैं। इससे उप-वर्ग के सदस्यों के पहले कुछ बाइट्स को ओवरराइट करने के लिए A::clear() का कारण होगा।

लेकिन व्यवहार में, मैं इस या तो जीसीसी या विजुअल स्टूडियो 2008 इस परीक्षण का उपयोग के साथ होने की प्राप्त करने में सक्षम नहीं किया गया है:

struct A 
{ 
    void clear() { memset(this, 0, sizeof(A)); } 

    int age; 
    char type; 
}; 

struct B : public A 
{ 
    char x; 
}; 

int main(void) 
{ 
    B b; 
    printf("%d %d %d\n", sizeof(A), sizeof(B), ((char*)&b.x - (char*)&b)); 
    b.x = 3; 
    b.clear(); 
    printf("%d\n", b.x); 

    return 0; 
} 

और संशोधित A, B, या दोनों 'पैक' होने के लिए (वीएस में #pragma pack और जीसीसी में __attribute__((packed)) के साथ), मुझे किसी भी मामले में अधिलेखित होने के लिए b.x नहीं मिला। अनुकूलन सक्षम थे। आकार/ऑफसेट के लिए मुद्रित 3 मान हमेशा 8/12/8, 8/9/8, या 5/6/5 थे।

+1

यहां मेरा टेस्टकेस है। आधार हालांकि पीओडी नहीं है: http://ideone.com/GHATf। प्रारंभ में मैंने यह नहीं देखा कि जीसीसी पीओडी के लिए अलग व्यवहार करेगा, इसलिए मैंने इस सवाल को इस तरह से कहा कि इस मुद्दे को मजबूत बना दिया गया है। लेकिन जैसा कि आप कहते हैं, संभावना अभी भी मौजूद है। और मैं शर्त लगाता हूं कि बहुत से लोग सोचेंगे कि एक मूर्ख 'निजी' का कोई प्रभाव नहीं पड़ता है। 3.9 एमपी 3 और 3.9 पी 2 को नोट करने के लायक भी है, जहां इसमें टिप्पणियां शामिल हैं "... जहां न तो obj1 और न ही obj2 एक बेस-क्लास सबोबजेक्ट है ..." विभिन्न लेआउटिंग का समर्थन करने के लिए। –

0

clear बेस क्लास की विधि केवल वर्ग के सदस्यों के मान निर्धारित करेगी।

संरेखण नियमों के अनुसार, कंपाइलर को पैडिंग डालने की अनुमति है ताकि अगला डेटा सदस्य गठबंधन सीमा पर हो। इस प्रकार type डेटा सदस्य के बाद पैडिंग होगी। वंशज के पहले डेटा सदस्य इस स्लॉट पर कब्जा करेंगे और memset के प्रभाव से मुक्त होंगे, क्योंकि sizeof बेस क्लास में वंश का आकार शामिल नहीं है। माता-पिता का आकार! = बच्चे का आकार (जब तक कि बच्चे के पास कोई डेटा सदस्य न हो)। स्लाइसिंग देखें।

संरचनाओं का पैकिंग भाषा मानक का हिस्सा नहीं है। उम्मीद है कि, एक अच्छे कंपाइलर के साथ, पैक किए गए ढांचे के आकार में अंतिम के बाद कोई अतिरिक्त बाइट शामिल नहीं होता है। फिर भी, एक पैक किए गए माता-पिता से विरासत में प्राप्त एक पैक किए गए वंशज को एक ही परिणाम देना चाहिए: माता-पिता केवल माता-पिता में डेटा सदस्य सेट करते हैं।

0

संक्षेप: मुझे ऐसा लगता है कि केवल एक ही potentional समस्या यह है कि मैं के बारे में "पैडिंग बाइट्स" C89 में की गारंटी देता है किसी भी जानकारी नहीं मिली सकते में है, C2003 standarts .... वे कुछ असाधारण अस्थिर हो या केवल पढ़ने है व्यवहार - मैं भी नहीं मिल रहा है क्या करता है यह शब्द "पैडिंग बाइट्स" standarts मतलब ...

विस्तृत: पॉड प्रकार यह सी ++ 2003 मानक है कि द्वारा की गारंटी है की वस्तुओं के लिए

:

  • जब आप अपने ऑब्जेक्ट की सामग्री को char या unsigned char की सरणी में याद करते हैं, और उसके बाद सामग्री को वापस अपने ऑब्जेक्ट में memcpy करते हैं, तो ऑब्जेक्ट अपना मूल मान
  • गारंटी देता है कि शुरुआत में कोई पैडिंग नहीं होगी एक पॉड वस्तु की

  • के बारे में सी ++ नियमों को तोड़ने के कर सकते हैं: गोटो बयान, जीवन भर

C89 के लिए वहाँ भी कुछ की गारंटी देता है संरचना के बारे में मौजूद है:

  • जब संघ संरचनाओं का एक मिश्रण के लिए इस्तेमाल किया है, तो structs ही शुरुआत है, तो पहले compoments सही mathing

  • sizeof संरचना है सी में स्मृति की मात्रा सभी घटकों को स्टोर करने के लिए, के तहत जगह के बराबर है घटकों के बीच पैडिंग, निम्नलिखित संरचनाओं के तहत पैडिंग रखें

  • संरचना के सी घटकों में पते दिए गए हैं। एक गारंटी है कि पते के घटक आरोही क्रम में हैं। और पहले घटक का पता संरचना के प्रारंभ पते के साथ मेल खाता है। जहां कार्यक्रम चलाता है

तो मुझे ऐसा लगता है कि इस तरह के नियम C++ के लिए उपयुक्त है भी परवाह किए बिना, जिनमें से कंप्यूटर एन्डियन, और सब ठीक है। मुझे सच में लगता है कि हार्डवेयर स्तर में कोई भी आपको गैर-कॉन्स्ट ऑब्जेक्ट के लिए पैडिंग बाइट्स में लिखने से प्रतिबंधित नहीं करेगा।

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