NULL को पुनर्परिभाषित करना


118

मैं एक कोड के लिए C कोड लिख रहा हूँ जहाँ पता 0x0000 मान्य है और इसमें पोर्ट I / O है। इसलिए, NULL पॉइंटर तक पहुंचने वाले किसी भी संभावित कीड़े अनिर्धारित रहेंगे और साथ ही खतरनाक व्यवहार का कारण बनेंगे।

इस कारण से मैं NULL को दूसरा पता फिर से परिभाषित करना चाहता हूं, उदाहरण के लिए एक ऐसा पता जो मान्य नहीं है। अगर मैं गलती से ऐसे पते पर पहुँच जाता हूँ तो मुझे एक हार्डवेयर व्यवधान मिलेगा जहाँ मैं त्रुटि को संभाल सकता हूँ। इस संकलक के लिए मेरे पास stddef.h तक पहुंच है, इसलिए मैं वास्तव में मानक हेडर को बदल सकता हूं और NULL को फिर से परिभाषित कर सकता हूं।

मेरा सवाल है: सी मानक के साथ यह संघर्ष होगा? जहां तक ​​मैं मानक में 7.17 से बता सकता हूं, मैक्रो कार्यान्वयन-परिभाषित है। क्या मानक में कहीं और कुछ भी नहीं कहा गया है कि NULL 0 होना चाहिए?

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


3
यह वास्तव में एक अच्छा सवाल है। मेरे पास आपके लिए कोई उत्तर नहीं है, लेकिन मुझे पूछना होगा: क्या आप सुनिश्चित हैं कि यह आपके वैध सामान को 0x00 से दूर ले जाना संभव नहीं है और NULL को "सामान्य" सिस्टम की तरह एक अमान्य पता होने दें? यदि आप नहीं कर सकते हैं, तो उपयोग करने के लिए केवल सुरक्षित रूप से अमान्य पते वे होंगे जिन्हें आप सुनिश्चित कर सकते हैं कि आप उन्हें आवंटित कर सकते हैं और फिर mprotectसुरक्षित कर सकते हैं । या, यदि प्लेटफ़ॉर्म में कोई ASLR या जैसा नहीं है, तो प्लेटफ़ॉर्म भौतिक मेमोरी से परे पते। सौभाग्य।
बोरेलिड

8
यदि आपका कोड उपयोग कर रहा है तो यह कैसे काम करेगा if(ptr) { /* do something on ptr*/ }? यदि NULL को 0x0 से अलग परिभाषित किया जाए तो क्या यह काम करेगा?
ज़ेवियर टी।

3
C पॉइंटर का मेमोरी पतों से कोई जबरन संबंध नहीं है। जब तक सूचक अंकगणित के नियमों का अनुपालन किया जाता है, तब तक एक संकेत मूल्य कुछ भी हो सकता है। अधिकांश कार्यान्वयन मेमोरी पतों को पॉइंटर मान के रूप में उपयोग करने के लिए चुनते हैं, लेकिन वे तब तक कुछ भी उपयोग कर सकते हैं जब तक कि यह एक समरूपता है।
डेटेनवॉल्फ

2
@bdonlan जो MISRA-C में भी (सलाहकार) नियमों का उल्लंघन करेगा।
लुंडिन

2
@ और हां, यह मेरे विचार भी हैं। हार्डवेयर लोगों को उस हार्डवेयर को डिज़ाइन करने की अनुमति नहीं दी जानी चाहिए जो सॉफ्टवेयर में चलनी चाहिए! :)
लुंडिन

जवाबों:


84

C मानक को मशीन के पते पर शून्य बिंदुओं की आवश्यकता नहीं होती है। कैसे, 0एक पॉइंटर पॉइंटर को स्थिर रखने से एक पॉइंटर NULL(VER6.3.2.3 / 3) में परिणाम होना चाहिए , और बूलियन के रूप में अशक्त पॉइंटर का मूल्यांकन करना गलत होना चाहिए। इसमें कुछ समय अजीब यदि आप वास्तव में हो सकता है है एक शून्य पता चाहते हैं, और NULLशून्य का पता नहीं है।

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

विशेष रूप से, आपको निम्न की आवश्यकता होगी:

  • असाइनमेंट टू पॉइंटर्स (या कास्ट टू पॉइंटर्स) को शाब्दिक शून्य में व्यवस्थित करने के लिए कुछ अन्य जादुई मूल्य में परिवर्तित किया जा सकता है -1
  • 0इसके बजाय मैजिक वैल्यू की जांच के लिए पॉइंटर्स और एक स्थिर पूर्णांक के बीच समानता परीक्षणों की व्यवस्था करें (tests6.5.9 / 6)
  • सभी संदर्भों के लिए व्यवस्थित करें, जिसमें एक सूचक प्रकार का मूल्यांकन बूलियन के रूप में किया जाता है ताकि शून्य के लिए जाँच करने के बजाय जादू मूल्य के लिए समानता की जांच की जा सके। यह समानता परीक्षण शब्दार्थ से आता है, लेकिन संकलक इसे आंतरिक रूप से अलग तरह से लागू कर सकता है। See6.5.13 / 3, .16.5.14 / 3, .16.5.15 / 4, /6.5.3.3 / 5, §6.8.4.1 / 2, §6.8.5 / 4 देखें
  • जैसा कि बताया गया है, नए शून्य सूचक प्रतिनिधित्व को दर्शाने के लिए स्थैतिक वस्तुओं (.76.7.8 / 10) और आंशिक यौगिक आरंभीकरण (initial6.7.8 / 21) के आरंभीकरण के लिए शब्दार्थ को अद्यतन करें।
  • सत्य पता शून्य तक पहुँचने का एक वैकल्पिक तरीका बनाएँ।

कुछ चीजें हैं जिन्हें आपको संभालना नहीं है। उदाहरण के लिए:

int x = 0;
void *p = (void*)x;

इसके बाद, pएक शून्य सूचक होने की गारंटी नहीं है। केवल निरंतर असाइनमेंट को संभालने की जरूरत है (यह सही पता शून्य तक पहुंचने के लिए एक अच्छा तरीका है)। इसी तरह:

int x = 0;
assert(x == (void*)0); // CAN BE FALSE

इसके अलावा:

void *p = NULL;
int x = (int)p;

xहोने की गारंटी नहीं है 0

संक्षेप में, इस स्थिति को सी भाषा समिति द्वारा स्पष्ट रूप से माना गया था, और उन लोगों के लिए विचार किया गया था जो NULL के लिए एक वैकल्पिक प्रतिनिधित्व का चयन करेंगे। अब आपको बस इतना करना है कि अपने कंपाइलर में बड़े बदलाव करें, और हे प्रीस्टो आप कर रहे हैं :)

एक साइड नोट के रूप में, कंपाइलर उचित से पहले एक स्रोत कोड परिवर्तन चरण के साथ इन परिवर्तनों को लागू करना संभव हो सकता है। यह है कि प्रीप्रोसेसर के सामान्य प्रवाह के बजाय -> संकलक -> कोडांतरक -> लिंकर, आप एक पूर्वप्रक्रमक जोड़ेंगे -> पूर्ण परिवर्तन -> संकलक -> कोडांतरक -> लिंकर। तब आप परिवर्तन कर सकते हैं जैसे:

p = 0;
if (p) { ... }
/* becomes */
p = (void*)-1;
if ((void*)(p) != (void*)(-1)) { ... }

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


2
ठीक है मुझे §6.3.2.3 में पाठ नहीं मिला था, लेकिन मुझे संदेह था कि कहीं ऐसा वक्तव्य होगा :)। मुझे लगता है कि यह मेरे सवाल का जवाब देता है, मानक द्वारा मुझे NULL को फिर से परिभाषित करने की अनुमति नहीं है, जब तक कि मैं एक नया सी कंपाइलर लिखने की कल्पना नहीं करता हूं :)
लुंडिन

2
कंपाइलर को हैक करने के लिए एक अच्छी ट्रिक यह है कि पॉइंटर <-> पूर्णांक रूपांतरण XOR एक विशिष्ट मान है जो एक अमान्य पॉइंटर है और अभी भी तुच्छ है जो लक्ष्य आर्किटेक्चर को सस्ते में कर सकता है (आमतौर पर, यह एक एकल बिट सेट के साथ एक मान होगा) , जैसे 0x20000000)।
साइमन रिक्टर

2
एक और चीज़ जिसे आपको कंपाइलर में बदलना होगा, वह है कंपाउंड टाइप के साथ ऑब्जेक्ट्स का इनिशियलाइज़ेशन - अगर किसी ऑब्जेक्ट को आंशिक रूप से इनिशियलाइज़ किया जाता है, तो किसी भी पॉइंटर्स के लिए जो एक स्पष्ट इनबिल्टज़र मौजूद नहीं है, को इनिशियलाइज़ करना होगा NULL
कैफ़े

20

मानक बताता है कि मान 0 के साथ पूर्णांक स्थिर अभिव्यक्ति, या इस तरह की अभिव्यक्ति void *प्रकार में परिवर्तित , एक अशक्त सूचक स्थिरांक है। इसका मतलब है कि (void *)0हमेशा एक शून्य सूचक है, लेकिन दिया गया है int i = 0;,(void *)i जरूरत नहीं है।

सी कार्यान्वयन में इसके हेडर के साथ संकलक शामिल हैं। यदि आप हेडर को फिर से परिभाषित करने के लिए संशोधित करते हैंNULL , लेकिन स्थैतिक आरंभ को ठीक करने के लिए संकलक को संशोधित नहीं करते हैं, तो आपने एक गैर-अनुरूपण कार्यान्वयन बनाया है। यह एक साथ लिया गया संपूर्ण कार्यान्वयन है जिसमें गलत व्यवहार है, और यदि आपने इसे तोड़ दिया है, तो आपके पास वास्तव में दोष देने वाला कोई और नहीं है;)

उपरोक्त नियम के कारण , आपको निश्चित रूप से केवल स्थिर इनिशियलाइज़ेशन से अधिक को ठीक करना होगा - एक पॉइंटर दिया गया है p, if (p)इसके बराबर है if (p != NULL)


8

यदि आप C std लाइब्रेरी का उपयोग करते हैं, तो आप उन कार्यों के साथ समस्या में भाग लेंगे जो NULL को वापस ला सकते हैं। उदाहरण के लिए मल्लोक प्रलेखन राज्य:

यदि फ़ंक्शन मेमोरी के अनुरोधित ब्लॉक को आवंटित करने में विफल रहा, तो एक शून्य पॉइंटर लौटाया जाता है।

क्योंकि मॉलॉक और संबंधित कार्यों को पहले से ही एक विशिष्ट NULL मान के साथ बायनेरिज़ में संकलित किया जाता है, यदि आप NULL को फिर से परिभाषित करते हैं, तो आप सीधे सी std लाइब्रेरी का उपयोग नहीं कर पाएंगे, जब तक कि आप अपनी पूरी टूल चेन को पुनः नहीं बना लेते, जिसमें C std libs शामिल हैं।

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

इसके बजाय मैं आपके स्वयं के उपयोग के लिए आपके स्वयं के NULL, "MYPRODUCT_NULL" को परिभाषित करूंगा और / से लेकर C std लाइब्रेरी तक या इससे बचूंगा या अनुवाद करूंगा।


6

NULL को अकेला छोड़ दें और IO को 0x0000 पोर्ट करने के लिए एक विशेष मामले के रूप में मानें, शायद कोडांतरक में लिखी गई दिनचर्या का उपयोग कर रहे हैं, और इस प्रकार मानक सी शब्दार्थ के अधीन नहीं है। IOW, NULL को फिर से परिभाषित न करें, पोर्ट 0x00000 को फिर से परिभाषित करें।

ध्यान दें कि यदि आप C कंपाइलर लिख रहे हैं या संशोधित कर रहे हैं, तो डेलीफ्रेंसिंग NULL से बचने के लिए आवश्यक कार्य (यह मानते हुए कि आपके मामले में CPU मदद नहीं कर रहा है) वही है जो NULL परिभाषित नहीं है, इसलिए NULL परिभाषित को छोड़ना आसान है शून्य के रूप में, और सुनिश्चित करें कि शून्य को कभी भी C से अलग नहीं किया जा सकता है।


समस्या केवल तभी उत्पन्न होगी जब NULL गलती से एक्सेस किया जाता है, तब नहीं जब पोर्ट जानबूझकर एक्सेस किया जाता है। मैं उसके लिए पोर्ट I / O को फिर से परिभाषित क्यों करूंगा? यह पहले से ही काम कर रहा है जैसा कि इसे करना चाहिए।
लंडिन

2
@Lundin संयोगवश या नहीं, NULL को केवल C प्रोग्राम का उपयोग करके डीरेल किया जा सकता है *p, p[]या p(), इसलिए कंपाइलर को केवल IO पोर्ट 0x0000 की सुरक्षा के लिए उन लोगों की देखभाल करने की आवश्यकता है।
अपाला

@ लुंडिन आपके प्रश्न का दूसरा भाग: एक बार जब आप C के भीतर से शून्य को संबोधित करने के लिए पहुँच को प्रतिबंधित कर देते हैं, तो आपको पोर्ट 0x0000 तक पहुंचने के लिए एक और रास्ता चाहिए। कोडांतरक में लिखा एक फ़ंक्शन ऐसा कर सकता है। C के भीतर से, पोर्ट को 0xFFFF या जो भी मैप किया जा सकता है, लेकिन फ़ंक्शन का उपयोग करना और पोर्ट नंबर के बारे में भूलना सबसे अच्छा है।
अपाला

3

NULL को फिर से परिभाषित करने में अत्यधिक कठिनाई को देखते हुए, शायद जाने-माने हार्डवेयर पतों के लिए डेरेफ़रिंग को फिर से परिभाषित करना आसान है । एक पता बनाते समय, प्रत्येक प्रसिद्ध पते में 1 जोड़ें, ताकि आपका प्रसिद्ध आईओ पोर्ट हो:

  #define CREATE_HW_ADDR(x)(x+1)
  #define DEREFERENCE_HW_ADDR(x)(*(x-1))

  int* wellKnownIoPort = CREATE_HW_ADDR(0x00000000);

  printf("IoPortIs" DEREFERENCE_HW_ADDR(wellKnownIoPort));

यदि आपके द्वारा संबंधित पते एक साथ समूहीकृत हैं और आप सुरक्षित महसूस कर सकते हैं कि पते में 1 जोड़ना किसी भी चीज़ के साथ संघर्ष नहीं करेगा (जो कि ज्यादातर मामलों में नहीं होना चाहिए), तो आप इसे सुरक्षित रूप से करने में सक्षम हो सकते हैं। और फिर आपको फॉर्म में अपनी टूल चेन / एसटीडी लिब और एक्सप्रेशंस के पुनर्निर्माण के बारे में चिंता करने की जरूरत नहीं है:

  if (pointer)
  {
     ...
  }

अभी भी काम

पागल मुझे पता है, लेकिन बस सोचा था कि मैं इस विचार को वहां फेंक दूंगा :)।


समस्या केवल तभी उत्पन्न होगी जब NULL गलती से एक्सेस किया जाता है, तब नहीं जब पोर्ट जानबूझकर एक्सेस किया जाता है। मैं उसके लिए पोर्ट I / O को फिर से परिभाषित क्यों करूंगा? यह पहले से ही काम कर रहा है जैसा कि इसे करना चाहिए।
लंडिन

@LundIn मुझे लगता है कि आपको चुनना होगा कि कौन सा अधिक दर्दनाक है, पूरे टूलचिन को फिर से बनाना या अपने कोड के इस एक हिस्से को बदलना।
डग टी।

2

शून्य सूचक के लिए बिट पैटर्न पूर्णांक के लिए बिट पैटर्न के समान नहीं हो सकता है। लेकिन NULL मैक्रो का विस्तार एक शून्य सूचक स्थिर होना चाहिए, जो मान 0 का एक निरंतर पूर्णांक है जिसे (शून्य) में डाला जा सकता है *)।

अनुरूप रहने के दौरान आप जो परिणाम चाहते हैं, उसे प्राप्त करने के लिए, आपको अपनी टूल श्रृंखला को संशोधित (या शायद कॉन्फ़िगर करना होगा) करना होगा, लेकिन यह प्राप्त करने योग्य है।


1

तुम मुसीबत पूछ रहे हो। पुनर्परिभाषितNULL एक गैर शून्य मान के लिए इस कोड टूट जाएगा:

   अगर (myPointer)
   {
      // myPointer शून्य नहीं है
      ...
   }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.