खोज…


परिचय

अपरिभाषित व्यवहार (UB) क्या है? आईएसओ सी ++ मानक (§1.3.24, N4296) के अनुसार, यह "व्यवहार है जिसके लिए यह अंतर्राष्ट्रीय मानक कोई आवश्यकता नहीं रखता है।"

इसका मतलब यह है कि जब कोई कार्यक्रम यूबी का सामना करता है, तो उसे वह करने की अनुमति दी जाती है जो वह चाहता है। यह अक्सर एक दुर्घटना का मतलब है, लेकिन यह बस कुछ भी नहीं कर सकता है, राक्षसों को अपनी नाक से बाहर उड़ने दें , या यहां तक कि ठीक से काम करने के लिए दिखाई दें!

कहने की जरूरत नहीं है, आपको यूबी को आमंत्रित करने वाले कोड को लिखने से बचना चाहिए।

टिप्पणियों

यदि किसी प्रोग्राम में अपरिभाषित व्यवहार होता है, तो C ++ मानक उसके व्यवहार पर कोई बाधा नहीं डालता है।

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

एक कार्यान्वयन एक ऑपरेशन के परिणाम का दस्तावेजीकरण कर सकता है जो मानक के अनुसार अपरिभाषित व्यवहार पैदा करता है, लेकिन एक प्रोग्राम जो इस तरह के प्रलेखित व्यवहार पर निर्भर करता है, पोर्टेबल नहीं है।

अपरिभाषित व्यवहार क्यों मौजूद है

सहज रूप से, अपरिभाषित व्यवहार को एक बुरी बात माना जाता है क्योंकि इस तरह की त्रुटियों को अपवादस्वरूप हैंडलर्स के माध्यम से गंभीर रूप से नियंत्रित नहीं किया जा सकता है।

लेकिन कुछ व्यवहार को अपरिभाषित छोड़ना वास्तव में C ++ के वादे का एक अभिन्न अंग है "जो आप उपयोग नहीं करते हैं उसके लिए आप भुगतान नहीं करते हैं"। अनिर्धारित व्यवहार एक संकलक को यह मानने की अनुमति देता है कि डेवलपर जानता है कि वह क्या कर रहा है और उपरोक्त उदाहरणों में हाइलाइट की गई गलतियों की जाँच के लिए कोड का परिचय नहीं दे रहा है।

अपरिभाषित व्यवहार को खोजना और उससे बचना

विकास के दौरान अपरिभाषित व्यवहार की खोज के लिए कुछ उपकरणों का उपयोग किया जा सकता है:

  • अधिकांश संकलक के पास ध्वज के झंडे हैं जो संकलन समय पर अपरिभाषित व्यवहार के कुछ मामलों के बारे में चेतावनी देते हैं।
  • -fsanitize=undefined नए संस्करणों में एक तथाकथित " -fsanitize=undefined बिहेवियर -fsanitize=undefined " फ़्लैग ( -fsanitize=undefined ) शामिल है जो एक प्रदर्शन लागत पर रनटाइम में अपरिभाषित व्यवहार की जाँच करेगा।
  • lint -जैसे उपकरण अधिक गहन अपरिहार्य व्यवहार विश्लेषण कर सकते हैं।

अनिर्धारित, अनिर्दिष्ट और कार्यान्वयन-परिभाषित व्यवहार

C ++ 14 मानक (ISO / IEC 14882: 2014) सेक्शन 1.9 (प्रोग्राम एक्ज़ेक्यूशन):

  1. इस अंतर्राष्ट्रीय मानक में शब्दार्थ विवरण एक मानकीकृत नॉनडेटर्मिनिस्टिक अमूर्त मशीन को परिभाषित करते हैं। [कट गया]

  2. अमूर्त मशीन के कुछ पहलुओं और संचालन को इस अंतर्राष्ट्रीय मानक में कार्यान्वयन-परिभाषित (उदाहरण के लिए, sizeof(int) ) के रूप में वर्णित किया गया है। ये अमूर्त मशीन के मापदंडों का गठन करते हैं । प्रत्येक कार्यान्वयन में इन मामलों में इसकी विशेषताओं और व्यवहार का वर्णन करने वाले दस्तावेज़ शामिल होंगे। [कट गया]

  3. अमूर्त मशीन के कुछ अन्य पहलुओं और संचालन को इस अंतर्राष्ट्रीय मानक में अनिर्दिष्ट (उदाहरण के लिए, एक नए-शुरुआती में अभिव्यक्तियों का मूल्यांकन, यदि आवंटन फ़ंक्शन मेमोरी आवंटित करने में विफल रहता है) के रूप में वर्णित किया गया है। जहाँ संभव हो, यह अंतर्राष्ट्रीय मानक स्वीकार्य व्यवहारों के एक सेट को परिभाषित करता है। ये अमूर्त मशीन के nondeterministic पहलुओं को परिभाषित करते हैं। अमूर्त मशीन का एक उदाहरण इस प्रकार किसी दिए गए कार्यक्रम और दिए गए इनपुट के लिए एक से अधिक संभावित निष्पादन हो सकता है।

  4. इस अंतर्राष्ट्रीय मानक में कुछ अन्य परिचालनों को अपरिभाषित (या उदाहरण के लिए, एक const ऑब्जेक्ट को संशोधित करने के प्रयास का प्रभाव) के रूप में वर्णित किया गया है। [ नोट : यह अंतर्राष्ट्रीय मानक उन कार्यक्रमों के व्यवहार पर कोई आवश्यकता नहीं लगाता है जिनमें अपरिभाषित व्यवहार होता है। - अंतिम नोट ]

एक नल सूचक के माध्यम से पढ़ना या लिखना

int *ptr = nullptr;
*ptr = 1; // Undefined behavior

यह अपरिभाषित व्यवहार है , क्योंकि एक शून्य सूचक किसी भी वैध वस्तु की ओर इशारा नहीं करता है, इसलिए लिखने के लिए *ptr पर कोई वस्तु नहीं है।

हालांकि यह अक्सर एक विभाजन दोष का कारण बनता है, यह अपरिभाषित है और कुछ भी हो सकता है।

गैर-शून्य रिटर्न प्रकार के साथ एक फ़ंक्शन के लिए कोई रिटर्न स्टेटमेंट नहीं

किसी फ़ंक्शन में return स्टेटमेंट को छोड़ना जिसका रिटर्न प्रकार है जो void नहीं है अपरिभाषित व्यवहार है

int function() {  
    // Missing return statement
} 

int main() {
    function(); //Undefined Behavior
}

अधिकांश आधुनिक दिन के कंपाइलर इस तरह के अपरिभाषित व्यवहार के लिए संकलन समय पर चेतावनी देते हैं।


नोट: main नियम का एकमात्र अपवाद है। यदि main return स्टेटमेंट नहीं है, तो कंपाइलर स्वचालित रूप से return 0; सम्मिलित करता है return 0; आपके लिए, इसलिए इसे सुरक्षित रूप से छोड़ा जा सकता है।

एक स्ट्रिंग शाब्दिक संशोधन

सी ++ 11
char *str = "hello world";
str[0] = 'H';

"hello world" एक स्ट्रिंग शाब्दिक है, इसलिए इसे संशोधित करना अपरिभाषित व्यवहार देता है।

उपरोक्त उदाहरण में str का आरंभीकरण औपचारिक रूप से घटाया गया था (मानक के भविष्य के संस्करण से हटाने के लिए अनुसूचित) C ++ 03 में। 2003 से पहले के कई संकलक इस बारे में एक चेतावनी जारी कर सकते हैं (जैसे एक संदिग्ध रूपांतरण)। 2003 के बाद, संकलक आमतौर पर पदावनत रूपांतरण के बारे में चेतावनी देते हैं।

सी ++ 11

उपरोक्त उदाहरण अवैध है, और एक संकलक निदान में सी ++ 11 और बाद में परिणाम होता है। इस तरह के रूपांतरण को स्पष्ट रूप से अनुमति देकर अपरिभाषित व्यवहार का प्रदर्शन करने के लिए एक समान उदाहरण का निर्माण किया जा सकता है:

char *str = const_cast<char *>("hello world");
str[0] = 'H'; 

एक बाहरी सीमा सूचकांक तक पहुँचना

यह एक अनुक्रमणिका तक पहुँचने के लिए अपरिभाषित व्यवहार है जो एक सरणी के लिए सीमा से बाहर है (या उस मामले के लिए मानक पुस्तकालय कंटेनर, क्योंकि वे सभी एक कच्चे सरणी का उपयोग करके कार्यान्वित किए जाते हैं):

 int array[] = {1, 2, 3, 4, 5};
 array[5] = 0;  // Undefined behavior

इसे सरणी के अंत में एक पॉइंटर इंगित करने की अनुमति दी गई है (इस मामले में array + 5 ), आप बस इसे मना नहीं कर सकते, क्योंकि यह एक वैध तत्व नहीं है।

 const int *end = array + 5;  // Pointer to one past the last index
 for (int *p = array; p != end; ++p)
   // Do something with `p`

सामान्य तौर पर, आपको आउट-ऑफ-बाउंड्स पॉइंटर बनाने की अनुमति नहीं है। एक पॉइंटर को एरे के भीतर एक तत्व को इंगित करना चाहिए, या अंत में एक अतीत होना चाहिए।

पूर्णांक विभाजन शून्य से

int x = 5 / 0;    // Undefined behavior

विभाजन 0 से गणितीय रूप से अपरिभाषित है, और जैसे कि यह समझ में आता है कि यह अपरिभाषित व्यवहार है।

तथापि:

float x = 5.0f / 0.0f;   // x is +infinity

अधिकांश कार्यान्वयन IEEE-754 को लागू करते हैं, जो NaN (यदि अंश 0.0f ), infinity (यदि अंश सकारात्मक है) या -infinity (यदि अंश नकारात्मक है) वापस करने के लिए फ्लोटिंग पॉइंट डिवीजन को शून्य से परिभाषित करता है।

हस्ताक्षर किए गए पूर्णांक अतिप्रवाह

int x = INT_MAX + 1;

// x can be anything -> Undefined behavior

यदि किसी अभिव्यक्ति के मूल्यांकन के दौरान, परिणाम गणितीय रूप से परिभाषित नहीं है या अपने प्रकार के लिए प्रतिनिधित्व योग्य मूल्यों की सीमा में नहीं है, तो व्यवहार अपरिभाषित है।

(C ++ 11 मानक पैराग्राफ 5/4)

यह अधिक बुरा लोगों में से एक है, क्योंकि यह आमतौर पर प्रतिलिपि प्रस्तुत करने योग्य, गैर-दुर्घटनाग्रस्त व्यवहार करता है ताकि डेवलपर्स को मनाया व्यवहार पर बहुत भरोसा करने के लिए लुभाया जा सके।


दूसरी ओर:

unsigned int x = UINT_MAX + 1;

// x is 0

तब से अच्छी तरह से परिभाषित किया गया है:

अहस्ताक्षरित पूर्णांक, जिन्हें अहस्ताक्षरित घोषित किया गया है, अंकगणितीय मॉडुलो 2^n के नियमों का पालन करेंगे जहां n पूर्णांक के उस विशेष आकार के मूल्य प्रतिनिधित्व में बिट्स की संख्या है।

(C ++ 11 मानक पैराग्राफ 3.9.1 / 4)

कभी-कभी कंपाइलर एक अपरिभाषित व्यवहार और अनुकूलन का फायदा उठा सकते हैं

signed int x ;
if(x > x + 1)
{
    //do something
}

यहां चूंकि एक हस्ताक्षरित पूर्णांक अतिप्रवाह परिभाषित नहीं है, इसलिए संकलक यह मानने के लिए स्वतंत्र है कि यह कभी नहीं हो सकता है और इसलिए यह "यदि" ब्लॉक को दूर कर सकता है।

एक असिंचित स्थानीय चर का उपयोग करना

int a;
std::cout << a; // Undefined behavior!

यह अपरिभाषित व्यवहार का परिणाम है, क्योंकि a असंवैधानिक है।

यह अक्सर गलत तरीके से दावा किया जाता है कि ऐसा इसलिए है क्योंकि मूल्य "अनिश्चित" है, या "उस स्मृति स्थान में जो भी मूल्य था"। हालांकि, यह का मूल्य तक पहुँचने का कार्य है a ऊपर के उदाहरण है कि अपरिभाषित व्यवहार देता में। व्यवहार में, "कचरा मूल्य" को प्रिंट करना इस मामले में एक सामान्य लक्षण है, लेकिन यह अपरिभाषित व्यवहार का केवल एक संभावित रूप है।

हालांकि व्यवहार में अत्यधिक संभावना नहीं है (क्योंकि यह विशिष्ट हार्डवेयर समर्थन पर निर्भर है) कंपाइलर ऊपर के कोड नमूने को संकलित करते समय प्रोग्रामर को समान रूप से अच्छी तरह से इलेक्ट्रोक्यूट कर सकता है। इस तरह के एक कंपाइलर और हार्डवेयर समर्थन के साथ, अपरिभाषित व्यवहार के प्रति ऐसी प्रतिक्रिया स्पष्ट रूप से अपरिभाषित व्यवहार के वास्तविक अर्थ की औसत (लिविंग) प्रोग्रामर समझ को बढ़ाएगी - जो यह है कि परिणामी व्यवहार पर मानक कोई बाधा नहीं डालता है।

सी ++ 14

unsigned char प्रकार के अनिश्चित मान का उपयोग अपरिभाषित व्यवहार उत्पन्न नहीं करता है यदि मान का उपयोग किया जाता है:

  • दूसरे या तीसरे ऑपरेशन के टर्नरी कंडिशनर;
  • बिल्ट-इन कॉमा ऑपरेटर का सही संचालन;
  • unsigned char रूपांतरण के संचालन;
  • असाइनमेंट ऑपरेटर का सही संचालन, अगर बाएं ऑपरेंड भी unsigned char ;
  • एक unsigned char ऑब्जेक्ट के लिए unsigned char ;

या यदि मान त्याग दिया गया है। ऐसे मामलों में, अनिश्चित मूल्य केवल अभिव्यक्ति के परिणाम के लिए प्रचारित करता है, यदि लागू हो।

ध्यान दें कि एक static चर हमेशा शून्य-आरंभीकृत होता है (यदि संभव हो):

static int a;
std::cout << a; // Defined behavior, 'a' is 0

एकाधिक गैर-समान परिभाषा (एक परिभाषा नियम)

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

foo.h :

class Foo {
  public:
    double x;
  private:
    int y;
};

Foo get_foo();

foo.cpp :

#include "foo.h"
Foo get_foo() { /* implementation */ }

main.cpp :

// I want access to the private member, so I am going to replace Foo with my own type
class Foo {
  public:
    double x;
    int y;
};
Foo get_foo(); // declare this function ourselves since we aren't including foo.h
int main() {
    Foo foo = get_foo();
    // do something with foo.y
}

उपरोक्त कार्यक्रम अपरिभाषित व्यवहार प्रदर्शित करता है क्योंकि इसमें वर्ग की दो परिभाषाएँ होती हैं ::Foo , जिसमें बाहरी लिंकेज है, विभिन्न अनुवाद इकाइयों में, लेकिन दोनों परिभाषाएँ समान नहीं हैं। एक ही अनुवाद इकाई के भीतर एक वर्ग के पुनर्वितरण के विपरीत, इस समस्या का संकलनकर्ता द्वारा निदान करने की आवश्यकता नहीं है।

स्मृति आबंटन और डीलोकेशन की गलत जोड़ी

किसी ऑब्जेक्ट को केवल delete करके ही निपटाया जा सकता है यदि वह new द्वारा आवंटित किया गया हो और एरे न हो। यदि delete का तर्क new या एक सरणी से वापस नहीं किया गया था, तो व्यवहार अपरिभाषित है।

किसी ऑब्जेक्ट को केवल delete[] द्वारा निपटाया जा सकता है अगर वह new द्वारा आवंटित किया गया था और एक सरणी है। यदि delete[] का तर्क delete[] new द्वारा वापस नहीं किया गया था या एक सरणी नहीं है, तो व्यवहार अपरिभाषित है।

यदि free करने के तर्क को malloc द्वारा वापस नहीं किया गया था, तो व्यवहार अपरिभाषित है।

int* p1 = new int;
delete p1;      // correct
// delete[] p1; // undefined
// free(p1);    // undefined

int* p2 = new int[10];
delete[] p2;    // correct
// delete p2;   // undefined
// free(p2);    // undefined

int* p3 = static_cast<int*>(malloc(sizeof(int)));
free(p3);       // correct
// delete p3;   // undefined
// delete[] p3; // undefined

इस तरह के मुद्दों को पूरी तरह से malloc से बचने और सी ++ कार्यक्रमों में free में बचा जा सकता है, कच्ची new और delete पर मानक पुस्तकालय स्मार्ट पॉइंटर्स को प्राथमिकता देना, और std::vector और std::string कच्चे new पर std::string और delete[]

किसी वस्तु को गलत प्रकार से एक्सेस करना

ज्यादातर मामलों में, एक प्रकार की वस्तु तक पहुंचना गैरकानूनी है क्योंकि यह एक अलग प्रकार (सीवी-क्वालीफायर की अवहेलना) थी। उदाहरण:

float x = 42;
int y = reinterpret_cast<int&>(x);

परिणाम अपरिभाषित व्यवहार है।

इस सख्त अलियासिंग नियम के कुछ अपवाद हैं:

  • वर्ग प्रकार की एक वस्तु तक पहुँचा जा सकता है जैसे कि यह एक प्रकार का था जो वास्तविक वर्ग प्रकार का आधार वर्ग है।
  • किसी भी प्रकार को char या unsigned char रूप में एक्सेस किया जा सकता है, लेकिन रिवर्स सच नहीं है: एक चार सरणी तक नहीं पहुँचा जा सकता है क्योंकि यह एक मनमाना प्रकार था।
  • एक हस्ताक्षरित पूर्णांक प्रकार को संबंधित अहस्ताक्षरित प्रकार और इसके विपरीत पहुँचा जा सकता है।

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

struct Base {
};
struct Derived : Base {
    void f() {}
};
struct Unrelated {};
Unrelated u;
Derived& r1 = reinterpret_cast<Derived&>(u); // ok
r1.f();                                      // UB
Base b;
Derived& r2 = reinterpret_cast<Derived&>(b); // ok
r2.f();                                      // UB

चल बिन्दु बाढ़

यदि एक अंकगणितीय ऑपरेशन जो एक फ्लोटिंग पॉइंट प्रकार की उपज देता है, तो एक मान उत्पन्न होता है जो परिणाम प्रकार के प्रतिनिधित्व योग्य मूल्यों की सीमा में नहीं है, व्यवहार C ++ मानक के अनुसार अपरिभाषित है, लेकिन अन्य मानकों द्वारा परिभाषित किया जा सकता है जो मशीन के अनुरूप हो सकते हैं, जैसे IEEE 754।

float x = 1.0;
for (int i = 0; i < 10000; i++) {
    x *= 10.0; // will probably overflow eventually; undefined behavior
}

कॉलिंग (शुद्ध) कंस्ट्रक्टर या डिस्ट्रक्टर से वर्चुअल सदस्य

मानक (10.4) स्थिति:

सदस्य कार्यों को एक अमूर्त वर्ग के निर्माता (या विध्वंसक) से बुलाया जा सकता है; इस तरह के एक कंस्ट्रक्टर (या डिस्ट्रक्टर) से बनाई गई (या नष्ट) वस्तु के लिए प्रत्यक्ष या अप्रत्यक्ष रूप से एक शुद्ध आभासी कार्य करने के लिए एक आभासी कॉल (10.3) करने का प्रभाव अपरिभाषित है।

अधिक सामान्यतः, कुछ C ++ अधिकारी, जैसे स्कॉट मेयर्स, सुझाव देते हैं कि कभी भी निर्माणकर्ताओं और अवरोधकों से आभासी कार्यों (यहां तक कि गैर-शुद्ध वाले) को कॉल न करें।

निम्नलिखित उदाहरण पर विचार करें, उपरोक्त लिंक से संशोधित:

class transaction
{
public:
    transaction(){ log_it(); }
    virtual void log_it() const = 0;
};

class sell_transaction : public transaction
{
public:
    virtual void log_it() const { /* Do something */ }
};

मान लें कि हम एक sell_transaction ऑब्जेक्ट बनाते हैं:

sell_transaction s;

यह स्पष्ट रूप से sell_transaction के निर्माता को sell_transaction , जो पहले transaction के निर्माता को कॉल करता है। जब transaction के निर्माता को हालांकि कहा जाता है, तो वस्तु अभी तक sell_transaction के प्रकार की नहीं है, बल्कि केवल प्रकार के transaction

नतीजतन, transaction::transaction() में कॉल transaction::transaction() to log_it , वह नहीं होगा जो सहज ज्ञान युक्त बात हो सकती है - अर्थात् कॉल sell_transaction::log_it

  • यदि log_it शुद्ध आभासी है, तो इस उदाहरण में, व्यवहार अपरिभाषित है।

  • यदि log_it गैर-शुद्ध आभासी है, तो transaction::log_it कहा जाएगा।

किसी ऑब्जेक्ट को किसी पॉइंटर क्लास के लिए पॉइंटर के माध्यम से हटाना जिसमें वर्चुअल डिस्ट्रक्टर नहीं है।

class base { };
class derived: public base { }; 

int main() {
    base* p = new derived();
    delete p; // The is undefined behavior!
}

खंड में [expr.delete] [5.3.5 / 3 मानक का कहना है कि यदि delete को उस वस्तु पर कहा जाता है जिसके स्थिर प्रकार में एक virtual विध्वंसक नहीं है:

यदि हटाई जाने वाली वस्तु का स्थैतिक प्रकार उसके गतिशील प्रकार से भिन्न होता है, तो स्थैतिक प्रकार हटाए जाने वाली वस्तु के गतिशील प्रकार का एक आधार वर्ग होगा और स्थैतिक प्रकार में एक आभासी विध्वंसक होगा या व्यवहार अपरिभाषित होगा।

यह इस मामले की परवाह किए बिना है कि क्या व्युत्पन्न वर्ग ने किसी भी डेटा सदस्यों को आधार वर्ग में जोड़ा है।

झूलते संदर्भ तक पहुँचना

किसी ऐसी वस्तु के संदर्भ को एक्सेस करना अवैध है जो दायरे से बाहर हो गई है या अन्यथा नष्ट हो गई है। इस तरह के संदर्भ को झूलना कहा जाता है क्योंकि यह अब एक वैध वस्तु को संदर्भित नहीं करता है।

#include <iostream>
int& getX() {
    int x = 42;
    return x;
}
int main() {
    int& r = getX();
    std::cout << r << "\n";
}

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

`Std` या` posix` नामस्थान का विस्तार

मानक (17.6.4.2.1 / 1) आम तौर पर std नामस्थान को बढ़ाने से मना किया जाता है:

C ++ प्रोग्राम का व्यवहार अपरिभाषित है यदि यह नेमस्पेस std के लिए या नामस्थान std के भीतर एक नेमस्पेस में घोषणा या परिभाषा जोड़ता है जब तक कि अन्यथा निर्दिष्ट न हो।

उसी के लिए चला जाता है posix (17.6.4.2.2 / 1):

C ++ प्रोग्राम का व्यवहार अपरिभाषित है, यदि यह नेमस्पेस पॉज़िक्स या नेमस्पेस पॉज़िक्स में किसी नेमस्पेस में घोषणाएँ या परिभाषाएँ जोड़ता है जब तक कि अन्यथा निर्दिष्ट न हो।

निम्नलिखित को धयान मे रखते हुए:

#include <algorithm>

namespace std
{
    int foo(){}
}

मानक परिभाषा algorithm में कुछ भी नहीं (या एक हेडर इसमें शामिल है) एक ही परिभाषा को परिभाषित करता है, और इसलिए यह कोड वन डेफिनिशन नियम का उल्लंघन करेगा।

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

class foo
{
    // Stuff
};

फिर निम्नलिखित ठीक है

namespace std
{
    template<>
    struct hash<foo>
    {
    public:
        size_t operator()(const foo &f) const;
    };
}

रूपांतरण के दौरान या फ़्लोटिंग पॉइंट प्रकार से ओवरफ़्लो

यदि, रूपांतरण के दौरान:

  • एक पूर्णांक प्रकार एक फ्लोटिंग पॉइंट प्रकार के लिए,
  • पूर्णांक प्रकार के लिए एक अस्थायी बिंदु प्रकार, या
  • एक फ्लोटिंग पॉइंट प्रकार एक छोटे फ्लोटिंग पॉइंट प्रकार के लिए,

स्रोत मान उन मानों की श्रेणी के बाहर है जिन्हें गंतव्य प्रकार में दर्शाया जा सकता है, परिणाम अपरिभाषित व्यवहार है। उदाहरण:

double x = 1e100;
int y = x; // int probably cannot hold numbers that large, so this is UB

अमान्य आधार-व्युत्पन्न स्थिर कलाकार

अगर static_cast class को एक पॉइंटर (रेफरेंस रिफरेंस) को बेस क्लास से पॉइंटर (रेफरेंस रेफरेंस) में परिवर्तित करने के लिए व्युत्पन्न क्लास में इस्तेमाल किया जाता है, लेकिन ओपेरन्ड व्युत्पन्न क्लास टाइप के किसी ऑब्जेक्ट के लिए पॉइंट (रेफरेंस रेफरेंस) को इंगित नहीं करता है, तो व्यवहार। अपरिभाषित है। व्युत्पन्न रूपांतरण के लिए बेस देखें।

बेमेल फ़ंक्शन पॉइंटर प्रकार के माध्यम से फ़ंक्शन कॉल

फ़ंक्शन पॉइंटर के माध्यम से फ़ंक्शन को कॉल करने के लिए, फ़ंक्शन पॉइंटर के प्रकार को फ़ंक्शन के प्रकार से बिल्कुल मेल खाना चाहिए। अन्यथा, व्यवहार अपरिभाषित है। उदाहरण:

int f();
void (*p)() = reinterpret_cast<void(*)()>(f);
p(); // undefined

एक कास्ट ऑब्जेक्ट को संशोधित करना

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

इस तरह का प्रयास const_cast माध्यम से किया जा सकता है:

const int x = 123;
const_cast<int&>(x) = 456;
std::cout << x << '\n';

एक कंपाइलर आमतौर पर एक const int ऑब्जेक्ट के इनलाइन को इनलाइन करता है, इसलिए यह संभव है कि यह कोड 123 प्रिंट करता है और प्रिंट करता है। कंपाइलर रीड-ओनली मेमोरी में const ऑब्जेक्ट्स का मान भी रख सकते हैं, इसलिए सेगमेंटेशन फॉल्ट हो सकता है। किसी भी मामले में, व्यवहार अपरिभाषित है और कार्यक्रम कुछ भी कर सकता है।

निम्नलिखित कार्यक्रम कहीं अधिक सूक्ष्म त्रुटि को छुपाता है:

#include <iostream>

class Foo* instance;

class Foo {
  public:
    int get_x() const { return m_x; }
    void set_x(int x) { m_x = x; }
  private:
    Foo(int x, Foo*& this_ref): m_x(x) {
        this_ref = this;
    }
    int m_x;
    friend const Foo& getFoo();
};

const Foo& getFoo() {
    static const Foo foo(123, instance);
    return foo;
}

void do_evil(int x) {
    instance->set_x(x);
}

int main() {
    const Foo& foo = getFoo();
    do_evil(456);
    std::cout << foo.get_x() << '\n';
}

इस कोड में, getFoo टाइप m_x const Foo का एक सिंगलटन बनाता है और इसके सदस्य m_x को 123 आरंभीकृत किया जाता है। तब do_evil कहा जाता है और का मूल्य foo.m_x जाहिरा तौर पर 456. क्या गलत हो गया था करने के लिए बदल रहा है?

अपने नाम के बावजूद, do_evil कुछ भी विशेष रूप से बुराई नहीं करता है; यह सब एक Foo* माध्यम से एक सेटर है। लेकिन वह पॉइंटर एक const_cast const Foo ऑब्जेक्ट की ओर const_cast , जबकि const_cast का उपयोग नहीं किया गया था। यह सूचक Foo के निर्माता के माध्यम से प्राप्त किया गया था। एक const वस्तु नहीं बन जाता है const , जब तक अपनी आरंभीकरण पूरा हो गया है इसलिए this प्रकार है Foo* , नहीं const Foo* , निर्माता के भीतर।

इसलिए, अपरिभाषित व्यवहार तब भी होता है जब इस कार्यक्रम में कोई स्पष्ट रूप से खतरनाक निर्माण नहीं होते हैं।

पॉइंटर से मेम्बर के माध्यम से सदस्य तक कोई नहीं पहुंच सकता है

जब किसी ऑब्जेक्ट के गैर-स्थैतिक सदस्य को पॉइंटर के माध्यम से सदस्य तक पहुँचाना, यदि ऑब्जेक्ट में वास्तव में पॉइंटर द्वारा निरूपित सदस्य शामिल नहीं है, तो व्यवहार अपरिभाषित है। (सदस्य को ऐसा सूचक static_cast माध्यम से प्राप्त किया जा सकता है।)

struct Base { int x; };
struct Derived : Base { int y; };
int Derived::*pdy = &Derived::y;
int Base::*pby = static_cast<int Base::*>(pdy);

Base* b1 = new Derived;
b1->*pby = 42; // ok; sets y in Derived object to 42
Base* b2 = new Base;
b2->*pby = 42; // undefined; there is no y member in Base

सदस्यों के लिए संकेत के लिए अमान्य व्युत्पन्न-से-आधार रूपांतरण

जब static_cast का उपयोग TD::* को TB::* परिवर्तित करने के लिए किया जाता है, तो सदस्य को उस वर्ग से संबंधित होना चाहिए जो कि आधार वर्ग है या B का व्युत्पन्न वर्ग है। अन्यथा व्यवहार अपरिभाषित है। सदस्यों के लिए संकेत के आधार रूपांतरण के लिए व्युत्पन्न देखें

अमान्य सूचक अंकगणित

सूचक अंकगणित के निम्न उपयोग अपरिभाषित व्यवहार का कारण बनते हैं:

  • पूर्णांक का जोड़ या घटाव, यदि परिणाम समान सरणी ऑब्जेक्ट के रूप में नहीं होता है, तो सूचक ऑपरेंड। (यहाँ, तत्व एक पिछले अंत को अभी भी सरणी से संबंधित माना जाता है।)

    int a[10];
    int* p1 = &a[5];
    int* p2 = p1 + 4; // ok; p2 points to a[9]
    int* p3 = p1 + 5; // ok; p2 points to one past the end of a
    int* p4 = p1 + 6; // UB
    int* p5 = p1 - 5; // ok; p2 points to a[0]
    int* p6 = p1 - 6; // UB
    int* p7 = p3 - 5; // ok; p7 points to a[5]
    
  • दो पॉइंटर्स का घटाव यदि वे दोनों एक ही ऐरे ऑब्जेक्ट के नहीं हैं। (फिर से, अंत के एक तत्व को सरणी से संबंधित माना जाता है।) अपवाद यह है कि दो नल बिंदुओं को घटाया जा सकता है, उपज 0।

    int a[10];
    int b[10];
    int *p1 = &a[8], *p2 = &a[3];
    int d1 = p1 - p2; // yields 5
    int *p3 = p1 + 2; // ok; p3 points to one past the end of a
    int d2 = p3 - p2; // yields 7
    int *p4 = &b[0];
    int d3 = p4 - p1; // UB
    
  • दो पॉइंटर्स का घटाव यदि परिणाम overflows std::ptrdiff_t

  • कोई भी सूचक अंकगणित, जहां या तो ऑपरेंड के पॉइंटी टाइप को इंगित किए गए ऑब्जेक्ट के गतिशील प्रकार से मेल नहीं खाता है (सीवी-योग्यता की अनदेखी)। मानक के अनुसार, "[में] विशेष रूप से, एक आधार वर्ग के एक सूचक का उपयोग सूचक अंकगणितीय के लिए नहीं किया जा सकता है जब सरणी में एक व्युत्पन्न वर्ग प्रकार की वस्तुएं होती हैं।"

    struct Base { int x; };
    struct Derived : Base { int y; };
    Derived a[10];
    Base* p1 = &a[1];           // ok
    Base* p2 = p1 + 1;          // UB; p1 points to Derived
    Base* p3 = p1 - 1;          // likewise
    Base* p4 = &a[2];           // ok
    auto p5 = p4 - p1;          // UB; p4 and p1 point to Derived
    const Derived* p6 = &a[1];
    const Derived* p7 = p6 + 1; // ok; cv-qualifiers don't matter
    

अमान्य पदों की संख्या से स्थानांतरण

बिल्ट-इन शिफ्ट ऑपरेटर के लिए, दायाँ ऑपरेंड नॉनएक्टिव होना चाहिए और प्रमोटेड लेफ्ट ऑपरेंड की बिट चौड़ाई से कम होना चाहिए। अन्यथा, व्यवहार अपरिभाषित है।

const int a = 42;
const int b = a << -1; // UB
const int c = a << 0;  // ok
const int d = a << 32; // UB if int is 32 bits or less
const int e = a >> 32; // also UB if int is 32 bits or less
const signed char f = 'x';
const int g = f << 10; // ok even if signed char is 10 bits or less;
                       // int must be at least 16 bits

[[नोटरी]] फ़ंक्शन से वापस लौटना

सी ++ 11

मानक से उदाहरण, [dcl.attr.noreturn]:

[[ noreturn ]] void f() {
  throw "error"; // OK
}
[[ noreturn ]] void q(int i) { // behavior is undefined if called with an argument <= 0
  if (i > 0)
    throw "positive";
}

उस वस्तु को नष्ट करना जो पहले ही नष्ट हो चुकी है

इस उदाहरण में, एक विध्वंसक को स्पष्ट रूप से एक वस्तु के लिए लगाया जाता है जो बाद में स्वचालित रूप से नष्ट हो जाएगा।

struct S {
    ~S() { std::cout << "destroying S\n"; }
};
int main() {
    S s;
    s.~S();
} // UB: s destroyed a second time here

एक समान समस्या तब होती है जब एक std::unique_ptr<T> स्वचालित या स्थिर भंडारण अवधि के साथ एक T पर इंगित करने के लिए बनाया जाता है।

void f(std::unique_ptr<S> p);
int main() {
    S s;
    std::unique_ptr<S> p(&s);
    f(std::move(p)); // s destroyed upon return from f
}                    // UB: s destroyed

एक वस्तु को दो बार नष्ट करने का दूसरा तरीका है दो shared_ptr दोनों एक दूसरे के साथ स्वामित्व साझा किए बिना वस्तु का प्रबंधन करना।

void f(std::shared_ptr<S> p1, std::shared_ptr<S> p2);
int main() {
    S* p = new S;
    // I want to pass the same object twice...
    std::shared_ptr<S> sp1(p);
    std::shared_ptr<S> sp2(p);
    f(sp1, sp2);
} // UB: both sp1 and sp2 will destroy s separately
// NB: this is correct:
// std::shared_ptr<S> sp(p);
// f(sp, sp);

अनंत टेम्पलेट पुनरावृत्ति

मानक से उदाहरण, [temp.inst] / 17:

template<class T> class X {
    X<T>* p; // OK
    X<T*> a; // implicit generation of X<T> requires
             // the implicit instantiation of X<T*> which requires
             // the implicit instantiation of X<T**> which ...
};


Modified text is an extract of the original Stack Overflow Documentation
के तहत लाइसेंस प्राप्त है CC BY-SA 3.0
से संबद्ध नहीं है Stack Overflow