सी में सामान्य अपरिभाषित व्यवहार के बारे में पूछने पर , लोग कभी-कभी सख्त अलियासिंग नियम का उल्लेख करते हैं।
उनकी बातचीत किस बारे में हो रही है?
सी में सामान्य अपरिभाषित व्यवहार के बारे में पूछने पर , लोग कभी-कभी सख्त अलियासिंग नियम का उल्लेख करते हैं।
उनकी बातचीत किस बारे में हो रही है?
जवाबों:
एक विशिष्ट स्थिति जहां आप सख्त अलियासिंग समस्याओं का सामना करते हैं, जब आपके सिस्टम के शब्द आकार के बफर पर एक संरचना (एक उपकरण / नेटवर्क संदेश की तरह) का ओवरलेइंग होता है (जैसे एक संकेतक जैसे uint32_t
s या uint16_t
s)। जब आप ऐसे बफर पर एक संरचना को ओवरले करते हैं, या पॉइंटर कास्टिंग के माध्यम से ऐसी संरचना पर एक बफर आप आसानी से सख्त अलियासिंग नियमों का उल्लंघन कर सकते हैं।
इसलिए इस तरह के सेटअप में, अगर मैं कुछ संदेश भेजना चाहता हूं, तो मुझे दो असंगत बिंदुओं को एक ही समय में याद रखना होगा। मैं तब कुछ इस तरह कोड कर सकता हूं (सिस्टम के साथ sizeof(int) == 2
):
typedef struct Msg
{
unsigned int a;
unsigned int b;
} Msg;
void SendWord(uint32_t);
int main(void)
{
// Get a 32-bit buffer from the system
uint32_t* buff = malloc(sizeof(Msg));
// Alias that buffer through message
Msg* msg = (Msg*)(buff);
// Send a bunch of messages
for (int i =0; i < 10; ++i)
{
msg->a = i;
msg->b = i+1;
SendWord(buff[0]);
SendWord(buff[1]);
}
}
सख्त अलियासिंग नियम इस सेटअप को गैरकानूनी बनाता है: एक पॉइंटर को डीरेफेरेंस करते हुए कि एलियास एक ऐसी वस्तु है जो एक संगत प्रकार की नहीं है या सी 2011 6.5 पैरा 7 1 द्वारा अनुमत अन्य प्रकारों में से एक अपरिभाषित व्यवहार है। दुर्भाग्य से, आप अभी भी इस तरह से कोड कर सकते हैं, हो सकता है कि कुछ चेतावनी प्राप्त करें, क्या यह ठीक संकलन है, केवल अजीब अप्रत्याशित व्यवहार करने के लिए जब आप कोड चलाते हैं।
(जीसीसी अलियासिंग चेतावनी देने की अपनी क्षमता में कुछ असंगत प्रतीत होता है, कभी-कभी हमें एक दोस्ताना चेतावनी देता है और कभी-कभी नहीं।)
यह देखने के लिए कि यह व्यवहार अपरिभाषित क्यों है, हमें यह सोचना होगा कि सख्त अलियासिंग नियम संकलक को क्या खरीदता है। मूल रूप से, इस नियम के साथ, buff
लूप के प्रत्येक रन की सामग्री को ताज़ा करने के लिए निर्देश डालने के बारे में सोचना नहीं पड़ता है । इसके बजाय, जब अनुकूलन करते हैं, तो एलियासिंग के बारे में कुछ कष्टप्रद धारणाओं के साथ, यह उन निर्देशों, भार buff[0]
और buff[1
] को लूप चलाने से पहले एक बार सीपीयू रजिस्टरों में छोड़ सकता है, और लूप के शरीर को गति दे सकता है। सख्ती से पेश आने से पहले, कंपाइलर को व्यामोह की स्थिति में रहना पड़ता था कि सामग्री buff
किसी भी समय कहीं से भी बदल सकती थी। इसलिए एक अतिरिक्त प्रदर्शन बढ़त हासिल करने के लिए, और ज्यादातर लोगों को लगता है कि टाइप-पॉइंट पॉइंटर्स नहीं हैं, सख्त अलियासिंग नियम पेश किया गया था।
ध्यान रखें, यदि आपको लगता है कि उदाहरण से वंचित हैं, तो यह तब भी हो सकता है जब आप किसी अन्य फ़ंक्शन के लिए एक बफर पास कर रहे हों, जो आपके लिए भेज रहा है, यदि आपके पास है।
void SendMessage(uint32_t* buff, size_t size32)
{
for (int i = 0; i < size32; ++i)
{
SendWord(buff[i]);
}
}
और इस सुविधाजनक फ़ंक्शन का लाभ उठाने के लिए हमारे पहले के लूप को फिर से लिखा
for (int i = 0; i < 10; ++i)
{
msg->a = i;
msg->b = i+1;
SendMessage(buff, 2);
}
कंपाइलर SendMessage को इनलाइन करने की कोशिश करने में सक्षम या स्मार्ट नहीं हो सकता है और यह बफ़र को फिर से लोड करने या न करने का निर्णय ले सकता है या नहीं भी कर सकता है। यदि SendMessage
किसी अन्य एपीआई का हिस्सा है जिसे अलग से संकलित किया गया है, तो संभवतः उसके पास बफ़र्स की सामग्री लोड करने के निर्देश हैं। तो फिर, शायद आप C ++ में हैं और यह कुछ टेम्प्लेटेड हेडर है जो केवल कार्यान्वयन के लिए है जो कंपाइलर को लगता है कि यह इनलाइन कर सकता है। या हो सकता है कि यह केवल कुछ है जो आपने अपनी .c फ़ाइल में अपनी सुविधा के लिए लिखा है। वैसे भी अपरिभाषित व्यवहार अभी भी सुनिश्चित हो सकता है। यहां तक कि जब हम जानते हैं कि हुड के तहत क्या हो रहा है, यह अभी भी नियम का उल्लंघन है, इसलिए कोई भी अच्छी तरह से परिभाषित व्यवहार की गारंटी नहीं है। तो बस एक फ़ंक्शन में लपेटकर जो हमारे शब्द को सीमांकित बफर लेता है वह जरूरी मदद नहीं करता है।
तो मैं इसके आसपास कैसे पहुंचूं?
एक संघ का उपयोग करें। अधिकांश कंपाइलर सख्त अलियासिंग की शिकायत किए बिना इसका समर्थन करते हैं। इसे C99 में अनुमति दी गई है और C11 में स्पष्ट रूप से अनुमति दी गई है।
union {
Msg msg;
unsigned int asBuffer[sizeof(Msg)/sizeof(unsigned int)];
};
आप अपने कंपाइलर में सख्त अलियासिंग को निष्क्रिय कर सकते हैं ( f [no-] सख्त- aliasing in gcc))
आप char*
अपने सिस्टम के शब्द के बजाय अलियासिंग के लिए उपयोग कर सकते हैं । नियम char*
(सहित signed char
और unsigned char
) के लिए एक अपवाद की अनुमति देते हैं । यह हमेशा माना जाता है कि char*
अन्य प्रकार के उपनाम। हालांकि यह दूसरे तरीके से काम नहीं करेगा: इस बात की कोई धारणा नहीं है कि आपकी संरचना अलायस चार्ज़ का बफर है।
शुरुआत करने वाले सावधान रहें
यह केवल एक ही संभावित माइनफील्ड है जब एक दूसरे पर दो प्रकार से ओवरलेइंग होती है। आपको धीरज , शब्द संरेखण , और सही ढंग से पैकिंग संरचनाओं के माध्यम से संरेखण मुद्दों से कैसे निपटना चाहिए, इसके बारे में भी सीखना चाहिए ।
1 सी 2011 6.5 7 प्रकार के उपयोग की अनुमति देता है जो निम्न हैं:
unsigned char*
दूर इस्तेमाल किया जा सकता है char*
? मैं अंतर्निहित प्रकार के unsigned char
बजाय उपयोग करना चाहता हूं क्योंकि मेरे बाइट्स पर हस्ताक्षर नहीं किए गए हैं और मैं हस्ताक्षरित व्यवहार की अजीबता नहीं चाहता (विशेष रूप से अतिप्रवाह करने के लिए wrt)char
byte
unsigned char *
करना ठीक है।
uint32_t* buff = malloc(sizeof(Msg));
और बाद की यूनियन unsigned int asBuffer[sizeof(Msg)];
बफर घोषणाओं के अलग-अलग आकार होंगे और न ही सही है। malloc
कॉल हुड के नीचे 4 बाइट संरेखण पर निर्भर है (यह क्या करते हो नहीं) और संघ 4 बार बड़ा की तुलना में यह करने की जरूरत हो जाएगा ... मैं समझता हूँ कि यह स्पष्टता के लिए है, लेकिन यह कीड़े मुझे कोई-मिलने वाली कम ...
माइक एक्टन, अंडरस्टैंडिंग स्ट्रिक्ट अलायसिंग द्वारा मुझे सबसे अच्छी व्याख्या मिली है । यह PS3 विकास पर थोड़ा केंद्रित है, लेकिन यह मूल रूप से सिर्फ जीसीसी है।
लेख से:
"सख्त अलियासिंग एक धारणा है, जो सी (या सी ++) कंपाइलर द्वारा बनाई गई है, जो विभिन्न प्रकारों की वस्तुओं के लिए डेरेफेरिंग पॉइंट कभी भी एक ही मेमोरी लोकेशन (यानी एक दूसरे को उर्फ) का उल्लेख नहीं करेंगे।"
इसलिए मूल रूप से यदि आपके पास int*
कुछ मेमोरी की ओर इशारा है int
और फिर आप float*
उस मेमोरी को इंगित करते हैं और इसे float
नियम तोड़ने के रूप में उपयोग करते हैं । यदि आपका कोड इसका सम्मान नहीं करता है, तो संकलक के ऑप्टिमाइज़र आपके कोड को सबसे अधिक बार तोड़ देंगे।
नियम का अपवाद एक है char*
, जिसे किसी भी प्रकार को इंगित करने की अनुमति है।
यह सख्त अलियासिंग नियम है, जो C ++ 03 मानक के खंड 3.10 में पाया गया है (अन्य उत्तर अच्छी व्याख्या प्रदान करते हैं, लेकिन कोई भी नियम स्वयं प्रदान नहीं करता है):
यदि कोई प्रोग्राम किसी वस्तु के संग्रहित मूल्य को निम्न प्रकारों के अलावा किसी अन्य व्यवहार के माध्यम से उपयोग करने का प्रयास करता है, तो व्यवहार अप्रभावित है:
- वस्तु का गतिशील प्रकार,
- ऑब्जेक्ट के गतिशील प्रकार का एक cv- योग्य संस्करण,
- एक प्रकार जो वस्तु के गतिशील प्रकार के अनुरूप हस्ताक्षरित या अहस्ताक्षरित प्रकार है,
- एक प्रकार जो ऑब्जेक्ट के गतिशील प्रकार के cv- योग्य संस्करण के अनुरूप हस्ताक्षरित या अहस्ताक्षरित प्रकार है,
- एक समुच्चय या संघ प्रकार जिसमें उसके सदस्यों के बीच पूर्वोक्त प्रकार शामिल हैं (सहित, पुनरावर्ती, उपसमूह या निहित संघ के सदस्य),
- एक प्रकार जो ऑब्जेक्ट का गतिशील प्रकार का (संभवतः cv-योग्य) बेस क्लास प्रकार है,
- एक
char
याunsigned char
प्रकार।
C ++ 11 और C ++ 14 शब्द (परिवर्तन पर बल दिया गया):
यदि कोई प्रोग्राम किसी वस्तु के संग्रहित मूल्य को निम्न प्रकार के व्यवहार के अलावा किसी अन्य वस्तु के ग्लव्यू के माध्यम से एक्सेस करने का प्रयास करता है , तो व्यवहार अप्रभावित है:
- वस्तु का गतिशील प्रकार,
- ऑब्जेक्ट के गतिशील प्रकार का एक cv- योग्य संस्करण,
- गतिशील प्रकार के ऑब्जेक्ट के समान (जैसा कि 4.4 में परिभाषित),
- एक प्रकार जो वस्तु के गतिशील प्रकार के अनुरूप हस्ताक्षरित या अहस्ताक्षरित प्रकार है,
- एक प्रकार जो ऑब्जेक्ट के गतिशील प्रकार के cv- योग्य संस्करण के अनुरूप हस्ताक्षरित या अहस्ताक्षरित प्रकार है,
- एक समग्र या संघ प्रकार जिसमें उसके तत्वों या गैर-स्थैतिक डेटा सदस्यों में एक प्रकार शामिल होता है (सहित, पुनरावर्ती, एक तत्व या उप-समूह या निहित संघ के गैर-स्थैतिक डेटा सदस्य ),
- एक प्रकार जो ऑब्जेक्ट का गतिशील प्रकार का (संभवतः cv-योग्य) बेस क्लास प्रकार है,
- एक
char
याunsigned char
प्रकार।
दो परिवर्तन छोटे थे: glvalue बजाय lvalue , और कुल मिलाकर / संघ मामले का स्पष्टीकरण।
तीसरा परिवर्तन एक मजबूत गारंटी देता है (मजबूत अलियासिंग नियम को शांत करता है): इसी प्रकार की नई अवधारणा जो अब उर्फ के लिए सुरक्षित है।
इसके अलावा सी शब्द (C99; ISO / IEC 9899: 1999 6.5 / 7; सटीक वही शब्दांकन ISO / IEC 9899: 2011 §6.5 ¶7 में प्रयोग किया जाता है):
एक वस्तु का भंडारित मूल्य केवल एक लैवल्यू एक्सप्रेशन द्वारा एक्सेस किया जाना चाहिए जिसमें निम्नलिखित प्रकारों में से एक है 73) या 88) :
- वस्तु के प्रभावी प्रकार के साथ संगत एक प्रकार,
- गुण के प्रभावी प्रकार के साथ संगत एक प्रकार का a एड संस्करण,
- एक प्रकार जो वस्तु के प्रभावी प्रकार से संबंधित हस्ताक्षरित या अहस्ताक्षरित प्रकार है,
- एक प्रकार जो ऑब्जेक्ट के प्रभावी प्रकार के क्वालि of एड संस्करण के अनुरूप हस्ताक्षरित या अहस्ताक्षरित प्रकार है,
- एक समग्र या संघ प्रकार जिसमें उसके सदस्यों के बीच पूर्वोक्त प्रकारों में से एक शामिल है (सहित, पुनरावर्ती, एक उपसमूह या निहित संघ का सदस्य), या
- एक चरित्र प्रकार।
73) या 88) इस सूची का आशय उन परिस्थितियों को निर्दिष्ट करना है जिनमें कोई वस्तु अलियास हो सकती है या नहीं।
wow(&u->s1,&u->s2)
करना कानूनी था, तो एक पॉइंटर को संशोधित करने के लिए उपयोग किए जाने पर भी कानूनी होने की आवश्यकता होगी u
, और यह सबसे अनुकूलन को नकार देगा सुविधा के लिए अलियासिंग नियम बनाया गया था।
यह मेरे "सख्त उपनाम नियम क्या है और हम क्यों परवाह करते हैं?" लिखें।
C और C ++ में अलियासिंग को किस प्रकार के अभिव्यक्ति के माध्यम से संग्रहीत मूल्यों तक पहुंचने की अनुमति है, इसके साथ क्या करना है। C और C ++ दोनों में, मानक निर्दिष्ट करता है कि किस प्रकार के उर्फ को किस प्रकार की अनुमति दी जाती है। कंपाइलर और ऑप्टिमाइज़र को यह मानने की अनुमति है कि हम अलियासिंग नियमों का कड़ाई से पालन करते हैं, इसलिए शब्द सख्त अलियासिंग नियम । यदि हम एक प्रकार का उपयोग करके एक मान तक पहुंचने का प्रयास करते हैं, तो इसे अपरिभाषित व्यवहार ( यूबी ) के रूप में वर्गीकृत नहीं किया जाता है । एक बार जब हमारे पास अपरिभाषित व्यवहार होता है तो सभी दांव बंद हो जाते हैं, हमारे कार्यक्रम के परिणाम अब विश्वसनीय नहीं हैं।
दुर्भाग्य से सख्त उर्फ उल्लंघन के साथ, हम अक्सर हम जो परिणाम की उम्मीद करते हैं, वह प्राप्त करेंगे, संभावना है कि एक नए अनुकूलन के साथ एक संकलक के भविष्य के संस्करण को कोड को तोड़ दिया जाएगा जिसे हमने सोचा था कि यह वैध था। यह अवांछनीय है और यह सख्त अलियासिंग नियमों को समझने का एक सार्थक लक्ष्य है और उनका उल्लंघन करने से कैसे बचें।
हम क्यों परवाह करते हैं, इसके बारे में अधिक समझने के लिए, हम उन मुद्दों पर चर्चा करेंगे जो सख्त अलियासिंग नियमों का उल्लंघन करते समय सामने आते हैं, टाइपिंग पाइंटिंग क्योंकि टाइप पाइंटिंग में उपयोग की जाने वाली सामान्य तकनीकें अक्सर सख्त अलियासिंग नियमों का उल्लंघन करती हैं और सही ढंग से सजा कैसे टाइप करें।
आइए कुछ उदाहरणों को देखें, फिर हम इस बारे में बात कर सकते हैं कि मानक (एस) क्या कहते हैं, कुछ और उदाहरणों की जांच करें और फिर देखें कि कैसे सख्त अलियासिंग से बचें और उन उल्लंघनों को पकड़ें जिनसे हम चूक गए थे। यहाँ एक उदाहरण है जो आश्चर्यजनक नहीं होना चाहिए ( लाइव उदाहरण ):
int x = 10;
int *ip = &x;
std::cout << *ip << "\n";
*ip = 12;
std::cout << x << "\n";
हम एक है पूर्णांक * स्मृति को ओर इशारा करते एक के कब्जे में पूर्णांक और यह एक वैध अलियासिंग है। ऑप्टिमाइज़र को यह मान लेना चाहिए कि आईपी के माध्यम से असाइनमेंट x के कब्जे वाले मूल्य को अपडेट कर सकता है ।
अगला उदाहरण अलियासिंग दिखाता है जो अपरिभाषित व्यवहार ( जीवंत उदाहरण ) की ओर जाता है :
int foo( float *f, int *i ) {
*i = 1;
*f = 0.f;
return *i;
}
int main() {
int x = 0;
std::cout << x << "\n"; // Expect 0
x = foo(reinterpret_cast<float*>(&x), &x);
std::cout << x << "\n"; // Expect 0?
}
फ़ंक्शन फू में हम एक इंट * और एक फ्लोट * लेते हैं , इस उदाहरण में हम फू कहते हैं और दोनों मापदंडों को एक ही मेमोरी स्थान पर इंगित करने के लिए सेट करते हैं जिसमें इस उदाहरण में एक इंट होता है । ध्यान दें, reinterpret_cast अभिव्यक्ति का इलाज करने के लिए संकलक से कह रहा है जैसे कि यह अपने टेम्पलेट पैरामीटर द्वारा टाइप किया गया था। इस मामले में हम इसे एक्सप्रेशन & x का इलाज करने के लिए कह रहे हैं जैसे कि यह फ्लोट * था । हम दूसरे काउंट के परिणाम की उम्मीद कर सकते हैं 0 हो सकता है, लेकिन ऑप्टिमाइज़ेशन के साथ -O2 का उपयोग करके सक्षम किया जाता है, दोनों gcc और clang निम्न परिणाम उत्पन्न करते हैं:
0
1
जिसकी उम्मीद नहीं की जा सकती है लेकिन यह पूरी तरह से वैध है क्योंकि हमने अपरिभाषित व्यवहार किया है। एक नाव वैध एक उपनाम नहीं कर सकते पूर्णांक वस्तु। इसलिए अनुकूलक मान सकते हैं निरंतर 1 जब अपसंदर्भन संग्रहीत मैं के बाद से के माध्यम से एक दुकान वापसी मान हो जाएगा च वैध एक को प्रभावित नहीं कर सकता पूर्णांक वस्तु। कंपाइलर एक्सप्लोरर में कोड प्लग करना यह दिखाता है कि वास्तव में क्या हो रहा है ( लाइव उदाहरण ):
foo(float*, int*): # @foo(float*, int*)
mov dword ptr [rsi], 1
mov dword ptr [rdi], 0
mov eax, 1
ret
का उपयोग कर अनुकूलक प्रकार के आधार उर्फ विश्लेषण (TBAA) मान लिया गया है 1 लौटा दी जाएगी और सीधे रजिस्टर में निरंतर मूल्य बढ़ता रहता है eax वापसी मान किया जाता है जो। TBAA लोड और स्टोर को अनुकूलित करने के लिए अन्य प्रकार की अनुमति देने के बारे में भाषाओं के नियमों का उपयोग करता है। इस मामले में TBAA जानता है कि एक फ्लोट उर्फ और int नहीं कर सकता है और i के भार को दूर करता है ।
वास्तव में मानक क्या कहता है कि हमें अनुमति दी गई है और क्या करने की अनुमति नहीं है? मानक भाषा सीधी नहीं है, इसलिए प्रत्येक आइटम के लिए मैं कोड उदाहरण प्रदान करने का प्रयास करूंगा जो अर्थ को प्रदर्शित करता है।
C11 मानक खंड में निम्नलिखित कहते हैं 6.5 भाव पैरा 7 :
एक वस्तु का अपना संग्रहीत मूल्य केवल एक लैवल्यू एक्सप्रेशन द्वारा पहुँचा जा सकता है, जिसमें निम्न में से एक प्रकार है: 88) - एक प्रकार की वस्तु के प्रभावी प्रकार के साथ संगत,
int x = 1;
int *p = &x;
printf("%d\n", *p); // *p gives us an lvalue expression of type int which is compatible with int
- वस्तु के प्रभावी प्रकार के साथ संगत एक प्रकार का एक योग्य संस्करण,
int x = 1;
const int *p = &x;
printf("%d\n", *p); // *p gives us an lvalue expression of type const int which is compatible with int
- एक प्रकार जो वस्तु के प्रभावी प्रकार के अनुरूप हस्ताक्षरित या अहस्ताक्षरित प्रकार है,
int x = 1;
unsigned int *p = (unsigned int*)&x;
printf("%u\n", *p ); // *p gives us an lvalue expression of type unsigned int which corresponds to
// the effective type of the object
gcc / clang का एक एक्सटेंशन है और यह भी अहस्ताक्षरित int * को int * असाइन करने की अनुमति देता है, भले ही वे संगत प्रकार न हों।
- एक प्रकार जो वस्तु के प्रभावी प्रकार के एक योग्य संस्करण के अनुरूप हस्ताक्षरित या अहस्ताक्षरित प्रकार है,
int x = 1;
const unsigned int *p = (const unsigned int*)&x;
printf("%u\n", *p ); // *p gives us an lvalue expression of type const unsigned int which is a unsigned type
// that corresponds with to a qualified verison of the effective type of the object
- एक समग्र या संघ प्रकार जिसमें इसके सदस्यों में से एक प्रकार शामिल है (सहित, पुनरावर्ती, एक उपसमूह या निहित संघ का सदस्य), या
struct foo {
int x;
};
void foobar( struct foo *fp, int *ip ); // struct foo is an aggregate that includes int among its members so it can
// can alias with *ip
foo f;
foobar( &f, &f.x );
- एक चरित्र प्रकार।
int x = 65;
char *p = (char *)&x;
printf("%c\n", *p ); // *p gives us an lvalue expression of type char which is a character type.
// The results are not portable due to endianness issues.
अनुभाग में C ++ 17 मसौदा मानक [basic.lval] पैराग्राफ 11 कहता है:
यदि कोई प्रोग्राम किसी वस्तु के संग्रहित मूल्य को निम्न प्रकार के व्यवहार के अलावा किसी अन्य वस्तु के ग्लव्यू तक पहुँचाने का प्रयास करता है तो व्यवहार अपरिभाषित है: 63 (11.1) - वस्तु का गतिशील प्रकार,
void *p = malloc( sizeof(int) ); // We have allocated storage but not started the lifetime of an object
int *ip = new (p) int{0}; // Placement new changes the dynamic type of the object to int
std::cout << *ip << "\n"; // *ip gives us a glvalue expression of type int which matches the dynamic type
// of the allocated object
(11.2) - वस्तु के गतिशील प्रकार का एक cv- योग्य संस्करण,
int x = 1;
const int *cip = &x;
std::cout << *cip << "\n"; // *cip gives us a glvalue expression of type const int which is a cv-qualified
// version of the dynamic type of x
(11.3) - वस्तु के गतिशील प्रकार के समान (जैसा कि 7.5 में परिभाषित)
(११.४) - एक प्रकार जो वस्तु के गतिशील प्रकार के अनुरूप हस्ताक्षरित या अहस्ताक्षरित प्रकार है,
// Both si and ui are signed or unsigned types corresponding to each others dynamic types
// We can see from this godbolt(https://godbolt.org/g/KowGXB) the optimizer assumes aliasing.
signed int foo( signed int &si, unsigned int &ui ) {
si = 1;
ui = 2;
return si;
}
(११.५) - एक प्रकार जो वस्तु के गतिशील प्रकार के cv- योग्य संस्करण के अनुरूप हस्ताक्षरित या अहस्ताक्षरित प्रकार है,
signed int foo( const signed int &si1, int &si2); // Hard to show this one assumes aliasing
(११.६) - एक समग्र या संघ प्रकार जिसमें इसके तत्वों या गैर-डेटा डेटा सदस्यों के बीच पूर्वोक्त प्रकारों में से एक शामिल है (सहित, पुनरावर्ती, एक तत्व या किसी उप-समूह या निहित यूनियन के गैर-स्थैतिक डेटा सदस्य),
struct foo {
int x;
};
// Compiler Explorer example(https://godbolt.org/g/z2wJTC) shows aliasing assumption
int foobar( foo &fp, int &ip ) {
fp.x = 1;
ip = 2;
return fp.x;
}
foo f;
foobar( f, f.x );
(११. () - एक प्रकार जो वस्तु के गतिशील प्रकार का एक (संभवतः cv-योग्य) आधार वर्ग प्रकार है,
struct foo { int x ; };
struct bar : public foo {};
int foobar( foo &f, bar &b ) {
f.x = 1;
b.x = 2;
return f.x;
}
(11.8) - एक चार, अहस्ताक्षरित चार, या एसटीडी :: बाइट प्रकार।
int foo( std::byte &b, uint32_t &ui ) {
b = static_cast<std::byte>('a');
ui = 0xFFFFFFFF;
return std::to_integer<int>( b ); // b gives us a glvalue expression of type std::byte which can alias
// an object of type uint32_t
}
वर्थ नोटिंग साइन किए गए चार्ट को ऊपर की सूची में शामिल नहीं किया गया है, यह सी से एक उल्लेखनीय अंतर है जो एक चरित्र प्रकार कहता है ।
हम इस बिंदु पर पहुंच गए हैं और हम सोच रहे होंगे कि हम क्यों इसके लिए उपनाम देना चाहेंगे? उत्तर आमतौर पर दंड टाइप करने के लिए होता है , अक्सर उपयोग किए जाने वाले तरीके सख्त अलियासिंग नियमों का उल्लंघन करते हैं।
कभी-कभी हम टाइप सिस्टम को दरकिनार करना चाहते हैं और किसी ऑब्जेक्ट को एक अलग प्रकार के रूप में व्याख्या करते हैं। मेमोरी के एक सेगमेंट को दूसरे प्रकार की व्याख्या करने के लिए इसे टाइप पिंगिंग कहा जाता है । टाइप पाइंटिंग उन कार्यों के लिए उपयोगी है जो किसी ऑब्जेक्ट के अंतर्निहित प्रतिनिधित्व तक पहुंच, परिवहन या हेरफेर करना चाहते हैं। विशिष्ट क्षेत्र हम पाते हैं कि टाइपिंग का उपयोग किया जा रहा है कंपाइलर, क्रमांकन, नेटवर्किंग कोड, आदि…
परंपरागत रूप से यह वस्तु के पते को लेते हुए पूरा किया गया है, इसे उस प्रकार के एक पॉइंटर को कास्टिंग करना है जिसे हम इसे फिर से व्याख्या करना चाहते हैं और फिर मूल्य को एक्सेस करके या अन्य शब्दों में अलियासिंग करके। उदाहरण के लिए:
int x = 1 ;
// In C
float *fp = (float*)&x ; // Not a valid aliasing
// In C++
float *fp = reinterpret_cast<float*>(&x) ; // Not a valid aliasing
printf( "%f\n", *fp ) ;
जैसा कि हमने पहले भी देखा है कि यह वैध रूप से भिन्न नहीं है, इसलिए हम अपरिभाषित व्यवहार कर रहे हैं। लेकिन पारंपरिक रूप से संकलक सख्त अलियासिंग नियमों का लाभ नहीं उठाते थे और इस प्रकार के कोड आमतौर पर बस काम करते थे, डेवलपर्स ने दुर्भाग्य से इस तरह से काम करने की आदत डाल ली है। टाइप पाइंटिंग के लिए एक सामान्य वैकल्पिक विधि यूनियनों के माध्यम से है, जो C ++ में मान्य है लेकिन C ++ में अपरिभाषित व्यवहार ( उदाहरण उदाहरण देखें ):
union u1
{
int n;
float f;
} ;
union u1 u;
u.f = 1.0f;
printf( "%d\n”, u.n ); // UB in C++ n is not the active member
यह C ++ में मान्य नहीं है और कुछ लोग यूनियनों के उद्देश्य को पूरी तरह से वैरिएंट प्रकारों को लागू करने के लिए मानते हैं और टाइप पाइंटिंग के लिए यूनियनों का उपयोग करने का अनुभव करते हैं।
C और C ++ दोनों में टाइप पाइंटिंग के लिए मानक विधि यादगार है । यह एक छोटे से भारी हाथ लग सकता है लेकिन अनुकूलक के उपयोग की पहचान करनी चाहिए memcpy के लिए प्रकार punning और इसे दूर का अनुकूलन और चाल रजिस्टर करने के लिए एक रजिस्टर उत्पन्न करते हैं। उदाहरण के लिए यदि हम जानते हैं कि int64_t डबल के समान आकार है :
static_assert( sizeof( double ) == sizeof( int64_t ) ); // C++17 does not require a message
हम memcpy का उपयोग कर सकते हैं :
void func1( double d ) {
std::int64_t n;
std::memcpy(&n, &d, sizeof d);
//...
एक पर्याप्त अनुकूलन स्तर पर किसी भी सभ्य आधुनिक संकलक कि पहले उल्लेख के समान कोड उत्पन्न reinterpret_cast विधि या संघ के लिए विधि प्रकार punning । उत्पन्न कोड की जांच करने पर हम देखते हैं कि यह केवल रजिस्टर mov ( लाइव कंपाइलर एक्सप्लोरर उदाहरण ) का उपयोग करता है ।
C ++ 20 में हम बिट_कास्ट ( प्रस्ताव से लिंक में उपलब्ध कार्यान्वयन) प्राप्त कर सकते हैं, जो एक कॉन्स्ट्रेक्स संदर्भ में प्रयोग करने योग्य होने के साथ-साथ टाइप-वाक्य को एक सरल और सुरक्षित तरीका देता है।
निम्नलिखित कैसे उपयोग करने के लिए का एक उदाहरण है bit_cast एक यमक टाइप करने के लिए अहस्ताक्षरित पूर्णांक के लिए नाव , ( यह सीधा प्रसारण दिखाई ):
std::cout << bit_cast<float>(0x447a0000) << "\n" ; //assuming sizeof(float) == sizeof(unsigned int)
उस स्थिति में जहां To और From के समान आकार नहीं है, हमें एक मध्यवर्ती संरचना 15 का उपयोग करने की आवश्यकता होती है। हम एक struct एक युक्त का उपयोग करेगा sizeof (अहस्ताक्षरित int) चरित्र सरणी ( मान लिया गया 4 बाइट अहस्ताक्षरित int ) होने के लिए से प्रकार और अहस्ताक्षरित पूर्णांक के रूप में करने के लिए टाइप करें .:
struct uint_chars {
unsigned char arr[sizeof( unsigned int )] = {} ; // Assume sizeof( unsigned int ) == 4
};
// Assume len is a multiple of 4
int bar( unsigned char *p, size_t len ) {
int result = 0;
for( size_t index = 0; index < len; index += sizeof(unsigned int) ) {
uint_chars f;
std::memcpy( f.arr, &p[index], sizeof(unsigned int));
unsigned int result = bit_cast<unsigned int>(f);
result += foo( result );
}
return result ;
}
यह दुर्भाग्यपूर्ण है कि हमें इस मध्यवर्ती प्रकार की आवश्यकता है लेकिन यह बिट_कास्ट का वर्तमान अवरोध है ।
हमारे पास C ++ में सख्त अलियासिंग को पकड़ने के लिए बहुत सारे अच्छे उपकरण नहीं हैं, हमारे पास जो उपकरण हैं वे सख्त अलियासिंग उल्लंघन के कुछ मामलों और गलत लोड और स्टोर के कुछ मामलों को पकड़ लेंगे।
gcc का उपयोग करके फ्लैग -ऑफस्ट्रिक्ट-अलियासिंग और -स्ट्रिक्ट-अलियासिंग कुछ मामलों को पकड़ सकते हैं, हालांकि झूठी सकारात्मक / नकारात्मक के बिना नहीं। उदाहरण के लिए निम्नलिखित मामले gcc में एक चेतावनी उत्पन्न करेंगे ( इसे लाइव देखें ):
int a = 1;
short j;
float f = 1.f; // Originally not initialized but tis-kernel caught
// it was being accessed w/ an indeterminate value below
printf("%i\n", j = *(reinterpret_cast<short*>(&a)));
printf("%i\n", j = *(reinterpret_cast<int*>(&f)));
हालाँकि यह इस अतिरिक्त मामले को नहीं पकड़ेगा ( इसे लाइव देखें ):
int *p;
p=&a;
printf("%i\n", j = *(reinterpret_cast<short*>(p)));
हालांकि क्लैंग इन झंडों को अनुमति देता है लेकिन यह स्पष्ट रूप से चेतावनियों को लागू नहीं करता है।
एक और उपकरण जो हमारे पास उपलब्ध है वह है आसन जो गलत लोड और स्टोर को पकड़ सकता है। हालाँकि ये सीधे तौर पर सख्त अलिज़िंग उल्लंघन नहीं हैं लेकिन ये सख्त अलियासिंग उल्लंघन का एक सामान्य परिणाम हैं। उदाहरण के लिए, निम्न मामले- क्लेन्साइनेट = पते के उपयोग के साथ निर्मित होने पर रनटाइम त्रुटियाँ उत्पन्न करेंगे
int *x = new int[2]; // 8 bytes: [0,7].
int *u = (int*)((char*)x + 6); // regardless of alignment of x this will not be an aligned address
*u = 1; // Access to range [6-9]
printf( "%d\n", *u ); // Access to range [6-9]
अंतिम उपकरण जिसकी मैं सिफारिश करूंगा, वह C ++ विशिष्ट है और कड़ाई से एक उपकरण नहीं है, लेकिन एक कोडिंग अभ्यास है, C- शैली की कास्ट की अनुमति न दें। Gcc और clang दोनों ही C- शैली के कलाकारों के लिए एक डायग्नोस्टिक का निर्माण करेंगे, जो कि-स्टाइल-कास्ट का उपयोग करेगा । यह किसी भी अपरिभाषित प्रकार के पुंजों को रीइंटरप्रिट_कास्ट का उपयोग करने के लिए मजबूर करेगा, सामान्य रीइंटरप्रिट_कास्ट में कोड कोड की समीक्षा के लिए एक ध्वज होना चाहिए। ऑडिट करने के लिए reinterpret_cast के लिए अपना कोड आधार खोजना भी आसान है।
सी के लिए हमारे पास पहले से ही कवर किए गए सभी उपकरण हैं और हमारे पास टिस-इंटरप्रेटर भी है, एक स्थिर विश्लेषक जो सी भाषा के एक बड़े उपसमूह के लिए प्रोग्राम का विस्तृत विश्लेषण करता है। पहले के उदाहरण के C verions को देखते हुए जहां -fstrict-aliasing का एक मामला याद आता है ( इसे लाइव देखें )
int a = 1;
short j;
float f = 1.0 ;
printf("%i\n", j = *((short*)&a));
printf("%i\n", j = *((int*)&f));
int *p;
p=&a;
printf("%i\n", j = *((short*)p));
tis-interpeter तीनों को पकड़ने में सक्षम है, निम्नलिखित उदाहरण tis-interpreter के रूप में tis-kernal को आमंत्रित करता है (आउटपुट संक्षिप्तता के लिए संपादित किया जाता है):
./bin/tis-kernel -sa example1.c
...
example1.c:9:[sa] warning: The pointer (short *)(& a) has type short *. It violates strict aliasing
rules by accessing a cell with effective type int.
...
example1.c:10:[sa] warning: The pointer (int *)(& f) has type int *. It violates strict aliasing rules by
accessing a cell with effective type float.
Callstack: main
...
example1.c:15:[sa] warning: The pointer (short *)p has type short *. It violates strict aliasing rules by
accessing a cell with effective type int.
अंत में TySan है जो वर्तमान में विकास में है। यह सैनिटाइज़र एक छाया स्मृति खंड में टाइप चेकिंग जानकारी जोड़ता है और यह देखने के लिए एक्सेस की जांच करता है कि क्या वे अलियासिंग नियमों का उल्लंघन करते हैं। उपकरण संभावित रूप से सभी अलियासिंग उल्लंघनों को पकड़ने में सक्षम होना चाहिए, लेकिन एक बड़ा रन-टाइम ओवरहेड हो सकता है।
reinterpret_cast
सकते हैं या क्या cout
मतलब हो सकता है। (C ++ का उल्लेख करना सही है लेकिन मूल प्रश्न C और IIUC के बारे में था, ये उदाहरण वैसा ही हो सकता है जैसा कि सी। में लिखा गया है)
सख्त अलियासिंग केवल संकेतकर्ताओं को संदर्भित नहीं करता है, यह संदर्भों को भी प्रभावित करता है, मैंने इसके बारे में एक पेपर को बढ़ावा देने वाले विकी के लिए लिखा था और यह इतनी अच्छी तरह से प्राप्त हुआ था कि मैंने इसे अपने परामर्श वेब साइट पर एक पृष्ठ में बदल दिया। यह पूरी तरह से समझाता है कि यह क्या है, यह लोगों को इतना भ्रमित क्यों करता है और इसके बारे में क्या करना है। सख्त अलियासिंग श्वेत पत्र । विशेष रूप से यह बताता है कि यूनियनें C ++ के लिए जोखिम भरा व्यवहार क्यों कर रही हैं, और मेमसीपी का उपयोग करना C और C ++ दोनों में एकमात्र फिक्स पोर्टेबल क्यों है। आशा है कि यह उपयोगी है।
डग टी के पहले से ही लिखे गए परिशिष्ट के रूप में, यहाँ एक सरल परीक्षण मामला है जो शायद इसे gcc से चलाता है:
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 संस्करणों के साथ मैंने कोशिश की) यह "सख्त अलियासिंग समस्या" को उत्पन्न करता है, क्योंकि संकलक मानता है कि "h" "चेक" फ़ंक्शन में "k" के समान पता नहीं हो सकता है। इसके कारण कंपाइलर if (*h == 5)
दूर का अनुकूलन करता है और हमेशा प्रिंटफ को कॉल करता है।
जो लोग यहाँ रुचि रखते हैं उनके लिए x64 कोडांतरण कोड है, जो कि gcc 4.6.3 द्वारा निर्मित है, ubuntu 12.04.2 पर चल रहा है:
movw $5, (%rdi)
movq $6, (%rsi)
movl $.LC0, %edi
jmp puts
तो अगर हालत पूरी तरह से कोडांतरक कोड से चला गया है।
long long*
और int64_t
*)। एक उम्मीद कर सकते हैं कि एक समझदार संकलक पहचान करनी चाहिए कि एक long long*
और int64_t*
एक ही भंडारण अगर वे हूबहू संग्रह किए गए हैं यहां पहुंच सकता है, लेकिन इस तरह के उपचार फैशनेबल नहीं रह गया है।
पॉइंटर कास्ट्स (जैसा कि एक यूनियन का उपयोग करने का विरोध किया गया है) के माध्यम से टाइप करें, सख्त अलियासिंग को तोड़ने का एक प्रमुख उदाहरण है।
fpsync()
fp के रूप में लिखने और int या इसके विपरीत पढ़ने के बीच एक निर्देश निष्पादित करता है [अलग पूर्णांक और FPU पाइपलाइनों और कैश के साथ कार्यान्वयन] , ऐसा निर्देश महंगा हो सकता है, लेकिन उतना महंगा नहीं जितना कि कंपाइलर हर यूनियन एक्सेस पर इस तरह का सिंक्रोनाइज़ेशन करता हो]। या एक कार्यान्वयन यह निर्दिष्ट कर सकता है कि परिणामी मूल्य सामान्य प्रारंभिक अनुक्रमों का उपयोग करने वाली परिस्थितियों को छोड़कर कभी भी उपयोग करने योग्य नहीं होगा।
C89 के तर्क के अनुसार, मानक के लेखकों को इस तरह के संकलक की आवश्यकता नहीं होनी चाहिए:
int x;
int test(double *p)
{
x=5;
*p = 1.0;
return x;
}
x
असाइनमेंट और रिटर्न स्टेटमेंट के बीच के मूल्य को फिर से लोड करने के लिए आवश्यक होना चाहिए ताकि संभावना p
को इंगित करने के लिए अनुमति मिल सके x
और असाइनमेंट *p
परिणामस्वरूप मान को बदल सकता है x
। यह धारणा कि एक संकलक को यह मानने का अधिकार होना चाहिए कि उपरोक्त स्थितियों में उपद्रव नहीं होगा , जैसे कि गैर-विवादास्पद था।
दुर्भाग्य से, C89 के लेखकों ने अपना नियम इस तरह से लिखा है कि, अगर सचमुच पढ़ा जाए, तो निम्न कार्य भी अपरिभाषित व्यवहार को आमंत्रित करेगा:
void test(void)
{
struct S {int x;} s;
s.x = 1;
}
क्योंकि यह प्रकार की int
वस्तु का उपयोग करने के लिए प्रकार का एक अंतराल का उपयोग करता है struct S
, और int
उन प्रकारों में से नहीं है , जिनका उपयोग किया जा सकता है a struct S
। क्योंकि यह अपरिभाषित व्यवहार के रूप में गैर-वर्ण-प्रकार के सदस्यों और संघों के सभी प्रकार के उपयोग के लिए बेतुका होगा, लगभग सभी लोग मानते हैं कि कम से कम कुछ परिस्थितियां हैं जहां एक प्रकार का एक अंतराल का उपयोग किसी अन्य प्रकार की वस्तु तक पहुंचने के लिए किया जा सकता है। । दुर्भाग्य से, सी मानक समिति उन परिस्थितियों को परिभाषित करने में विफल रही है।
अधिकांश समस्या दोषपूर्ण रिपोर्ट # 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 में कहा गया है कि कार्यक्रम अपरिभाषित व्यवहार को आमंत्रित करता है क्योंकि टाइप "डबल" के एक यूनियन सदस्य को लिखने और "इंट" में से एक को पढ़ने की कार्रवाई कार्यान्वयन-परिभाषित व्यवहार को आमंत्रित करती है। ऐसा तर्क निरर्थक है, लेकिन प्रभावी प्रकार के नियमों के लिए आधार बनाता है जो मूल समस्या को दूर करने के लिए कुछ भी नहीं करते हुए भाषा को अनावश्यक रूप से जटिल करते हैं।
मूल समस्या को हल करने का सबसे अच्छा तरीका संभवतः नियम के उद्देश्य के बारे में फ़ुटनोट का इलाज करना होगा जैसे कि यह नियमात्मक थे, और नियम को उन मामलों को छोड़कर अप्राप्य बना दिया, जिनमें वास्तव में एलियंस का उपयोग करके परस्पर विरोधी पहुंच शामिल है। कुछ दिया जैसे:
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
प्रकार के अंतराल के साथ की जाती है int
, और इसमें कोई संघर्ष नहीं है test
क्योंकि p
यह एक से प्राप्त होता है struct S
, और अगली बार जब 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
पहुंच के बीच एक अलियासिंग संघर्ष है और s.x
निष्पादन में उस बिंदु पर एक और संदर्भ मौजूद है जिसका उपयोग उसी भंडारण तक पहुंचने के लिए किया जाएगा ।
डिफेक्ट रिपोर्ट 028 में दो बिंदुओं के निर्माण और उपयोग के बीच ओवरलैप के कारण मूल उदाहरण यूबी को आमंत्रित किया गया था, जिसने "प्रभावी प्रकार" या ऐसी अन्य जटिलता को जोड़ने के बिना चीजों को बहुत अधिक स्पष्ट कर दिया होगा।
कई उत्तरों को पढ़ने के बाद, मुझे कुछ जोड़ने की आवश्यकता महसूस हुई:
सख्त अलियासिंग (जो मैं थोड़ा वर्णन करूंगा) महत्वपूर्ण है क्योंकि :
मेमोरी एक्सेस महंगी (प्रदर्शन वार) हो सकती है, यही कारण है कि भौतिक मेमोरी में वापस लिखे जाने से पहले सीपीयू रजिस्टरों में डेटा में हेरफेर किया जाता है ।
यदि दो अलग-अलग सीपीयू रजिस्टरों में डेटा एक ही मेमोरी स्पेस पर लिखा जाएगा, तो हम यह अनुमान नहीं लगा सकते कि सी में कोड होने पर कौन सा डेटा "जीवित" रहेगा ।
असेंबली में, जहां हम मैन्युअल रूप से सीपीयू रजिस्टरों के लोडिंग और अनलोडिंग को कोड करते हैं, हमें पता चल जाएगा कि कौन सा डेटा बरकरार है। लेकिन सी (शुक्र है) इस विस्तार को दूर करता है।
चूंकि दो पॉइंटर्स मेमोरी में एक ही स्थान को इंगित कर सकते हैं, इसलिए यह जटिल कोड हो सकता है जो संभव टकराव को संभालता है ।
यह अतिरिक्त कोड धीमा है और प्रदर्शन को नुकसान पहुंचाता है क्योंकि यह अतिरिक्त मेमोरी रीड / राइट ऑपरेशन करता है जो धीमी और (संभवतः) दोनों अनावश्यक हैं।
सख्त अलियासिंग नियम हमें अनावश्यक मशीन कोड से बचने के लिए अनुमति देता है जिन मामलों में उस में होना चाहिए ग्रहण करने के लिए है कि दो संकेत दिए गए एक ही स्मृति ब्लॉक को इंगित नहीं है सुरक्षित (यह भी देखें restrict
कीवर्ड)।
स्ट्रिक्ट अलियासिंग में कहा गया है कि यह मान लेना सुरक्षित है कि विभिन्न प्रकार के पॉइंटर्स मेमोरी में अलग-अलग स्थानों पर इंगित करते हैं।
यदि कोई कंपाइलर यह नोटिस करता है कि दो पॉइंटर्स विभिन्न प्रकारों (उदाहरण के लिए, int *
a float *
) की ओर इशारा करते हैं , तो यह मान लेगा कि मेमोरी एड्रेस अलग है और यह मेमोरी एड्रेस की टक्करों से बचाव नहीं करेगा , जिसके परिणामस्वरूप तेज मशीन कोड होगा।
उदाहरण के लिए :
निम्नलिखित फ़ंक्शन को मानें:
void merge_two_ints(int *a, int *b) {
*b += *a;
*a += *b;
}
उस मामले को संभालने के लिए जिसमें a == b
(दोनों पॉइंटर्स एक ही मेमोरी को इंगित करते हैं), हमें सीपीयू रजिस्टरों में मेमोरी से डेटा लोड करने के तरीके को ऑर्डर करने और परीक्षण करने की आवश्यकता होती है, इसलिए कोड इस तरह समाप्त हो सकता है:
लोड a
और b
मेमोरी से।
जोड़ने a
के लिए b
।
सहेजें b
और पुनः लोड करें a
।
(सीपीयू रजिस्टर से मेमोरी में सेव करें और मेमोरी से सीपीयू रजिस्टर में लोड करें)।
जोड़ने b
के लिए a
।
a
(CPU रजिस्टर से) मेमोरी में सेव करें ।
चरण 3 बहुत धीमा है क्योंकि इसे भौतिक मेमोरी तक पहुंचने की आवश्यकता है। हालांकि, यह मामलों में जहां के खिलाफ की रक्षा करने के लिए आवश्यक है a
और b
एक ही स्मृति का पता करने के लिए बिंदु।
सख्त उर्फिंग हमें संकलक को यह बताकर इसे रोकने की अनुमति देगा कि ये मेमोरी पते अलग-अलग हैं (जो, इस मामले में, आगे भी अनुकूलन की अनुमति देगा जो कि संकेत मेमोरी साझा करने पर नहीं किया जा सकता है)।
यह दो प्रकार से संकलक को बताया जा सकता है, विभिन्न प्रकारों का उपयोग करके इंगित करने के लिए। अर्थात:
void merge_two_numbers(int *a, long *b) {...}
restrict
कीवर्ड का उपयोग करना । अर्थात:
void merge_two_ints(int * restrict a, int * restrict b) {...}
अब, सख्त एलियासिंग नियम को संतुष्ट करने से, चरण 3 से बचा जा सकता है और कोड काफी तेजी से चलेगा।
वास्तव में, restrict
कीवर्ड को जोड़कर , पूरे फ़ंक्शन को इसके लिए अनुकूलित किया जा सकता है:
लोड a
और b
मेमोरी से।
जोड़ने a
के लिए b
।
बचाने के लिए दोनों a
को b
।
यह अनुकूलन संभव टक्कर की वजह से पहले से नहीं किया जा सकता था किया गया है, (जहां a
और b
के बजाय तीन गुना किया जाएगा दोगुनी)।
b
(इसे पुनः लोड नहीं कर रहे हैं ) और पुनः लोड कर रहे हैं a
। मुझे उम्मीद है कि अब यह स्पष्ट हो जाएगा।
restrict
, लेकिन मुझे लगता है कि उत्तरार्द्ध ज्यादातर परिस्थितियों में अधिक प्रभावी होगा, और कुछ बाधाओं को ढीला register
करने से यह उन कुछ मामलों में भरने की अनुमति देगा जहां restrict
मदद नहीं करेगा। मुझे यकीन नहीं है कि मानक का इलाज करना कभी भी "महत्वपूर्ण" था क्योंकि सभी मामलों का पूरी तरह से वर्णन करने के लिए, जहां प्रोग्रामर को कंपाइलर को अलियासिंग के साक्ष्य को पहचानने की अपेक्षा करनी चाहिए, न कि केवल उन स्थानों का वर्णन करने के लिए जहां कंपाइलरों को अलियासिंग का अनुमान लगाना चाहिए , जब इसका कोई विशेष प्रमाण मौजूद न हो ।
restrict
कीवर्ड न केवल संचालन की गति को कम करता है, बल्कि उनकी संख्या भी है, जो सार्थक हो सकती है ... मेरा मतलब है, आखिरकार, सबसे तेज ऑपरेशन कोई भी ऑपरेशन नहीं है :)
सख्त अलियासिंग अलग-अलग पॉइंटर प्रकारों को एक ही डेटा की अनुमति नहीं दे रहा है।
इस लेख से आपको इस मुद्दे को पूरी तरह से समझने में मदद मिलेगी।
int
और एक संरचना जिसमें शामिल है int
)।
तकनीकी रूप से C ++ में, सख्त अलियासिंग नियम शायद कभी लागू नहीं होता है।
अप्रत्यक्ष की परिभाषा पर ध्यान दें ( * ऑपरेटर ):
यूनीरी * ऑपरेटर अप्रत्यक्ष प्रदर्शन करता है: जिस अभिव्यक्ति के लिए इसे लागू किया जाता है वह एक ऑब्जेक्ट प्रकार के लिए एक संकेतक होगा, या एक फ़ंक्शन प्रकार के लिए एक संकेतक होगा और परिणाम वस्तु या फ़ंक्शन का एक अंतराल है जो अभिव्यक्ति को इंगित करता है ।
इसके अलावा glvalue की परिभाषा से
एक चमक एक अभिव्यक्ति है जिसका मूल्यांकन किसी वस्तु की पहचान को निर्धारित करता है, (... स्निप)
तो किसी भी अच्छी तरह से परिभाषित कार्यक्रम ट्रेस में, एक चमक एक वस्तु को संदर्भित करता है। तथाकथित अलियासिंग नियम कभी लागू नहीं होता है। यह वह नहीं हो सकता है जो डिजाइनर चाहते थे।
int foo;
, तो क्या लवलीन अभिव्यक्ति द्वारा पहुँचा जाता है *(char*)&foo
? क्या वह प्रकार की वस्तु है char
? क्या वह वस्तु उसी समय अस्तित्व में आती है foo
? foo
टाइप की उक्त वस्तु के संग्रहीत मूल्य को बदलने के लिए लिखना चाहेंगे char
? यदि ऐसा है, तो क्या ऐसा कोई नियम है जो किसी प्रकार के ऑब्जेक्ट का संग्रहित मूल्य टाइप के char
एक अंतराल का उपयोग करके एक्सेस करने की अनुमति देगा int
?
int i;
प्रत्येक चरित्र के चार ऑब्जेक्ट्स in addition to one of type
int ? I see no way to apply a consistent definition of "object" which would allow for operations on both
* (char *) & i` और बनाती है i
। अंत में, मानक में ऐसा कुछ भी नहीं है जो volatile
हार्डवेयर रजिस्टरों तक पहुँचने के लिए एक- अयोग्य सूचक को भी अनुमति देता है जो "ऑब्जेक्ट" की परिभाषा को पूरा नहीं करता है।
c
और के साथ टैग किया गया हैc++faq
।