क्या अशक्त संदर्भ संभव है?


102

क्या यह कोड मान्य (और परिभाषित व्यवहार) है?

int &nullReference = *(int*)0;

दोनों जी ++ और बजना ++ संकलन यह बिना किसी चेतावनी के, तब भी जब का उपयोग कर -Wall, -Wextra, -std=c++98, -pedantic, -Weffc++...

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

if( & nullReference == 0 ) // null reference

1
क्या आप कोई ऐसा मामला दे सकते हैं जहां यह वास्तव में उपयोगी हो? दूसरे शब्दों में, क्या यह सिर्फ एक सिद्धांत का सवाल है?
cdhowie

खैर, क्या संदर्भ कभी अपरिहार्य हैं? उनकी जगह हमेशा पॉइंटर्स का इस्तेमाल किया जा सकता है। ऐसा अशक्त संदर्भ आपको एक संदर्भ का उपयोग करने देगा जब आपके पास संदर्भित करने के लिए कोई वस्तु नहीं हो सकती है। पता नहीं यह कितना गंदा है, लेकिन इसके बारे में सोचने से पहले मुझे इसकी वैधता के बारे में दिलचस्पी थी।
peoro

8
मुझे लगता है कि यह है पर सिकोड़ी
डिफ़ॉल्ट

22
"हम जाँच कर सकते हैं" - नहीं, आप नहीं कर सकते। ऐसे कंपाइलर हैं जो स्टेटमेंट को if (false)चेक को खत्म कर देते हैं, ठीक है क्योंकि रेफरेंस वैसे भी अशक्त नहीं हो सकते। लिनक्स कर्नेल में एक बेहतर प्रलेखित संस्करण मौजूद था, जहाँ एक समान समान NULL चेक आउट अनुकूलित किया गया था: isc.sans.edu/diary.html?storyid=6820
MSalters

2
"एक पॉइंटर के बजाय एक संदर्भ का उपयोग करने का एक बड़ा कारण यह है कि आपको यह देखने के लिए परीक्षण करने के बोझ से मुक्त होना है कि क्या यह एक वैध वस्तु को संदर्भित करता है", इस उत्तर को, डिफ़ॉल्ट लिंक में, बहुत अच्छा लगता है!
peoro

जवाबों:


75

सन्दर्भ सूचक नहीं हैं।

8.3.2 / 1:

एक मान्य ऑब्जेक्ट या फ़ंक्शन को संदर्भित करने के लिए एक संदर्भ प्रारंभ किया जाएगा। [नोट: विशेष रूप से, एक अच्छी तरह से परिभाषित कार्यक्रम में एक अशक्त संदर्भ मौजूद नहीं हो सकता है, क्योंकि इस तरह के संदर्भ को बनाने का एकमात्र तरीका एक अशक्त सूचक को dereferencing द्वारा प्राप्त "ऑब्जेक्ट" से बांधना होगा, जो अपरिभाषित व्यवहार का कारण बनता है। 9.6 में वर्णित के रूप में, एक संदर्भ सीधे एक बिट क्षेत्र के लिए बाध्य नहीं किया जा सकता है। ]

1.9 / 4:

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

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


मैंने अपना उत्तर हटा दिया क्योंकि मैंने महसूस किया कि एक अशक्त सूचक को डीरेफ्रेंसिंग करने और एक अंतराल प्राप्त करने का मात्र एक मुद्दा है जो वास्तव में एक संदर्भ को बांधने की तुलना में एक अलग चीज है, जैसा कि आप उल्लेख करते हैं। हालाँकि, वस्तुओं या कार्यों को संदर्भित करने के लिए अंतरालों को कहा जाता है (इसलिए इस बिंदु में, वास्तव में एक संदर्भ बंधन के लिए कोई अंतर नहीं है), ये दो चीजें अभी भी अलग-अलग चिंताएं हैं। : अपसंदर्भन के मात्र अधिनियम के लिए, यहाँ लिंक है open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1102
Johannes Schaub - litb

1
@MSalters (हटाए गए उत्तर पर टिप्पणी करने के लिए उत्तर; यहां प्रासंगिक) मैं विशेष रूप से वहां प्रस्तुत तर्क से सहमत नहीं हो सकता। यद्यपि यह सार्वभौमिक &*pरूप से विचार करने के लिए सुविधाजनक हो सकता है p, लेकिन यह अपरिभाषित व्यवहार को खारिज नहीं करता है (जो इसकी प्रकृति से "काम करने के लिए प्रतीत हो सकता है"); और मैं इस बात से असहमत हूं कि एक typeidअभिव्यक्ति जो "डिरेल्ड नेल पॉइंटर" के प्रकार को निर्धारित करने की कोशिश करती है, वास्तव में डायर पॉइंटर को डेरेफेर करती है। मैंने देखा है कि लोग गंभीर रूप से बहस करते &a[size_of_array]हैं और उस पर भरोसा नहीं किया जा सकता है और वैसे भी यह सिर्फ लिखना आसान और सुरक्षित है a + size_of_array
कार्ल केनचेल

[C ++] टैग में @Default स्टैंडर्ड्स ऊंचे होने चाहिए। मेरा जवाब ऐसा लग रहा था कि दोनों ही हरकतें एक जैसी थीं और एक ही बात :) जबकि डेरेफ़रिंग और एक लवल्यू पाना जो आपके आस-पास से नहीं गुजरता है, जो "नो ऑब्जेक्ट" को संदर्भित करता है, संभवत: इसे एक संदर्भ में संग्रहीत किया जा सकता है, जो कि सीमित दायरे से बच जाता है और अचानक प्रभावित हो सकता है बहुत अधिक कोड।
जोहान्स शाउब -

@ कर्ल में अच्छी तरह से सी + +, "डेरेफ्रेंसिंग" का मतलब मान पढ़ना नहीं है। कुछ लोग सोचते हैं कि "डेरेफेरेंस" का मतलब वास्तव में संग्रहीत मूल्य को एक्सेस करना या संशोधित करना है, लेकिन यह सच नहीं है। तर्क यह है कि सी ++ का कहना है कि एक अंतराल "ऑब्जेक्ट या फ़ंक्शन" को संदर्भित करता है। यदि ऐसा है, तो सवाल यह है कि लैवल्यू का *pसंदर्भ क्या है, जब pएक शून्य सूचक है। C ++ में वर्तमान में एक खाली अंतराल की धारणा नहीं है, जिसे 232 अंक पेश करना चाहते थे।
जोहान्स स्काउब -

typeidसिंटैक्स के आधार पर, शब्दार्थ के आधार पर, कार्यों में विधिवत शून्य बिंदुओं का पता लगाना । यही है, यदि आप करते typeid(0, *(ostream*)0)हैं तो आपके पास अपरिभाषित व्यवहार होता है - कोई bad_typeidभी फेंका जाने की गारंटी नहीं है, भले ही आप एक लेल्टू पास करते हैं, जिसके परिणामस्वरूप एक शून्य सूचक डेरेफेरेंस शब्दार्थ से होता है। लेकिन वाक्यात्मक रूप से टॉपवेल पर, यह एक संदर्भ नहीं है, बल्कि एक अल्पविराम ऑपरेटर अभिव्यक्ति है।
जोहान्स शाउब -

26

उत्तर आपके दृष्टिकोण पर निर्भर करता है:


यदि आप C ++ मानक से न्याय करते हैं, तो आपको अशक्त संदर्भ नहीं मिल सकता क्योंकि आपको पहले अपरिभाषित व्यवहार मिलता है। अपरिभाषित व्यवहार की पहली घटना के बाद, मानक कुछ भी होने की अनुमति देता है। इसलिए, यदि आप लिखते हैं *(int*)0, तो आपके पास पहले से ही अपरिभाषित व्यवहार है जैसा कि आप भाषा मानक दृष्टिकोण से करते हैं, अशक्त सूचक को निष्क्रिय करते हैं। बाकी कार्यक्रम अप्रासंगिक है, एक बार जब इस अभिव्यक्ति को निष्पादित किया जाता है, तो आप खेल से बाहर हो जाते हैं।


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

फिर भी, वह अनुकूलन दूर अपरिभाषित व्यवहार को साबित करने के लिए संकलक पर निर्भर करता है, जो करना संभव नहीं हो सकता है। एक फ़ाइल के अंदर इस सरल कार्य पर विचार करें converter.cpp:

int& toReference(int* pointer) {
    return *pointer;
}

जब कंपाइलर इस फ़ंक्शन को देखता है, तो यह नहीं जानता है कि सूचक एक अशक्त सूचक है या नहीं। तो यह सिर्फ एक कोड बनाता है जो किसी भी पॉइंटर को संबंधित संदर्भ में बदल देता है। (Btw: यह एक नोक है क्योंकि संकेत और संदर्भ कोडांतरक में एक ही जानवर हैं।) अब, यदि आपके पास user.cppकोड के साथ एक और फ़ाइल है

#include "converter.h"

void foo() {
    int& nullRef = toReference(nullptr);
    cout << nullRef;    //crash happens here
}

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

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


तो अशक्त संदर्भों के लिए जाँच कैसे की जाती है? आपने लाइन दी

if( & nullReference == 0 ) // null reference

आपके सवाल में। ठीक है, यह काम नहीं करेगा: मानक के अनुसार, यदि आपके पास अशक्त सूचक है, तो आपके पास अपरिभाषित व्यवहार है, और आप अशक्त संदर्भ को निष्क्रिय किए बिना एक शून्य संदर्भ नहीं बना सकते हैं, इसलिए अशक्त संदर्भ केवल अपरिभाषित व्यवहार के दायरे में मौजूद हैं। चूंकि आपका कंपाइलर यह मान सकता है कि आप अपरिभाषित व्यवहार को ट्रिगर नहीं कर रहे हैं, यह मान सकते हैं कि अशक्त संदर्भ जैसी कोई चीज नहीं है (भले ही यह आसानी से कोड का उत्सर्जन करेगा जो अशक्त संदर्भ उत्पन्न करता है!)। जैसे, यह if()स्थिति को देखता है , निष्कर्ष निकालता है कि यह सच नहीं हो सकता है, और बस पूरे if()बयान को दूर फेंक देना चाहिए । लिंक टाइम ऑप्टिमाइजेशन की शुरूआत के साथ, एक मजबूत तरीके से अशक्त संदर्भों की जांच करना सरल असंभव हो गया है।


टी एल; डॉ:

अशक्त संदर्भ कुछ हद तक अस्तित्व में हैं:

उनका अस्तित्व असंभव लगता है (= मानक द्वारा),
लेकिन वे मौजूद हैं (= उत्पन्न मशीन कोड द्वारा),
लेकिन आप उन्हें नहीं देख सकते हैं यदि वे मौजूद हैं (= आपके प्रयास दूर हो जाएंगे),
लेकिन वे आपको अनजान भी मार सकते हैं (= आपका कार्यक्रम अजीब बिंदुओं, या बदतर) पर क्रैश होता है।
आपकी एकमात्र आशा है कि वे मौजूद नहीं हैं (= उन्हें बनाने के लिए अपना कार्यक्रम न लिखें)।

मैं आशा करता हूं कि आपको परेशान नहीं करना पड़ेगा!


2
वास्तव में एक "पिंग हाथी" क्या है?
फाप्र्स

2
@ मेरा कोई सुराग नहीं है, यह सिर्फ एक टाइपो था। लेकिन C ++ मानक यह परवाह नहीं करेगा कि यह गुलाबी है या पिंग हाथी जो दिखाई देते हैं, वैसे भी ;-)
cmaster - reicaate monica

9

यदि आपका इरादा सिंगलटन ऑब्जेक्ट्स की गणना में अशक्त का प्रतिनिधित्व करने का एक तरीका खोजना था, तो यह (डी) संदर्भ नल (यह C ++ 11, nullptr) के लिए एक बुरा विचार है।

स्टैटिक सिंगलटन ऑब्जेक्ट को घोषित क्यों नहीं किया जाता है जो निम्न प्रकार से NULL का प्रतिनिधित्व करता है और nullrr रिटर्न करने वाले कास्ट-टू-पॉइंटर ऑपरेटर को जोड़ता है?

संपादित करें: कई गलतियां दुरुस्त कीं और डाली-टू-पॉइंटर ऑपरेटर को वास्तव में काम करने के लिए परीक्षण करने के लिए मुख्य () में स्टेटमेंट जोड़ा (जो कि मैं भूल गया .. मेरा बुरा) - 10 मार्च 2015 -

// Error.h
class Error {
public:
  static Error& NOT_FOUND;
  static Error& UNKNOWN;
  static Error& NONE; // singleton object that represents null

public:
  static vector<shared_ptr<Error>> _instances;
  static Error& NewInstance(const string& name, bool isNull = false);

private:
  bool _isNull;
  Error(const string& name, bool isNull = false) : _name(name), _isNull(isNull) {};
  Error() {};
  Error(const Error& src) {};
  Error& operator=(const Error& src) {};

public:
  operator Error*() { return _isNull ? nullptr : this; }
};

// Error.cpp
vector<shared_ptr<Error>> Error::_instances;
Error& Error::NewInstance(const string& name, bool isNull = false)
{
  shared_ptr<Error> pNewInst(new Error(name, isNull)).
  Error::_instances.push_back(pNewInst);
  return *pNewInst.get();
}

Error& Error::NOT_FOUND = Error::NewInstance("NOT_FOUND");
//Error& Error::NOT_FOUND = Error::NewInstance("UNKNOWN"); Edit: fixed
//Error& Error::NOT_FOUND = Error::NewInstance("NONE", true); Edit: fixed
Error& Error::UNKNOWN = Error::NewInstance("UNKNOWN");
Error& Error::NONE = Error::NewInstance("NONE");

// Main.cpp
#include "Error.h"

Error& getError() {
  return Error::UNKNOWN;
}

// Edit: To see the overload of "Error*()" in Error.h actually working
Error& getErrorNone() {
  return Error::NONE;
}

int main(void) {
  if(getError() != Error::NONE) {
    return EXIT_FAILURE;
  }

  // Edit: To see the overload of "Error*()" in Error.h actually working
  if(getErrorNone() != nullptr) {
    return EXIT_FAILURE;
  }
}

क्योंकि यह धीमी है
वंडेन

6

clang ++ 3.5 भी इस पर चेतावनी देता है:

/tmp/a.C:3:7: warning: reference cannot be bound to dereferenced null pointer in well-defined C++ code; comparison may be assumed to
      always evaluate to false [-Wtautological-undefined-compare]
if( & nullReference == 0 ) // null reference
      ^~~~~~~~~~~~~    ~
1 warning generated.
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.