C Language
अलियासिंग और प्रभावी प्रकार
खोज…
टिप्पणियों
नियमों का उल्लंघन करने और प्रभावी प्रकार की वस्तु का उल्लंघन करने के लिए दो अलग-अलग चीजें हैं और उन्हें भ्रमित नहीं किया जाना चाहिए।
एलियासिंग दो पॉइंटर्स
a
औरb
की संपत्ति है जोa == b
ही ऑब्जेक्ट को संदर्भित करता है, वह यह है किa == b
।किसी वस्तु के प्रभावी प्रकार का उपयोग C द्वारा यह निर्धारित करने के लिए किया जाता है कि उस वस्तु पर कौन से ऑपरेशन किए जा सकते हैं। विशेष रूप से प्रभावी प्रकार का उपयोग यह निर्धारित करने के लिए किया जाता है कि क्या दो पॉइंटर्स एक दूसरे को उर्फ कर सकते हैं।
अनुकूलन के लिए एक समस्या हो सकती है, क्योंकि ऑब्जेक्ट को एक पॉइंटर के माध्यम से बदलना, a
कहावत, दूसरे पॉइंटर के माध्यम से दिखाई देने वाली वस्तु को बदल सकता है, b
। यदि आपके सी कंपाइलर को यह मान लेना होगा कि पॉइंटर्स हमेशा एक-दूसरे को अलियास कर सकते हैं, भले ही उनके प्रकार और सिद्धता की परवाह किए बिना, कई अनुकूलन अवसर खो जाएंगे, और कई प्रोग्राम धीमी गति से चलेंगे।
सी के सख्त अलियासिंग नियम संकलक के मामलों को संदर्भित करते हैं जो यह मान सकते हैं कि ऑब्जेक्ट एक-दूसरे को अलियास करते हैं (या नहीं)। अंगूठे के दो नियम हैं जो आपको हमेशा डेटा पॉइंटर्स के लिए ध्यान में रखना चाहिए।
जब तक अन्यथा नहीं कहा जाता है, एक ही आधार प्रकार वाले दो संकेत उर्फ हो सकते हैं।
जब तक कम से कम दो प्रकारों में से कोई एक वर्ण प्रकार नहीं है, तब तक दो अलग-अलग आधार प्रकार के साथ संकेत नहीं दे सकते।
यहाँ आधार प्रकार का मतलब है कि हम एक तरफ इस तरह के रूप योग्यता टाइप रख const
, जैसे अगर a
है double*
और b
है const double*
, संकलक आम तौर पर की ही होगी का परिवर्तन है जो *a
परिवर्तन कर सकते हैं *b
।
दूसरे नियम का उल्लंघन करने से भयावह परिणाम हो सकते हैं। यहाँ सख्त अलियासिंग नियम का उल्लंघन करने का मतलब है कि आप दो संकेत पेश a
और b
संकलक करने के लिए विभिन्न प्रकार के जो एक ही वस्तु को वास्तविकता बिंदु में। कंपाइलर हमेशा यह मान सकता है कि दोनों अलग-अलग ऑब्जेक्ट्स की ओर इशारा करते हैं, और यदि आप ऑब्जेक्ट को *a
माध्यम से बदलते हैं तो यह *b
अपने विचार को अपडेट नहीं करेगा।
यदि आप ऐसा करते हैं तो आपके कार्यक्रम का व्यवहार अपरिभाषित हो जाता है। इसलिए, सी पॉइंटर रूपांतरणों पर काफी गंभीर प्रतिबंध लगाता है ताकि आप गलती से ऐसी स्थिति से बचने में मदद कर सकें।
जब तक स्रोत या लक्ष्य प्रकार
void
न हो, तब तक विभिन्न आधार प्रकार वाले बिंदुओं के बीच सभी सूचक रूपांतरण स्पष्ट होने चाहिए।
या दूसरे शब्दों में, वे एक डाली की जरूरत है, जब तक आप एक रूपांतरण है कि बस के रूप में एक क्वालीफायर कहते हैं करते हैं const
लक्ष्य प्रकार के लिए।
सामान्य रूप से और विशेष रूप से जातियों में सूचक रूपांतरणों से बचना आपको अन्य समस्याओं से बचाता है। जब तक आपको वास्तव में उनकी आवश्यकता नहीं होती है, और ये मामले बहुत विशेष हैं, तो आपको उनसे बचना चाहिए जैसा कि आप कर सकते हैं।
चरित्र प्रकारों को गैर-चरित्र प्रकारों के माध्यम से एक्सेस नहीं किया जा सकता है।
यदि किसी वस्तु को स्थैतिक, थ्रेड या स्वचालित भंडारण अवधि के साथ परिभाषित किया गया है, और इसमें एक वर्ण प्रकार है, तो: char
, unsigned char
या signed char
, यह एक गैर-वर्ण प्रकार द्वारा एक्सेस नहीं किया जा सकता है। उदाहरण नीचे में एक char
सरणी प्रकार के रूप में व्याख्या की है int
, और व्यवहार के हर भिन्नता पर अपरिभाषित है int
सूचक b
।
int main( void )
{
char a[100];
int* b = ( int* )&a;
*b = 1;
static char c[100];
b = ( int* )&c;
*b = 2;
_Thread_local char d[100];
b = ( int* )&d;
*b = 3;
}
यह अपरिभाषित है क्योंकि यह "प्रभावी प्रकार" नियम का उल्लंघन करता है, किसी भी प्रकार के डेटा ऑब्जेक्ट को प्रभावी प्रकार से दूसरे प्रकार के माध्यम से एक्सेस किया जा सकता है जो कि चरित्र प्रकार नहीं है। चूंकि यहां अन्य प्रकार int
, इसलिए इसकी अनुमति नहीं है।
भले ही संरेखण और सूचक आकार फिट करने के लिए जाना जाता है, यह इस नियम से छूट नहीं देगा, व्यवहार अभी भी अपरिभाषित होगा।
इसका मतलब यह है कि विशेष रूप से मानक सी में कोई ऐसा तरीका नहीं है जो चरित्र प्रकार के बफर ऑब्जेक्ट को आरक्षित कर सके जो विभिन्न प्रकारों के साथ पॉइंटर्स के माध्यम से उपयोग किया जा सकता है, क्योंकि आप एक बफर का उपयोग करेंगे जो कि malloc
या इसी तरह के फ़ंक्शन द्वारा प्राप्त किया गया था।
उपरोक्त लक्ष्य के समान लक्ष्य प्राप्त करने का एक सही तरीका union
का उपयोग करना होगा।
typedef union bufType bufType;
union bufType {
char c[sizeof(int[25])];
int i[25];
};
int main( void )
{
bufType a = { .c = { 0 } }; // reserve a buffer and initialize
int* b = a.i; // no cast necessary
*b = 1;
static bufType a = { .c = { 0 } };
int* b = a.i;
*b = 2;
_Thread_local bufType a = { .c = { 0 } };
int* b = a.i;
*b = 3;
}
यहां, union
यह सुनिश्चित करता है कि कंपाइलर शुरू से जानता है कि बफर को विभिन्न दृश्यों के माध्यम से एक्सेस किया जा सकता है। इसका यह भी लाभ है कि अब बफर में एक "व्यू" ai
जो पहले से ही टाइप int
और किसी भी पॉइंटर रूपांतरण की आवश्यकता नहीं है।
प्रभावी प्रकार
डेटा ऑब्जेक्ट का प्रभावी प्रकार अंतिम प्रकार की जानकारी है जो इसके साथ जुड़ा हुआ था, यदि कोई हो।
// a normal variable, effective type uint32_t, and this type never changes
uint32_t a = 0.0;
// effective type of *pa is uint32_t, too, simply
// because *pa is the object a
uint32_t* pa = &a;
// the object pointed to by q has no effective type, yet
void* q = malloc(sizeof uint32_t);
// the object pointed to by q still has no effective type,
// because nobody has written to it
uint32_t* qb = q;
// *qb now has effective type uint32_t because a uint32_t value was written
*qb = 37;
// the object pointed to by r has no effective type, yet, although
// it is initialized
void* r = calloc(1, sizeof uint32_t);
// the object pointed to by r still has no effective type,
// because nobody has written to or read from it
uint32_t* rc = r;
// *rc now has effective type uint32_t because a value is read
// from it with that type. The read operation is valid because we used calloc.
// Now the object pointed to by r (which is the same as *rc) has
// gained an effective type, although we didn't change its value.
uint32_t c = *rc;
// the object pointed to by s has no effective type, yet.
void* s = malloc(sizeof uint32_t);
// the object pointed to by s now has effective type uint32_t
// because an uint32_t value is copied into it.
memcpy(s, r, sizeof uint32_t);
निरीक्षण करें कि उत्तरार्द्ध के लिए, यह आवश्यक नहीं था कि हमारे पास उस वस्तु के लिए एक uint32_t*
सूचक भी हो। यह तथ्य कि हमने एक और uint32_t
ऑब्जेक्ट कॉपी किया है, पर्याप्त है।
सख्त अलियासिंग नियमों का उल्लंघन
निम्नलिखित कोड में हमें सरलता के लिए मान लें कि float
और uint32_t
का आकार समान है।
void fun(uint32_t* u, float* f) {
float a = *f
*u = 22;
float b = *f;
print("%g should equal %g\n", a, b);
}
u
और f
में अलग-अलग आधार प्रकार होते हैं, और इस तरह संकलक मान सकता है कि वे विभिन्न वस्तुओं की ओर इशारा करते हैं। इस बात की कोई संभावना नहीं है कि a
और b
की दो आदतों के बीच *f
को बदला जा सकता है, और इसलिए कंपाइलर कोड को कुछ के लिए अनुकूलित कर सकता है
void fun(uint32_t* u, float* f) {
float a = *f
*u = 22;
print("%g should equal %g\n", a, a);
}
यानी, *f
के दूसरे लोड ऑपरेशन को पूरी तरह से ऑप्टिमाइज़ किया जा सकता है।
यदि हम इस फ़ंक्शन को "सामान्य रूप से" कहते हैं
float fval = 4;
uint32_t uval = 77;
fun(&uval, &fval);
सब ठीक हो जाता है और कुछ पसंद आता है
4 को 4 के बराबर होना चाहिए
छपा है। लेकिन अगर हम एक ही पॉइंटर को धोखा देते हैं और पास करते हैं, तो इसे बदलने के बाद,
float fval = 4;
uint32_t* up = (uint32_t*)&fval;
fun(up, &fval);
हम सख्त अलियासिंग नियम का उल्लंघन करते हैं। तब व्यवहार अपरिभाषित हो जाता है। आउटपुट ऊपर के रूप में हो सकता है, यदि कंपाइलर ने दूसरी पहुंच को अनुकूलित किया है, या कुछ पूरी तरह से अलग है, और इसलिए आपका कार्यक्रम पूरी तरह से अविश्वसनीय स्थिति में समाप्त होता है।
सीमित योग्यता
यदि हमारे पास एक ही प्रकार के दो पॉइंटर तर्क हैं, तो कंपाइलर कोई धारणा नहीं बना सकता है और हमेशा यह मानना होगा कि परिवर्तन *e
बदल सकता है *f
:
void fun(float* e, float* f) {
float a = *f
*e = 22;
float b = *f;
print("is %g equal to %g?\n", a, b);
}
float fval = 4;
float eval = 77;
fun(&eval, &fval);
सब ठीक हो जाता है और कुछ पसंद आता है
4 4 के बराबर है?
छपा है। यदि हम एक ही पॉइंटर पास करते हैं, तो प्रोग्राम अभी भी सही काम करेगा और प्रिंट करेगा
4 22 के बराबर है?
यह अक्षम हो सकता है, अगर हम कुछ बाहरी जानकारी से जानते हैं कि e
और f
कभी भी एक ही डेटा ऑब्जेक्ट को इंगित नहीं करेंगे। हम सूचक मापदंडों में restrict
क्वालिफायर जोड़कर उस ज्ञान को प्रतिबिंबित कर सकते हैं:
void fan(float*restrict e, float*restrict f) {
float a = *f
*e = 22;
float b = *f;
print("is %g equal to %g?\n", a, b);
}
फिर कंपाइलर हमेशा e
और f
को अलग-अलग ऑब्जेक्ट्स के लिए इंगित कर सकता है।
बाइट्स बदलना
एक बार जब किसी वस्तु का एक प्रभावी प्रकार होता है, तो आपको किसी अन्य प्रकार के पॉइंटर के माध्यम से इसे संशोधित करने का प्रयास नहीं करना चाहिए, जब तक कि अन्य प्रकार एक चरित्र प्रकार, char
, signed char
या unsigned char
।
#include <inttypes.h>
#include <stdio.h>
int main(void) {
uint32_t a = 57;
// conversion from incompatible types needs a cast !
unsigned char* ap = (unsigned char*)&a;
for (size_t i = 0; i < sizeof a; ++i) {
/* set each byte of a to 42 */
ap[i] = 42;
}
printf("a now has value %" PRIu32 "\n", a);
}
यह एक मान्य प्रोग्राम है जो प्रिंट करता है
अब का मूल्य 707406378 है
यह काम करता है क्योंकि:
- एक्सेस अलग-अलग बाइट्स के लिए किया जाता है जो कि
unsigned char
साथ देखा जाता है ताकि प्रत्येक संशोधन अच्छी तरह से परिभाषित हो। - ऑब्जेक्ट के लिए दो दृश्य,
a
और के माध्यम से*ap
, उपनाम, लेकिन चूंकिap
एक चरित्र प्रकार के लिए एक संकेतक है, सख्त एलियासिंग नियम लागू नहीं होता है। इस प्रकार संकलक ग्रहण करने के लिए इस बात का महत्व हैa
में बदल दी गई हैंfor
पाश। के संशोधित मूल्यa
बाइट कि बदल दिया गया है का निर्माण किया जाना चाहिए। - के प्रकार के
a
,uint32_t
कोई पैडिंग बिट्स है। प्रतिनिधित्व के अपने सभी बिट्स मूल्य के लिए गणना करते हैं, यहां707406378
, और कोई जाल प्रतिनिधित्व नहीं हो सकता है।