c - सी संकलक संरेखण पैडिंग को खत्म करने के लिए संरचना सदस्यों को पुनर्व्यवस्थित क्यों नहीं कर सकते?




struct compiler-optimization (8)

संभावित डुप्लिकेट:
जीसीसी क्यों structs अनुकूलित नहीं है?
सी ++ संरचना को कड़ा क्यों नहीं बनाता है?

32 बिट x86 मशीन पर निम्न उदाहरण पर विचार करें:

संरेखण बाधाओं के कारण, निम्नलिखित संरचना

struct s1 {
    char a;
    int b;
    char c;
    char d;
    char e;
}

सदस्यों को फिर से व्यवस्थित किए जाने पर अधिक मेमोरी-कुशलतापूर्वक (12 बनाम 8 बाइट्स) का प्रतिनिधित्व किया जा सकता है

struct s2 {
    int b;
    char a;
    char c;
    char d;
    char e;
}

मुझे पता है कि सी / सी ++ कंपाइलर्स को ऐसा करने की अनुमति नहीं है। मेरा सवाल यह है कि भाषा इस तरह से डिजाइन क्यों की गई थी। आखिरकार, हम बड़ी मात्रा में स्मृति बर्बाद कर सकते हैं, और struct_ref->b जैसे संदर्भ अंतर के बारे में परवाह नहीं करेंगे।

संपादित करें : आपके बेहद उपयोगी उत्तरों के लिए सभी को धन्यवाद। आप बहुत अच्छी तरह से समझाते हैं कि भाषा को डिज़ाइन किए जाने के तरीके के कारण पुनर्व्यवस्थित क्यों काम नहीं करता है। हालांकि, यह मुझे सोचता है: अगर पुनर्गठन भाषा का हिस्सा था तो क्या ये तर्क अभी भी होंगे? मान लें कि कुछ निर्दिष्ट पुनर्गठन नियम थे, जिनसे हमें कम से कम आवश्यकता थी

  1. यदि आवश्यक हो तो हमें केवल संरचना को फिर से व्यवस्थित करना चाहिए (यदि संरचना पहले से ही "तंग" है तो कुछ भी न करें)
  2. नियम केवल संरचना की परिभाषा को देखता है, अंदरूनी structs के अंदर नहीं। यह सुनिश्चित करता है कि एक संरचना प्रकार में एक ही लेआउट होता है चाहे वह किसी अन्य संरचना में आंतरिक हो या नहीं
  3. किसी दिए गए ढांचे का संकलित स्मृति लेआउट अनुमानित है इसकी परिभाषा (यानी, नियम तय किया गया है)

अपने तर्कों को एक-एक करके दबाकर मैं तर्क देता हूं:

  • निम्न-स्तरीय डेटा मैपिंग, "कम से कम आश्चर्य का तत्व" : बस अपने structs को एक तंग शैली में लिखें (जैसे पेरी के जवाब में) और कुछ भी नहीं बदला है (आवश्यकता 1)। यदि, कुछ अजीब कारणों के लिए, आप आंतरिक पैडिंग चाहते हैं, तो आप इसे डमी चर का उपयोग करके मैन्युअल रूप से सम्मिलित कर सकते हैं, और / या कीवर्ड / निर्देश हो सकते हैं।

  • कंपाइलर मतभेद : आवश्यकता 3 इस चिंता को समाप्त करती है। दरअसल, @ डेविड हेफ़र्नन की टिप्पणियों से, ऐसा लगता है कि आज हमें यह समस्या है क्योंकि अलग-अलग कंपाइलर्स पैड अलग-अलग हैं?

  • अनुकूलन : पुनरावृत्ति का पूरा बिंदु (स्मृति) अनुकूलन है। मैं यहां बहुत सारी संभावनाएं देखता हूं। हम सभी को एक साथ पैडिंग को हटाने में सक्षम नहीं हो सकते हैं, लेकिन मुझे नहीं लगता कि कैसे रीडरिंग किसी भी तरह से अनुकूलन को सीमित कर सकती है।

  • कास्टिंग टाइप करें : ऐसा लगता है कि यह सबसे बड़ी समस्या है। फिर भी, इसके आसपास के तरीके होना चाहिए। चूंकि नियम भाषा में तय किए गए हैं, इसलिए संकलक यह पता लगाने में सक्षम है कि सदस्यों को कैसे पुन: व्यवस्थित किया गया था, और तदनुसार प्रतिक्रिया दें। जैसा ऊपर बताया गया है, उन मामलों में पुन: समन्वय को रोकना हमेशा संभव होगा जहां आप पूर्ण नियंत्रण चाहते हैं। इसके अलावा, आवश्यकता 2 सुनिश्चित करता है कि टाइप-सुरक्षित कोड कभी नहीं टूट जाएगा।

मुझे लगता है कि इस तरह का नियम समझ सकता है क्योंकि मुझे समूह संरचना सदस्यों को उनके प्रकार के मुकाबले ज्यादा सामग्री मिलती है। इसके अलावा कंपाइलर के लिए मेरे लिए सबसे अच्छा ऑर्डरिंग करना आसान है जब मेरे पास बहुत सारे आंतरिक structs हैं। इष्टतम लेआउट भी एक ऐसा हो सकता है जिसे मैं एक प्रकार-सुरक्षित तरीके से व्यक्त नहीं कर सकता। दूसरी ओर, यह भाषा को और अधिक जटिल बनाने के लिए प्रतीत होता है, जो निश्चित रूप से एक कमी है।

ध्यान दें कि मैं भाषा बदलने के बारे में बात नहीं कर रहा हूं - केवल अगर यह (/ चाहिए) अलग-अलग डिज़ाइन किया गया हो।

मुझे पता है कि मेरा प्रश्न कल्पित है, लेकिन मुझे लगता है कि चर्चा मशीन और भाषा डिजाइन के निचले स्तरों में गहरी अंतर्दृष्टि प्रदान करती है।

मैं यहां काफी नया हूं, इसलिए मुझे नहीं पता कि मुझे इसके लिए एक नया सवाल उठाना चाहिए या नहीं। कृपया मुझे बताएं कि क्या यह मामला है।


आपका मामला बहुत विशिष्ट है क्योंकि इसे फिर से ऑर्डर करने के लिए struct के पहले तत्व की आवश्यकता होगी। यह संभव नहीं है, क्योंकि तत्व जिसे पहले struct में परिभाषित किया गया है हमेशा ऑफसेट 0 पर होना चाहिए। अगर इसकी अनुमति होगी तो बहुत से (फर्जी) कोड तोड़ देंगे।

एक ही बड़ी वस्तु के अंदर रहने वाले उपोबजेक्ट्स के अधिक आम तौर पर पॉइंटर्स हमेशा पॉइंटर तुलना की अनुमति देते हैं। मैं कल्पना कर सकता हूं कि अगर आप ऑर्डर को घुमाएंगे तो इस सुविधा का उपयोग करने वाला कुछ कोड तोड़ देगा। और उस तुलना के लिए परिभाषा के बिंदु पर कंपाइलर का ज्ञान मदद नहीं करेगा: एक उपरोक्त के सूचक के पास "चिह्न" नहीं होता है जो यह बड़ी वस्तु से संबंधित होता है। जब किसी अन्य कार्य को पारित किया जाता है, तो संभव संदर्भ की सभी जानकारी खो जाती है।


डब्लूजी 14 के सदस्य नहीं होने के नाते, मैं कुछ भी निश्चित नहीं कह सकता, लेकिन मेरे पास अपने विचार हैं:

  1. यह कम से कम आश्चर्य के सिद्धांत का उल्लंघन करेगा - एक शापित अच्छा कारण हो सकता है कि मैं अपने तत्वों को एक विशिष्ट क्रम में क्यों रखना चाहता हूं, भले ही यह सबसे अधिक अंतरिक्ष-कुशल है या नहीं, और मैं नहीं चाहता कि संकलक पुनर्व्यवस्थित हो उन तत्वों;

  2. इसमें मौजूदा कोड की एक गैर-तुच्छ राशि तोड़ने की क्षमता है - वहां बहुत सारे विरासत कोड हैं जो संरचना के पते जैसे चीजों पर निर्भर करते हैं, जो पहले सदस्य के पते के समान होते हैं (बहुत सारे क्लासिक मैकोज़ कोड जो उस धारणा को बनाया);

सी 99 तर्क सीधे दूसरे बिंदु को संबोधित करता है ("मौजूदा कोड महत्वपूर्ण है, मौजूदा कार्यान्वयन नहीं हैं") और अप्रत्यक्ष रूप से पहले ("प्रोग्रामर पर भरोसा करें") को संबोधित करते हैं।


मान लीजिए कि आपके पास हेडर है

struct s1 {
    char a;
    int b;
    char c;
    char d;
    char e;
}

और यह एक अलग पुस्तकालय का हिस्सा है (जिसमें से आप केवल एक अज्ञात कंपाइलर द्वारा संकलित संकलित बाइनरी हैं) और आप इस पुस्तकालय के साथ संवाद करने के लिए इस संरचना का उपयोग करना चाहते हैं,

यदि संकलक को सदस्यों को किसी भी तरह से पुन: व्यवस्थित करने की अनुमति दी जाती है, तो यह असंभव होगा क्योंकि क्लाइंट कंपाइलर को यह नहीं पता कि संरचना का उपयोग या अनुकूलित किया गया है (और फिर b आगे या पीछे में जाता है) या 4 बाइट अंतराल पर गठबंधन हर सदस्य के साथ भी पूरी तरह से गद्देदार

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

#pragma को जोड़ना आसान है जो आपको किसी विशिष्ट संरचना के लेआउट की आवश्यकता होने पर ऑप्टिमाइज़ेशन को प्रतिबंधित करता है, जो आपको चाहिए, ताकि कोई समस्या न हो


यदि आप सी संरचनाओं से / से बाइनरी डेटा पढ़ रहे / लिख रहे थे, तो struct सदस्यों की struct एक आपदा होगी। वास्तव में एक बफर से संरचना को पॉप्युलेट करने का कोई व्यावहारिक तरीका नहीं होगा, उदाहरण के लिए।


यहां एक कारण है जिसे मैंने अभी तक नहीं देखा - बिना मानक पुनर्गठन नियमों के, यह स्रोत फ़ाइलों के बीच संगतता को तोड़ देगा।

मान लें कि एक हेडर फ़ाइल में एक स्ट्रक्चर परिभाषित किया गया है, और दो फाइलों में उपयोग किया जाता है।
दोनों फाइलों को अलग से संकलित किया गया है, और बाद में लिंक किया गया है। संकलन अलग-अलग समय पर हो सकता है (शायद आप केवल एक को छुआ, इसलिए इसे फिर से सम्मिलित किया जाना चाहिए), संभवतः विभिन्न कंप्यूटरों पर (यदि फ़ाइलें नेटवर्क ड्राइव पर हैं) या यहां तक ​​कि विभिन्न कंपाइलर संस्करण भी हो सकते हैं।
यदि एक समय में, संकलक फिर से व्यवस्थित करने का निर्णय लेगा, और दूसरे पर यह नहीं होगा, दोनों फाइलें इस बात पर सहमत नहीं होंगी कि फ़ील्ड कहां हैं।

उदाहरण के तौर पर, stat सिस्टम कॉल और struct stat बारे में सोचें।
जब आप लिनक्स स्थापित करते हैं (उदाहरण के लिए), आपको libC मिलता है, जिसमें stat शामिल है, जिसे किसी के द्वारा कभी संकलित किया गया था।
फिर आप अपने अनुकूलक झंडे के साथ अपने कंपाइलर के साथ एक आवेदन संकलित करते हैं, और दोनों संरचना के लेआउट पर सहमत होने की उम्मीद करते हैं।


सी [और सी ++] को सिस्टम प्रोग्रामिंग भाषाओं के रूप में माना जाता है ताकि वे हार्डवेयर के लिए निम्न स्तर तक पहुंच प्रदान कर सकें, उदाहरण के लिए, पॉइंटर्स के माध्यम से स्मृति। प्रोग्रामर डेटा खंड का उपयोग कर सकते हैं और इसे एक संरचना में डाल सकते हैं और विभिन्न सदस्यों [आसानी से] तक पहुंच सकते हैं।

एक और उदाहरण नीचे की तरह एक संरचना है, जो परिवर्तनीय आकार के डेटा स्टोर करता है।

struct {
  uint32_t data_size;
  uint8_t  data[1]; // this has to be the last member
} _vv_a;

सी संकलक स्वचालित रूप से फ़ील्ड को पुन: व्यवस्थित नहीं कर सकते हैं इसके कई कारण हैं:

  • सी संकलक यह नहीं जानता कि संरचना वर्तमान संकलन इकाई से परे वस्तुओं की स्मृति संरचना का प्रतिनिधित्व करती है (उदाहरण के लिए: एक विदेशी पुस्तकालय, डिस्क पर एक फ़ाइल, नेटवर्क डेटा, सीपीयू पेज टेबल, ...)। ऐसे मामले में डेटा की बाइनरी संरचना को संकलक के लिए पहुंच योग्य स्थान पर भी परिभाषित किया जाता है, इसलिए struct फ़ील्ड को पुन: व्यवस्थित करने से डेटा प्रकार उत्पन्न होता है जो अन्य परिभाषाओं के साथ असंगत होता है। उदाहरण के लिए, ज़िप फ़ाइल में फ़ाइल के शीर्षलेख में एकाधिक-गलत 32-बिट फ़ील्ड शामिल हैं। खेतों को पुन: व्यवस्थित करने से सी कोड को सीधे हेडर पढ़ने या लिखने के लिए असंभव हो जाएगा (माना जाता है कि ज़िप कार्यान्वयन सीधे डेटा तक पहुंचना चाहता है):

    struct __attribute__((__packed__)) LocalFileHeader {
        uint32_t signature;
        uint16_t minVersion, flag, method, modTime, modDate;
        uint32_t crc32, compressedSize, uncompressedSize;
        uint16_t nameLength, extraLength;
    };
    

    packed विशेषता संकलक को उनके प्राकृतिक संरेखण के अनुसार फ़ील्ड को संरेखित करने से रोकती है, और इसका क्षेत्रीय आदेश की समस्या से कोई संबंध नहीं है। LocalFileHeader के फ़ील्ड को पुन: व्यवस्थित करना संभव होगा ताकि संरचना में न्यूनतम आकार दोनों हो और सभी फ़ील्ड अपने प्राकृतिक संरेखण में गठबंधन हो जाएं। हालांकि, कंपाइलर फ़ील्ड को पुन: व्यवस्थित करने का चयन नहीं कर सकता क्योंकि यह नहीं जानता कि संरचना वास्तव में ज़िप फ़ाइल विनिर्देश द्वारा परिभाषित की गई है।

  • सी एक असुरक्षित भाषा है। सी कंपाइलर यह नहीं जानता कि संकलक द्वारा देखे गए डेटा की तुलना में डेटा को एक अलग प्रकार के माध्यम से एक्सेस किया जाएगा, उदाहरण के लिए:

    struct S {
        char a;
        int b;
        char c;
    };
    
    struct S_head {
        char a;
    };
    
    struct S_ext {
        char a;
        int b;
        char c;
        int d;
        char e;
    };
    
    struct S s;
    struct S_head *head = (struct S_head*)&s;
    fn1(head);
    
    struct S_ext ext;
    struct S *sp = (struct S*)&ext;
    fn2(sp);
    

    यह व्यापक रूप से उपयोग किया जाने वाला निम्न-स्तरीय प्रोग्रामिंग पैटर्न है, विशेष रूप से यदि शीर्षलेख में हेडर से परे स्थित डेटा की प्रकार आईडी होती है।

  • यदि किसी अन्य प्रकार के struct प्रकार में एक struct प्रकार एम्बेड किया गया है, तो आंतरिक struct को रेखांकित करना असंभव है:

    struct S {
        char a;
        int b;
        char c, d, e;
    };
    
    struct T {
        char a;
        struct S s; // Cannot inline S into T, 's' has to be compact in memory
        char b;
    };
    

    इसका यह भी अर्थ है कि S से कुछ फ़ील्ड को एक अलग संरचना में ले जाना कुछ अनुकूलन अक्षम करता है:

    // Cannot fully optimize S
    struct BC { int b; char c; };
    struct S {
        char a;
        struct BC bc;
        char d, e;
    };
    
  • चूंकि अधिकांश सी कंपाइलर कंपाइलर्स को अनुकूलित कर रहे हैं, फिर भी संरचना क्षेत्रों को पुन: व्यवस्थित करने के लिए नए अनुकूलन लागू किए जाने की आवश्यकता होगी। यह संदिग्ध है कि क्या वे अनुकूलक प्रोग्रामर लिखने में सक्षम हैं उससे बेहतर प्रदर्शन करने में सक्षम होंगे। हाथ से डेटा संरचनाओं को डिजाइन करना अन्य कंपाइलर कार्यों जैसे पंजीकरण आवंटन, फ़ंक्शन इनलाइनिंग, निरंतर फोल्डिंग, बाइनरी खोज में स्विच स्टेटमेंट का परिवर्तन इत्यादि से बहुत कम समय लेने वाला है। इस प्रकार संकलक को डेटा संरचनाओं को अनुकूलित करने की अनुमति देकर लाभ प्राप्त किया जा सकता है। पारंपरिक कंपाइलर अनुकूलन से कम मूर्त प्रतीत होता है।


स्ट्रक्चर का उपयोग बहुत कम स्तर पर भौतिक हार्डवेयर का प्रतिनिधित्व करने के लिए किया जाता है। जैसे कि संकलक उस स्तर पर चीजों को एक दौर में स्थानांतरित नहीं कर सकता है।

हालांकि यह #pragma होने के लिए अनुचित नहीं होगा जो संकलक को पूरी तरह से स्मृति आधारित structs को पुन: व्यवस्थित करने दें जो केवल प्रोग्राम के लिए आंतरिक रूप से उपयोग किए जाते हैं। हालांकि मुझे इस तरह के एक जानवर के बारे में पता नहीं है (लेकिन इसका मतलब स्क्वाट नहीं है - मैं सी / सी ++ के संपर्क में हूं)





memory-alignment