c सख्त एलियासिंग नियम क्या है?




undefined-behavior c++-faq (8)

सी 8 9 तर्क के मुताबिक, मानक के लेखकों ने उस कंपाइलर को कोड की आवश्यकता नहीं थी:

int x;
int test(double *p)
{
  x=5;
  *p = 1.0;
  return x;
}

असाइनमेंट और रिटर्न स्टेटमेंट के बीच x के मान को फिर से लोड करने की आवश्यकता होनी चाहिए ताकि p को इंगित करने की संभावना के लिए अनुमति दी जा सके और *p के असाइनमेंट के परिणामस्वरूप x के मान को बदल दिया जा सके। धारणा है कि एक कंपाइलर को यह मानने के हकदार होना चाहिए कि ऊपर की तरह स्थितियों में अलियासिंग नहीं किया जाएगा, विवादास्पद था।

दुर्भाग्यवश, सी 8 9 के लेखकों ने अपने शासन को इस तरह लिखा था कि, यदि सचमुच पढ़ा जाता है, तो निम्न कार्य भी अनिर्धारित व्यवहार का आह्वान करेगा:

void test(void)
{
  struct S {int x;} s;
  s.x = 1;
}

क्योंकि यह प्रकार struct S ऑब्जेक्ट तक पहुंचने के लिए प्रकार के int का उपयोग करता है, और int उन प्रकारों में से एक नहीं है जिनका उपयोग struct S तक पहुंचने के लिए किया जा सकता है। चूंकि यह अनिश्चित व्यवहार के रूप में structs और यूनियनों के गैर-वर्ण-प्रकार के सदस्यों के सभी उपयोगों का इलाज करने के लिए बेतुका होगा, लगभग हर कोई यह मानता है कि कम से कम कुछ परिस्थितियां हैं जहां एक प्रकार का एक प्रकार का उपयोग किसी अन्य प्रकार की वस्तु तक पहुंचने के लिए किया जा सकता है । दुर्भाग्य से, सी मानक समिति परिभाषित करने में विफल रही है कि उन परिस्थितियों में क्या है।

अधिकांश समस्या दोष रिपोर्ट # 028 का परिणाम है, जिसने प्रोग्राम के व्यवहार के बारे में पूछा:

int test(int *ip, double *dp)
{
  *ip = 1;
  *dp = 1.23;
  return *ip;
}
int test2(void)
{
  union U { int i; double d; } u;
  return test(&u.i, &u.d);
}

दोष रिपोर्ट # 28 बताती है कि कार्यक्रम अनिर्धारित व्यवहार का आह्वान करता है क्योंकि "डबल" प्रकार के यूनियन सदस्य को लिखने और "int" प्रकार में से एक को पढ़ने की क्रिया कार्यान्वयन-परिभाषित व्यवहार का आह्वान करती है। इस तरह का तर्क गैरकानूनी है, लेकिन प्रभावी प्रकार के नियमों का आधार बनाता है जो मूल समस्या को हल करने के लिए कुछ भी नहीं करते समय भाषा को जटिल रूप से जटिल करते हैं।

मूल समस्या को हल करने का सबसे अच्छा तरीका शायद नियम के उद्देश्य के बारे में फुटनोट का इलाज करना होगा, हालांकि यह मानक था, और नियमों को छोड़कर नियम लागू नहीं किया गया था, जिसमें वास्तव में उपनामों का उपयोग करके विवादित पहुंच शामिल थी। कुछ दिया गया है:

 void inc_int(int *p) { *p = 3; }
 int test(void)
 {
   int *p;
   struct S { int x; } s;
   s.x = 1;
   p = &s.x;
   inc_int(p);
   return s.x;
 }

inc_int भीतर कोई संघर्ष नहीं है क्योंकि *p माध्यम से उपयोग किए गए संग्रहण तक पहुंच सभी प्रकार के inc_int साथ की जाती है, और test में कोई संघर्ष नहीं होता है क्योंकि p को struct S से स्पष्ट रूप से व्युत्पन्न किया जाता है, और अगली बार इसका उपयोग किया जाता है, सभी उस भंडारण तक पहुंच जो p माध्यम से कभी भी की जाएगी, पहले ही हो चुकी है।

अगर कोड थोड़ा बदल दिया गया था ...

 void inc_int(int *p) { *p = 3; }
 int test(void)
 {
   int *p;
   struct S { int x; } s;
   p = &s.x;
   s.x = 1;  //  !!*!!
   *p += 1;
   return s.x;
 }

यहां, p बीच एक अलियासिंग संघर्ष है और चिह्नित रेखा पर sx तक पहुंच है क्योंकि निष्पादन में उस बिंदु पर एक और संदर्भ मौजूद है जिसका उपयोग उसी भंडारण तक पहुंचने के लिए किया जाएगा

अगर दोष रिपोर्ट 028 ने कहा था कि मूल उदाहरण ने यूबी को दो बिंदुओं के निर्माण और उपयोग के बीच ओवरलैप के कारण बुलाया था, तो इससे "प्रभावी प्रकार" या ऐसी अन्य जटिलताओं को जोड़ने के बिना चीजों को और अधिक स्पष्ट कर दिया गया होगा।

सी में सामान्य अपरिभाषित व्यवहार के बारे में पूछते समय, आत्माओं ने सख्त अलियासिंग नियम को संदर्भित करने से अधिक प्रबुद्ध किया।
उनकी बातचीत किस बारे में हो रही है?


सख्त एलियासिंग अलग पॉइंटर प्रकारों को एक ही डेटा में अनुमति नहीं दे रहा है।

इस आलेख को पूरी तरह से इस मुद्दे को समझने में आपकी सहायता करनी चाहिए।


सख्त एलियासिंग केवल पॉइंटर्स को संदर्भित नहीं करता है, यह संदर्भों को भी प्रभावित करता है, मैंने बूस्ट डेवलपर विकी के लिए इसके बारे में एक पेपर लिखा और यह बहुत अच्छी तरह से प्राप्त हुआ कि मैंने इसे अपनी परामर्श वेबसाइट पर एक पृष्ठ में बदल दिया। यह पूरी तरह से बताता है कि यह क्या है, यह लोगों को इतना भ्रमित क्यों करता है और इसके बारे में क्या करना है। सख्त एलिसिंग व्हाइट पेपर । विशेष रूप से यह बताता है कि यूनियन सी ++ के लिए जोखिम भरा व्यवहार क्यों करते हैं, और क्यों memcpy का उपयोग सी और सी ++ दोनों में पोर्टेबल एकमात्र तय है। उम्मीद है कि यह सहायक है।


डौग टी ने पहले ही लिखा है कि परिशिष्ट के रूप में, यहां एक साधारण परीक्षण मामला है जो शायद इसे जीसीसी के साथ ट्रिगर करता है:

check.c

#include <stdio.h>

void check(short *h,long *k)
{
    *h=5;
    *k=6;
    if (*h == 5)
        printf("strict aliasing problem\n");
}

int main(void)
{
    long      k[1];
    check((short *)k,k);
    return 0;
}

gcc -O2 -o check check.c साथ संकलित करें gcc -O2 -o check check.c . gcc -O2 -o check check.c । आमतौर पर (अधिकांश जीसीसी संस्करणों के साथ मैंने कोशिश की) यह "सख्त एलियासिंग समस्या" आउटपुट करता है, क्योंकि संकलक मानता है कि "एच" "चेक" फ़ंक्शन में "के" जैसा ही पता नहीं हो सकता है। इसके कारण संकलक if (*h == 5) दूर अनुकूलित करता है और हमेशा printf को कॉल करता है।

यहां रुचि रखने वालों के लिए x64 असेंबलर कोड है, जो जीसीसी 4.6.3 द्वारा उत्पादित है, x64 के लिए उबंटू 12.04.2 पर चल रहा है:

movw    $5, (%rdi)
movq    $6, (%rsi)
movl    $.LC0, %edi
jmp puts

तो यदि स्थिति पूरी तरह से असेंबलर कोड से चली गई है।


कई उत्तरों को पढ़ने के बाद, मुझे कुछ जोड़ने की आवश्यकता महसूस होती है:

सख्त एलियासिंग (जो मैं थोड़ा सा वर्णन करूंगा) महत्वपूर्ण है क्योंकि :

  1. मेमोरी एक्सेस महंगा (प्रदर्शन के अनुसार) हो सकती है, यही कारण है कि भौतिक स्मृति पर वापस जाने से पहले सीपीयू रजिस्टरों में डेटा का उपयोग किया जाता है

  2. यदि दो अलग-अलग CPU रजिस्टरों में डेटा उसी स्मृति स्थान पर लिखा जाएगा, तो हम भविष्यवाणी नहीं कर सकते कि सी में कोड करते समय कौन सा डेटा "जीवित" रहेगा

    असेंबली में, जहां हम मैन्युअल रूप से सीपीयू रजिस्टरों की लोडिंग और अनलोडिंग कोड करते हैं, हम जानते होंगे कि कौन सा डेटा बरकरार है। लेकिन सी (शुक्र है) इस विस्तार को दूर कर देता है।

चूंकि दो पॉइंटर्स स्मृति में एक ही स्थान पर इंगित कर सकते हैं, इसके परिणामस्वरूप जटिल कोड हो सकता है जो संभव टकराव को संभालता है

यह अतिरिक्त कोड धीमा है और प्रदर्शन को नुकसान पहुंचाता है क्योंकि यह अतिरिक्त मेमोरी रीड / राइट ऑपरेशंस करता है जो धीमे और (संभवतः) दोनों अनावश्यक होते हैं।

सख्त एलियासिंग नियम हमें उन मामलों में अनावश्यक मशीन कोड से बचने की इजाजत देता है , जिनमें यह मानना ​​सुरक्षित होना चाहिए कि दो पॉइंटर्स एक ही मेमोरी ब्लॉक को इंगित नहीं करते हैं ( restrict कीवर्ड भी देखें)।

सख्त एलियासिंग कहता है कि यह मानना ​​सुरक्षित है कि पॉइंटर्स विभिन्न प्रकारों को स्मृति में विभिन्न स्थानों पर इंगित करते हैं।

यदि एक कंपाइलर नोटिस करता है कि दो पॉइंटर्स विभिन्न प्रकारों (उदाहरण के लिए, एक int * और एक float * ) इंगित करते हैं, तो यह मान लेगा कि मेमोरी एड्रेस अलग है और यह मेमोरी एड्रेस टकराव के खिलाफ सुरक्षा नहीं करेगा , जिसके परिणामस्वरूप तेज मशीन कोड होगा।

उदाहरण के लिए :

आइए निम्नलिखित फ़ंक्शन मान लें:

void merge_two_ints(int *a, int *b) {
  *b += *a;
  *a += *b;
}

उस मामले को संभालने के लिए जिसमें a == b (दोनों पॉइंटर्स एक ही मेमोरी को इंगित करते हैं), हमें स्मृति से डेटा को सीपीयू रजिस्टरों तक लोड करने के तरीके को ऑर्डर करने और परीक्षण करने की आवश्यकता होती है, इसलिए कोड इस तरह समाप्त हो सकता है:

  1. स्मृति से a और b लोड करें।

  2. b जोड़ें।

  3. b बचाओ और पुनः लोड करें

    (सीपीयू रजिस्टर से मेमोरी में सहेजें और मेमोरी से सीपीयू रजिस्टर में लोड करें)।

  4. b जोड़ें।

  5. a (सीपीयू रजिस्टर से) स्मृति में सहेजें।

चरण 3 बहुत धीमा है क्योंकि इसे भौतिक स्मृति तक पहुंचने की आवश्यकता है। हालांकि, उन मामलों के खिलाफ सुरक्षा की आवश्यकता है जहां a और b उसी स्मृति पते पर इंगित करें।

सख्त एलियासिंग हमें संकलक को यह बताकर रोकने के लिए अनुमति देगा कि ये मेमोरी पते अलग-अलग हैं (जो, इस मामले में, और भी अनुकूलन की अनुमति देगा जो पॉइंटर्स मेमोरी एड्रेस साझा करते हैं)।

  1. विभिन्न प्रकारों का उपयोग करके इंगित करने के लिए इसे दो तरीकों से कंपाइलर को बताया जा सकता है। अर्थात:

    void merge_two_numbers(int *a, long *b) {...}
    
  2. restrict कीवर्ड का उपयोग करना। अर्थात:

    void merge_two_ints(int * restrict a, int * restrict b) {...}
    

अब, सख्त एलिसिंग नियम को संतुष्ट करके, चरण 3 से बचा जा सकता है और कोड काफी तेजी से चलाएगा।

वास्तव में, restrict कीवर्ड जोड़कर, संपूर्ण फ़ंक्शन को अनुकूलित किया जा सकता है:

  1. स्मृति से a और b लोड करें।

  2. b जोड़ें।

  3. दोनों को a से b तक परिणाम बचाएं।

संभवतः टकराव की वजह से यह अनुकूलन पहले नहीं किया जा सका (जहां a और b को दोगुना करने के बजाय तीन गुना किया जाएगा)।


मैंने पाया है कि सबसे अच्छा स्पष्टीकरण माइक एक्टन द्वारा है, सख्त एलिसिंग को समझना । यह पीएस 3 विकास पर थोड़ा ध्यान केंद्रित कर रहा है, लेकिन यह मूल रूप से सिर्फ जीसीसी है।

लेख से:

"सख्त एलियासिंग एक धारणा है, जिसे सी (या सी ++) कंपाइलर द्वारा बनाया गया है, कि अलग-अलग प्रकार की वस्तुओं के लिए डिफरेंसिंग पॉइंटर्स कभी भी उसी स्मृति स्थान (यानी एक दूसरे को उर्फ ​​नहीं) देखेंगे।"

तो मूल रूप से यदि आपके पास int* कुछ स्मृति की ओर इशारा करते हुए एक int* और फिर आप उस float* को उस स्मृति पर इंगित करते हैं और इसे float रूप में उपयोग करते हैं तो आप नियम तोड़ते हैं। यदि आपका कोड इसका सम्मान नहीं करता है, तो कंपाइलर का ऑप्टिमाइज़र आपके कोड को तोड़ने की संभावना है।

नियम का अपवाद एक char* , जिसे किसी भी प्रकार को इंगित करने की अनुमति है।


यह सख्त एलियासिंग नियम है, जो सी ++ 03 मानक की धारा 3.10 में पाया गया है (अन्य उत्तरों अच्छे स्पष्टीकरण प्रदान करते हैं, लेकिन कोई भी नियम स्वयं प्रदान नहीं करता है):

यदि कोई प्रोग्राम किसी ऑब्जेक्ट के संग्रहीत मूल्य को निम्न प्रकारों में से किसी एक के आभासी के माध्यम से एक्सेस करने का प्रयास करता है तो व्यवहार अपरिभाषित होता है:

  • वस्तु का गतिशील प्रकार,
  • वस्तु के गतिशील प्रकार का एक सीवी-योग्य संस्करण,
  • एक प्रकार जो ऑब्जेक्ट के गतिशील प्रकार से संबंधित हस्ताक्षरित या हस्ताक्षरित प्रकार है,
  • एक प्रकार जो ऑब्जेक्ट के गतिशील प्रकार के सीवी-योग्य संस्करण के अनुरूप हस्ताक्षरित या हस्ताक्षरित प्रकार है,
  • एक कुल या संघीय प्रकार जिसमें इसके सदस्यों के बीच उपर्युक्त प्रकारों में से एक शामिल है (समवर्ती रूप से, एक उपनिवेश या निहित संघ का सदस्य),
  • एक प्रकार जो ऑब्जेक्ट के गतिशील प्रकार के (संभवतः सीवी-योग्य) बेस क्लास प्रकार है,
  • एक char या unsigned char प्रकार।

सी ++ 11 और सी ++ 14 शब्द (परिवर्तन पर बल दिया गया):

यदि कोई प्रोग्राम निम्न प्रकारों में से किसी एक के अलावा किसी ऑब्जेक्ट के संग्रहीत मूल्य तक पहुंचने का प्रयास करता है तो व्यवहार अपरिभाषित होता है:

  • वस्तु का गतिशील प्रकार,
  • वस्तु के गतिशील प्रकार का एक सीवी-योग्य संस्करण,
  • वस्तु के गतिशील प्रकार के लिए एक प्रकार (जैसा कि 4.4 में परिभाषित किया गया है)
  • एक प्रकार जो ऑब्जेक्ट के गतिशील प्रकार से संबंधित हस्ताक्षरित या हस्ताक्षरित प्रकार है,
  • एक प्रकार जो ऑब्जेक्ट के गतिशील प्रकार के सीवी-योग्य संस्करण के अनुरूप हस्ताक्षरित या हस्ताक्षरित प्रकार है,
  • एक समेकित या संघीय प्रकार जिसमें उपरोक्त प्रकारों में से एक तत्व या गैर स्थैतिक डेटा सदस्यों (एक पुनरावर्ती, एक तत्व या गैर-स्थैतिक डेटा सदस्य या उपनिवेश संघ) शामिल है,
  • एक प्रकार जो ऑब्जेक्ट के गतिशील प्रकार के (संभवतः सीवी-योग्य) बेस क्लास प्रकार है,
  • एक char या unsigned char प्रकार।

दो परिवर्तन छोटे थे: अंतराल के बजाय glvalue , और कुल / संघ मामले के स्पष्टीकरण।

तीसरा परिवर्तन एक मजबूत गारंटी बनाता है (मजबूत एलियासिंग नियम को आराम देता है): समान प्रकार की नई अवधारणा जो अब उपनाम के लिए सुरक्षित है।

इसके अलावा सी वर्डिंग (सी 99; आईएसओ / आईईसी 98 99: 1 999 6.5 / 7; सटीक वही शब्द आईएसओ / आईईसी 98 99: 2011 §6.5 ¶7 में उपयोग किया जाता है):

किसी ऑब्जेक्ट में इसके संग्रहीत मूल्य को केवल एक आभासी अभिव्यक्ति द्वारा उपयोग किया जाएगा जिसमें निम्न में से एक प्रकार 73 है) या 88) :

  • वस्तु के प्रभावी प्रकार के साथ संगत एक प्रकार,
  • ऑब्जेक्ट के प्रभावी प्रकार के साथ संगत प्रकार के योग्यता संस्करण,
  • एक प्रकार जो ऑब्जेक्ट के प्रभावी प्रकार से संबंधित हस्ताक्षरित या हस्ताक्षरित प्रकार है,
  • एक प्रकार जो ऑब्जेक्ट के प्रभावी प्रकार के क्वालीफ संस्करण के अनुरूप हस्ताक्षरित या हस्ताक्षरित प्रकार है,
  • एक कुल या संघीय प्रकार जिसमें इसके सदस्यों के बीच उपर्युक्त प्रकारों में से एक शामिल है (जिसमें, संक्षेप में, उपसमूह या निहित संघ का सदस्य शामिल है), या
  • एक चरित्र प्रकार।

73) या 88) इस सूची का उद्देश्य उन परिस्थितियों को निर्दिष्ट करना है जिनमें कोई ऑब्जेक्ट एलियाड किया जा सकता है या नहीं।


पॉइंटर कास्ट (एक संघ का उपयोग करने के विपरीत) के माध्यम से पनिंग टाइप करें सख्त एलियासिंग तोड़ने का एक प्रमुख उदाहरण है।





type-punning