क्या C ++ मानक किसी प्रोग्राम को क्रैश करने के लिए एक गैर-विनिर्मित बूल की अनुमति देता है?


500

मुझे पता है कि C ++ में एक "अपरिभाषित व्यवहार" बहुत संकलक को कुछ भी करने की अनुमति दे सकता है जो वह चाहता है। हालांकि, मेरे पास एक दुर्घटना थी जिसने मुझे आश्चर्यचकित कर दिया, क्योंकि मैंने माना कि कोड काफी सुरक्षित था।

इस मामले में, वास्तविक समस्या केवल विशिष्ट संकलक का उपयोग करके एक विशिष्ट मंच पर हुई, और केवल अगर अनुकूलन सक्षम किया गया था।

मैंने समस्या को पुन: उत्पन्न करने और इसे अधिकतम करने के लिए कई चीजों की कोशिश की। यहाँ एक फ़ंक्शन का एक्सट्रेक्ट है Serialize, जो एक बूल पैरामीटर लेगा, और स्ट्रिंग trueया falseकिसी मौजूदा डेस्टिनेशन बफर को कॉपी करेगा ।

क्या यह फ़ंक्शन एक कोड समीक्षा में होगा, यह बताने का कोई तरीका नहीं होगा कि यह, वास्तव में, दुर्घटना हो सकती है अगर बूल पैरामीटर एक अनैतिक मूल्य था?

// Zero-filled global buffer of 16 characters
char destBuffer[16];

void Serialize(bool boolValue) {
    // Determine which string to print based on boolValue
    const char* whichString = boolValue ? "true" : "false";

    // Compute the length of the string we selected
    const size_t len = strlen(whichString);

    // Copy string into destination buffer, which is zero-filled (thus already null-terminated)
    memcpy(destBuffer, whichString, len);
}

यदि यह कोड 5.0.0 + अनुकूलन के साथ निष्पादित किया जाता है, तो यह दुर्घटनाग्रस्त हो सकता है।

अपेक्षित टर्नरी-ऑपरेटर boolValue ? "true" : "false"मेरे लिए पर्याप्त सुरक्षित था, मैं यह मान रहा था, "जो भी कचरा मूल्य है वह boolValueमायने नहीं रखता है, क्योंकि यह किसी भी तरह से सही या गलत का मूल्यांकन करेगा।"

मेरे पास एक कंपाइलर एक्सप्लोरर का उदाहरण है, जो डिस्सैम्प में समस्या दिखाता है, यहाँ पूरा उदाहरण है। नोट: इस समस्या को हल करने के लिए, मैंने जो संयोजन काम किया है, वह -O2 अनुकूलन के साथ Clang 5.0.0 का उपयोग करके है।

#include <iostream>
#include <cstring>

// Simple struct, with an empty constructor that doesn't initialize anything
struct FStruct {
    bool uninitializedBool;

   __attribute__ ((noinline))  // Note: the constructor must be declared noinline to trigger the problem
   FStruct() {};
};

char destBuffer[16];

// Small utility function that allocates and returns a string "true" or "false" depending on the value of the parameter
void Serialize(bool boolValue) {
    // Determine which string to print depending if 'boolValue' is evaluated as true or false
    const char* whichString = boolValue ? "true" : "false";

    // Compute the length of the string we selected
    size_t len = strlen(whichString);

    memcpy(destBuffer, whichString, len);
}

int main()
{
    // Locally construct an instance of our struct here on the stack. The bool member uninitializedBool is uninitialized.
    FStruct structInstance;

    // Output "true" or "false" to stdout
    Serialize(structInstance.uninitializedBool);
    return 0;
}

ऑप्टिमाइज़र की वजह से समस्या उत्पन्न होती है: यह समझने के लिए पर्याप्त चतुर था कि तार "सच" और "झूठे" केवल लंबाई में भिन्न होते हैं 1. इसलिए वास्तव में लंबाई की गणना करने के बजाय, यह स्वयं बूल के मूल्य का उपयोग करता है, जिसे चाहिए तकनीकी रूप से या तो 0 या 1 हो, और इस तरह से जाता है:

const size_t len = strlen(whichString); // original code
const size_t len = 5 - boolValue;       // clang clever optimization

हालांकि यह "चतुर" है, इसलिए बोलने के लिए, मेरा सवाल है: क्या सी ++ मानक एक संकलक को यह मानने की अनुमति देता है कि एक बूल में केवल '0' या '1' का आंतरिक संख्यात्मक प्रतिनिधित्व हो सकता है और इस तरह से इसका उपयोग कर सकते हैं?

या क्या यह कार्यान्वयन-परिभाषित का मामला है, जिस स्थिति में कार्यान्वयन ने यह मान लिया है कि उसके सभी बूल केवल 0 या 1 होंगे, और कोई अन्य मान अपरिभाषित व्यवहार क्षेत्र है?


200
बहुत अच्छा सवाल है। यह एक ठोस चित्रण है कि कैसे अपरिभाषित व्यवहार सिर्फ एक सैद्धांतिक चिंता नहीं है। जब लोग कहते हैं कि यूबी के परिणामस्वरूप कुछ भी हो सकता है, तो यह "कुछ भी" वास्तव में काफी आश्चर्यजनक हो सकता है। कोई यह मान सकता है कि अपरिभाषित व्यवहार अभी भी पूर्वानुमान योग्य तरीकों से प्रकट होता है, लेकिन इन दिनों आधुनिक ऑप्टिमाइज़र के साथ यह बिल्कुल भी सच नहीं है। ओपी ने MCVE बनाने के लिए समय लिया, समस्या की पूरी जांच की, गड़बड़ी का निरीक्षण किया, और इसके बारे में एक स्पष्ट, सीधा सवाल पूछा। अधिक के लिए नहीं पूछ सकता है।
जॉन कुगेलमैन

7
ध्यान रखें कि "गैर-शून्य का मूल्यांकन true" करने के लिए "बूल के लिए असाइनमेंट" सहित बुलियन संचालन के बारे में एक नियम है (जो कि static_cast<bool>()बारीकियों के आधार पर स्पष्ट रूप से आह्वान कर सकता है )। हालांकि यह boolसंकलक द्वारा चुने गए के आंतरिक प्रतिनिधित्व के बारे में एक आवश्यकता नहीं है।
यूरो मिकेली

2
टिप्पणियाँ विस्तारित चर्चा के लिए नहीं हैं; इस वार्तालाप को बातचीत में स्थानांतरित कर दिया गया है ।
शमूएल ल्यू

3
बहुत ही संबंधित नोट पर, यह द्विआधारी असंगति का एक "मज़ेदार" स्रोत है। यदि आपके पास एक ABI A है जो एक फ़ंक्शन को कॉल करने से पहले शून्य-पैड मान देता है, लेकिन फ़ंक्शन को ऐसे संकलित करता है कि मानदंड शून्य-पैडेड हैं, और एक ABI B जो विपरीत है (शून्य-पैड नहीं है, लेकिन शून्य नहीं मानता है -पेडेड पैरामीटर्स), यह ज्यादातर काम करेगा, लेकिन बी एबीआई का उपयोग करने वाला एक फ़ंक्शन उन मुद्दों का कारण होगा यदि यह ए एबीआई का उपयोग करके फ़ंक्शन को कॉल करता है जो 'छोटा' पैरामीटर लेता है। IIRC आपके पास cl86 और ICC के साथ x86 पर है।
टीएलडब्ल्यू

1
@ टीएलडब्ल्यू: हालांकि मानक को यह आवश्यकता नहीं है कि कार्यान्वयन कॉलिंग का कोई साधन प्रदान करते हैं या बाहर के कोड से कॉल किया जा रहा है, यह कार्यान्वयन के लिए ऐसी चीजों को निर्दिष्ट करने में मददगार होगा जहां वे प्रासंगिक हैं (कार्यान्वयन जहां ऐसे विवरण नहीं हैं प्रासंगिक ऐसी विशेषताओं को अनदेखा कर सकता है)।
सुपरकैट

जवाबों:


285

हां, आईएसओ सी ++ इस विकल्प को बनाने के लिए कार्यान्वयन की अनुमति देता है (लेकिन इसकी आवश्यकता नहीं है)।

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

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


आप x86-64 सिस्टम V ABI के लिए संकलन कर रहे हैं , जो निर्दिष्ट करता है कि boolएक रजिस्टर में एक फ़ंक्शन arg बिट-पैटर्न false=0औरtrue=1 रजिस्टर 1 के निम्न 8 बिट्स द्वारा दर्शाया गया है । स्मृति में, boolएक 1-बाइट प्रकार है जो फिर से 0 या 1 का पूर्णांक मान होना चाहिए।

(एक एबीआई कार्यान्वयन विकल्पों का एक सेट है जो एक ही मंच के लिए संकलक सहमत होते हैं ताकि वे कोड बना सकें जो एक दूसरे के कार्यों को कॉल करते हैं, जिसमें प्रकार आकार, संरचना लेआउट नियम और कॉलिंग कन्वेंशन शामिल हैं।)

ISO C ++ इसे निर्दिष्ट नहीं करता है, लेकिन यह ABI निर्णय व्यापक है क्योंकि यह bool-> int रूपांतरण सस्ते (सिर्फ शून्य-विस्तार) बनाता है । मैं किसी भी ABI के बारे में नहीं जानता जो boolकिसी आर्किटेक्चर के लिए 0 या 1 के लिए कंपाइलर को मानने नहीं देती (न कि सिर्फ x86)। यह कम बिट को फ्लिप करने के !myboolसाथ अनुकूलन की अनुमति देता है xor eax,1: कोई भी संभावित कोड जो सिंगल सीपीयू इंस्ट्रक्शन में 0 और 1 के बीच बिट / पूर्णांक / बूल को फ्लिप कर सकता है । या a&&bएक बिट वाइज और boolप्रकारों के लिए संकलन । कुछ कंपाइलर असल में बुलियन मान का फायदा उठाते हैं क्योंकि कंपाइलर में 8 बिट होते हैं। क्या उन पर परिचालन अक्षम है?

सामान्य तौर पर, जैसा कि नियम के अनुसार संकलक उन चीजों का लाभ उठाने की अनुमति देता है जो लक्ष्य प्लेटफ़ॉर्म पर सत्य हैं, जिनके लिए संकलित किया जा रहा है , क्योंकि अंतिम परिणाम निष्पादन योग्य कोड होगा जो C ++ स्रोत के समान बाह्य-दृश्य व्यवहार को लागू करता है। (उन सभी प्रतिबंधों के साथ जो अपरिभाषित व्यवहार वास्तव में "बाहरी रूप से दिखाई देते हैं": एक डिबगर के साथ नहीं, बल्कि एक अन्य सूत्र से एक सुव्यवस्थित / कानूनी C ++ प्रोग्राम में।

संकलक निश्चित रूप से अपने कोड पीढ़ी में एक ABI गारंटी का पूरा लाभ लेने, और जैसे आप पाया जो अनुकूलन कर कोड बनाने के लिए अनुमति दी है strlen(whichString)करने के लिए
5U - boolValue
(बीटीडब्लू, यह अनुकूलन एक तरह का चतुर है, लेकिन शायद शॉर्टसाइट बनाम ब्रांचिंग और memcpyतत्काल डेटा 2 के स्टोर के रूप में इनलाइनिंग ।)

या संकलक बिंदुओं की एक तालिका बना सकता है और इसे पूर्णांक मान के साथ अनुक्रमित कर सकता है bool, फिर से यह मानकर कि यह 0 या 1. है ( यह संभावना है कि @ Barmar का सुझाव दिया गया है ।)


__attribute((noinline))ऑप्टिमाइज़ेशन के साथ आपके कंस्ट्रक्टर ने स्टैक से सिर्फ एक बाइट को लोड करने के लिए क्लैंग के रूप में इस्तेमाल किया uninitializedBool। यह ऑब्जेक्ट के लिए अंतरिक्ष बनाया mainके साथ push rax(जो छोटे और विभिन्न कारण के लिए है के रूप में कुशल के रूप में के बारे में sub rsp, 8), इसलिए जो कुछ भी कचरा प्रवेश पर अल में था करने के लिए mainमूल्य इसके लिए प्रयोग किया जाता है uninitializedBool। यही कारण है कि आपको वास्तव में ऐसे मूल्य मिले हैं जो अभी नहीं थे 0

5U - random garbageआसानी से एक बड़े अहस्ताक्षरित मूल्य में लिपटे जा सकते हैं, बिना किसी स्मृति के जाने के लिए यादगार। गंतव्य स्टैटिक स्टोरेज में है, स्टैक नहीं, इसलिए आप रिटर्न एड्रेस या कुछ और नहीं लिख रहे हैं।


अन्य कार्यान्वयन विभिन्न विकल्प, जैसे false=0और कर सकते हैं true=any non-zero value। फिर क्लेंग शायद यूबी के इस विशिष्ट उदाहरण के लिए कोड को क्रैश नहीं करेगा । (लेकिन अगर यह चाहते थे, तब भी इसे अनुमति दी जाएगी।) मुझे किसी भी कार्यान्वयन के बारे में नहीं पता है जो x86-64 के लिए कुछ भी चुनता है bool, लेकिन सी ++ मानक कई चीजों की अनुमति देता है जो कोई भी करता है या करना भी नहीं चाहेगा। हार्डवेयर जो वर्तमान सीपीयू की तरह है।

आईएसओ C ++ इसे अनिर्दिष्ट छोड़ देता है कि जब आप किसी वस्तु के वस्तु निरूपण की जांच करेंगे या संशोधित करेंगे तो आप क्या पाएंगेbool । (उदाहरण के द्वारा memcpying boolमें unsigned charहै, जो आप क्योंकि ऐसा करने की अनुमति कर रहे हैं char*कर सकते हैं उर्फ कुछ भी। और unsigned charकोई पैडिंग बिट्स के लिए गारंटी है, तो सी ++ मानक औपचारिक रूप से आप किसी भी यूबी के बिना वस्तु अभ्यावेदन hexdump जाने है। वस्तु कॉपी करने के लिए सूचक कास्टिंग प्रतिनिधित्व char foo = my_bool, निश्चित रूप से असाइन करने से अलग है , इसलिए 0 या 1 के लिए बूलियनकरण नहीं होगा और आपको कच्ची वस्तु प्रतिनिधित्व मिलेगा।)

आप संकलक से इस निष्पादन पथ पर UB को आंशिक रूप से "छिपा" रहे हैंnoinline । यहां तक ​​कि अगर यह इनलाइन नहीं करता है, हालांकि, इंटरप्रोडेक्टोरल ऑप्टिमाइज़ेशन अभी भी फ़ंक्शन का एक संस्करण बना सकता है जो किसी अन्य फ़ंक्शन की परिभाषा पर निर्भर करता है। (सबसे पहले, क्लैंग एक निष्पादन योग्य बना रहा है, न कि एक यूनिक्स साझा पुस्तकालय जहां प्रतीक-अंतर्संबंध हो सकता है। दूसरा, परिभाषा के अंदर की class{}परिभाषा इसलिए सभी अनुवाद इकाइयों में एक ही परिभाषा होनी चाहिए। जैसे inlineकीवर्ड के साथ ।)

इसलिए एक संकलक परिभाषा के रूप में सिर्फ ( retया ud2अवैध निर्देश) का उत्सर्जन कर सकता है main, क्योंकि mainअपरिहार्य रूप से शीर्ष पर शुरू होने वाले निष्पादन का मार्ग अपरिभाषित व्यवहार का सामना करता है। (जो संकलक संकलन समय पर देख सकता है यदि उसने गैर-इनलाइन निर्माता के माध्यम से पथ का पालन करने का निर्णय लिया है।)

यूबी का सामना करने वाला कोई भी कार्यक्रम अपने पूरे अस्तित्व के लिए पूरी तरह से अपरिभाषित है। लेकिन एक फ़ंक्शन या if()शाखा के अंदर यूबी जो वास्तव में कभी नहीं चलता है, बाकी कार्यक्रम को भ्रष्ट नहीं करता है। व्यवहार में इसका मतलब है कि संकलक एक अवैध निर्देश का उत्सर्जन करने का निर्णय ले सकते हैं, या किसी retभी चीज़ का उत्सर्जन नहीं कर सकते हैं और अगले ब्लॉक / फ़ंक्शन में गिर सकते हैं, पूरे मूल ब्लॉक के लिए जो यूबी को शामिल या लीड करने के लिए संकलन समय पर साबित हो सकता है।

जीसीसी और व्यवहार में बजना है वास्तव में कभी कभी फेंकना ud2बजाय भी निष्पादन के पथ है कि कोई मतलब के लिए कोड उत्पन्न करने की कोशिश कर के, यूबी पर। या गैर- voidसमारोह के अंत में गिरने जैसे मामलों के लिए , जीसीसी कभी-कभी एक retनिर्देश छोड़ देगा । यदि आप सोच रहे थे कि "मेरा कार्य RAX में जो भी कचरा है, उसके साथ वापस आ जाएगा", तो आप गलती से गलत हैं। आधुनिक C ++ कंपाइलर भाषा का व्यवहार किसी पोर्टेबल असेंबली भाषा की तरह नहीं करते हैं। आपके प्रोग्राम को वास्तव में मान्य होना चाहिए C ++, इस बारे में धारणा बनाए बिना कि आपके फ़ंक्शन का स्टैंड-अलोन नॉन इनलाइन संस्करण कैसा हो सकता है।

एक और मज़ेदार उदाहरण यह है कि एमएमए 64 पर एमएमएफ़ मेमोरी को कभी-कभी सीगफॉल्ट तक पहुंच क्यों नहीं दिया जाता है ? । x86 अनइंस्टॉल किए गए पूर्णांकों पर गलती नहीं करता है, है ना? तो एक मिथ्या uint16_t*समस्या क्यों होगी ? क्योंकि alignof(uint16_t) == 2, उस धारणा का उल्लंघन करने पर SSE2 के साथ ऑटो-वेक्टरिंग होने पर सेगफॉल्ट हुआ।

यह भी देखें कि हर सी प्रोग्रामर को अपरिहार्य व्यवहार # 1/3 के बारे में जानना चाहिए , एक क्लैंग डेवलपर द्वारा एक लेख।

मुख्य बिंदु: यदि संकलक ने संकलित समय पर UB को देखा, तो यह आपके कोड के माध्यम से पथ को "तोड़" सकता है (आश्चर्य से बाहर निकल सकता है) जो ABB को लक्षित करने पर भी UB का कारण बनता है, जहां किसी भी बिट-पैटर्न के लिए एक वैध वस्तु प्रतिनिधित्व है bool

प्रोग्रामर द्वारा कई गलतियों के प्रति कुल शत्रुता की अपेक्षा करें, विशेष रूप से आधुनिक संकलक के बारे में बातें। यही कारण है कि आपको -Wallचेतावनी का उपयोग और ठीक करना चाहिए । C ++ एक उपयोगकर्ता के अनुकूल भाषा नहीं है, और C ++ में कुछ भी असुरक्षित हो सकता है, भले ही वह उस लक्ष्य पर सुरक्षित हो जो आप के लिए संकलन कर रहे हैं। (उदाहरण के लिए हस्ताक्षरित अतिप्रवाह U ++ C ++ में है और संकलक मानेंगे कि ऐसा तब नहीं होता है, जब तक कि आप 2 पूरक x86 के लिए संकलन नहीं करते हैं, जब तक आप उपयोग नहीं करते हैं clang/gcc -fwrapv।)

कंपाइल-टाइम-दृश्यमान यूबी हमेशा खतरनाक होता है, और यह सुनिश्चित करना बहुत कठिन है (लिंक-टाइम ऑप्टिमाइज़ेशन के साथ) जो आपने यूबी को वास्तव में कंपाइलर से छिपाया है और इस तरह से यह उत्पन्न कर सकता है कि किस तरह का एएसएम है।

अति-नाटकीय होने के लिए नहीं; अक्सर कंपाइलर आपको कुछ चीज़ों से दूर कर देते हैं और कोड को छोड़ देते हैं जैसे कि आप उम्मीद कर रहे हैं कि कुछ यूबी है। लेकिन शायद यह भविष्य में एक समस्या होगी यदि कंपाइलर देव कुछ अनुकूलन को लागू करते हैं जो मूल्य-सीमाओं के बारे में अधिक जानकारी प्राप्त करते हैं (जैसे कि एक चर गैर-नकारात्मक है, शायद यह x86- पर मुक्त शून्य-विस्तार के लिए साइन-एक्सटेंशन का अनुकूलन करने की अनुमति देता है- 64)। उदाहरण के लिए, वर्तमान gcc और clang में, करना हमेशा-झूठ के रूप में tmp = a+INT_MINअनुकूलन नहीं करता है a<0, केवल वह tmpहमेशा नकारात्मक होता है। (क्योंकि INT_MIN+ a=INT_MAXयह 2 के पूरक लक्ष्य पर नकारात्मक है, और aहै कि तुलना में किसी भी अधिक नहीं हो सकता है।)

इसलिए gcc / clang वर्तमान में गणना के इनपुट के लिए श्रेणी जानकारी प्राप्त करने के लिए पीछे नहीं हटते हैं , केवल बिना हस्ताक्षर किए अतिप्रवाह की धारणा के आधार पर परिणामों पर: गॉडबोल्ट पर उदाहरण । मुझे नहीं पता कि यह अनुकूलन उपयोगकर्ता-मित्रता या क्या के नाम पर जानबूझकर "चूक" गया है।

यह भी ध्यान दें कि कार्यान्वयन (उर्फ कंपाइलर) को आईएसओ सी ++ के अपरिभाषित होने वाले व्यवहार को परिभाषित करने की अनुमति है । उदाहरण के लिए, इंटेल के आंतरिक (जैसे _mm_add_ps(__m128, __m128)मैनुअल SIMD वेक्टराइजेशन के लिए) का समर्थन करने वाले सभी कंपाइलर्स को गलत-संरेखित बिंदु बनाने की अनुमति देनी चाहिए, जो कि C ++ में UB है, भले ही आप उन्हें डीरेंस करें। एक या नहीं, __m128i _mm_loadu_si128(const __m128i *)एक गलत तरीके से __m128i*आर्ग ले कर भारित नहीं करता है । हार्डवेयर वेक्टर पॉइंटर और संबंधित अपरिभाषित व्यवहार के बीच `reinterpret_cast`ing है?void*char*

GNU C / C ++ -fwrapvसामान्य हस्ताक्षरित-अतिप्रवाह UB नियमों से अलग एक नकारात्मक हस्ताक्षरित संख्या (यहां तक ​​कि बिना ) के बाएं-शिफ्टिंग के व्यवहार को भी परिभाषित करता है। ( यह आईएसओ सी ++ में यूबी है , जबकि हस्ताक्षरित संख्याओं की सही शिफ्ट कार्यान्वयन-परिभाषित (तार्किक बनाम अंकगणित) हैं; अच्छी गुणवत्ता कार्यान्वयन एचडब्ल्यू पर अंकगणित का चयन करता है जिसमें अंकगणितीय दाएं बदलाव होते हैं, लेकिन आईएसओ सी ++ निर्दिष्ट नहीं करता है)। यह जीसीसी मैनुअल के इंटेगर खंड में प्रलेखित है , कार्यान्वयन-परिभाषित व्यवहार को परिभाषित करने के साथ कि सी मानकों को एक या दूसरे तरीके से परिभाषित करने के लिए कार्यान्वयन की आवश्यकता होती है।

गुणवत्ता के कार्यान्वयन के मुद्दे निश्चित रूप से हैं जो कंपाइलर डेवलपर्स की देखभाल करते हैं; वे आम तौर पर ऐसे संकलक बनाने की कोशिश नहीं कर रहे हैं जो जानबूझकर शत्रुतापूर्ण हैं, लेकिन सी ++ में सभी यूबी गड्ढों का लाभ उठाते हैं (सिवाय उनके जिन्हें वे परिभाषित करने के लिए चुनते हैं) कई बार बेहतर करने के लिए लगभग अप्रभेद्य हो सकते हैं।


फुटनोट 1 : ऊपरी 56 बिट्स कचरा हो सकता है जिसे कैलली को अनदेखा करना चाहिए, जैसे कि एक रजिस्टर की तुलना में संकीर्ण प्रकार के लिए।

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

उदाहरण के लिए, एक कॉलर a & 0x01010101ने RDI में गणना की होगी और कॉल करने से पहले इसका इस्तेमाल किसी और चीज़ के लिए किया होगा bool_func(a&1)। कॉलर दूर का अनुकूलन कर सकता है &1क्योंकि यह पहले से ही कम बाइट के हिस्से के रूप में किया था and edi, 0x01010101, और यह जानता है कि कैली को उच्च बाइट्स को अनदेखा करना आवश्यक है।

या अगर एक बूल को तीसरे आर्ग के रूप में पारित किया जाता है, तो शायद कोड-आकार के लिए अनुकूलन करने वाला एक कॉलर mov dl, [mem]इसके बजाय इसे लोड करता है movzx edx, [mem], जो आरडीएक्स के पुराने मूल्य (या अन्य आंशिक-रजिस्टर प्रभाव) के आधार पर एक झूठी निर्भरता की कीमत पर 1 बाइट की बचत करता है। सीपीयू मॉडल पर)। या mov dil, byte [r10]इसके बजाय पहले arg के लिए movzx edi, byte [r10], क्योंकि दोनों को वैसे भी REX उपसर्ग की आवश्यकता होती है।

यही कारण है कि बजना का उत्सर्जन करता है movzx eax, dilमें Serialize, के बजाय sub eax, edi। (पूर्णांक आर्गन्स के लिए, क्लैग इस ABI नियम का उल्लंघन करता है, इसके बजाय gcc के अनिर्दिष्ट व्यवहार पर निर्भर करता है और शून्य-बिट्स को 32 बिट्स तक सीमित करता है या साइन-एक्सटेंड करता है। किसी सूचक के लिए 32 बिट ऑफसेट को जोड़ने पर साइन या शून्य एक्सटेंशन की आवश्यकता होती है। x86-64 ABI; इसलिए मुझे यह देखने में दिलचस्पी थी कि यह उसी काम को नहीं करता है bool।)


फुटनोट 2: शाखाओं में movबँटने के बाद, आपके पास बस एक 4-बाइट , या 4-बाइट + 1-बाइट स्टोर होगा। लंबाई दुकान की चौड़ाई + ऑफसेट में निहित है।

OTOH, ग्लिबेक मेम्स्की दो 4-बाइट लोड / स्टोर करेगा जो एक ओवरलैप के साथ होता है जो लंबाई पर निर्भर करता है, इसलिए यह वास्तव में बूलियन पर पूरी तरह से सशर्त शाखाओं से मुक्त बनाने का काम करता है। देखें L(between_4_7):ब्लॉक glibc के memcpy / memmove में। या कम से कम, चंकी आकार का चयन करने के लिए मेमकी की शाखाओं में बूलियन के लिए उसी तरह से जाएं।

यदि movinlining , आप 2x -immediate + cmovऔर एक सशर्त ऑफसेट का उपयोग कर सकते हैं , या आप स्ट्रिंग डेटा को स्मृति में छोड़ सकते हैं।

या अगर इंटेल आइस लेक के लिए ट्यूनिंग ( फास्ट शॉर्ट आरईपी एमओवी सुविधा के साथ ), एक वास्तविक rep movsbइष्टतम हो सकता है। glibc उस सुविधा के साथ CPU पर छोटे आकारों के लिए memcpyउपयोग करना शुरू कर सकता है rep movsb, जिससे बहुत सी शाखाएँ बच जाती हैं।


यूबी का पता लगाने के लिए उपकरण और असमान मूल्यों का उपयोग

Gcc और clang में, आप -fsanitize=undefinedरन-टाइम इंस्ट्रूमेंटेशन को जोड़ने के लिए संकलित कर सकते हैं जो रन-टाइम पर होने वाले UB पर चेतावनी या त्रुटि देगा। हालांकि, यह इकाईगत चर नहीं पकड़ेगा। (क्योंकि यह एक "असमान" बिट के लिए जगह बनाने के लिए प्रकार के आकार में वृद्धि नहीं करता है)।

Https://developers.redhat.com/blog/2014/10/16/gcc-undefined-behavior-sanitizer-ubsan/ देखें

एकतरफा डेटा का उपयोग खोजने के लिए, क्लैंग / एलएलवीएम में एड्रेस सेनिटाइज़र और मेमोरी सैनिटाइज़र है। https://github.com/google/sanitizers/wiki/MemorySanitizerclang -fsanitize=memory -fPIE -pie अनइंस्टॉल किए गए वास्तविक छड़ों का पता लगाने के उदाहरण दिखाता है। यदि आप अनुकूलन के बिना संकलित करते हैं तो यह सबसे अच्छा काम कर सकता है , इसलिए चर के सभी पाठ वास्तव में asm में मेमोरी से लोड हो रहे हैं। वे दिखाते हैं कि इसका उपयोग -O2ऐसे मामले में किया जा रहा है जहां लोड दूर नहीं होगा। मैंने खुद इसकी कोशिश नहीं की है। (कुछ मामलों में, उदाहरण के लिए, किसी सरणी को समेटने से पहले किसी संचायक को आरंभीकृत नहीं करना, clang -O3 कोड का उत्सर्जन करेगा, जो एक सदिश रजिस्टर में होता है, जो कभी भी आरंभिक नहीं होता है। इसलिए अनुकूलन के साथ, आपके पास एक ऐसा मामला हो सकता है, जिसमें UB से जुड़ी कोई मेमोरी रीड नहीं है। । परंतु-fsanitize=memory उत्पन्न asm को बदलता है, और इसके लिए जाँच हो सकती है।]

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

MemorySanitizer, Valgrind (Memcheck टूल) में पाई गई कार्यक्षमता का सबसेट लागू करता है।

क्योंकि कॉल glibc को इस मामले के लिए काम करना चाहिए memcpyके साथ एक lengthगणना की अप्रारंभीकृत स्मृति से के आधार पर (पुस्तकालय के अंदर) एक शाखा में परिणाम होगा length। यदि यह एक पूरी तरह से शाखाहीन संस्करण को इनलाइन करता था जो सिर्फ इस्तेमाल cmov, इंडेक्सिंग और दो स्टोर करता था, तो यह काम नहीं कर सकता था।

memcheckइस तरह की समस्या के लिए वाल्ग्रिंड की इच्छा भी होगी, फिर से शिकायत न करें कि क्या कार्यक्रम केवल अनधिकृत डेटा के आसपास कॉपी करता है। लेकिन यह कहता है कि यह पता चलेगा कि "कंडेनशनल जंप या मूव किसी भी बाहरी रूप से दिखने वाले व्यवहार को पकड़ने की कोशिश करता है, जो एकतरफा डेटा पर निर्भर करता है।"

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


2
मैंने एक बदतर स्थिति देखी है जहां चर ने 8 बिट पूर्णांक की सीमा में नहीं, बल्कि पूरे सीपीयू रजिस्टर का मूल्य लिया था। और इटेनियम का एक बुरा हाल है, एक असिंचित चर का उपयोग सीधे दुर्घटनाग्रस्त कर सकता है।
जोशुआ

2
@ जोशुआ: ओह ठीक है, अच्छी बात है, इटेनियम की स्पष्ट अटकलें मानों को "एक नंबर नहीं" के बराबर मान देंगी, जैसे कि मान दोष का उपयोग करना।
पीटर कॉर्डेस

11
इसके अलावा, यह भी दिखाता है क्यों यूबी featurebug भाषाओं C और C ++ पहली जगह में के डिजाइन में पेश किया गया था: क्योंकि यह संकलक देता है वास्तव में स्वतंत्रता के इस प्रकार है, जो अब सबसे आधुनिक compilers की अनुमति दी गई है इन उच्च गुणवत्ता वाले प्रदर्शन करने के लिए ऐसी अनुकूलन जो C / C ++ ऐसी उच्च-प्रदर्शन मध्य-स्तरीय भाषाएँ बनाती हैं।
the_Sympathizer

2
और इसलिए C ++ कंपाइलर लेखकों और C ++ प्रोग्रामर्स के बीच उपयोगी प्रोग्राम लिखने की कोशिश जारी है। यह उत्तर, इस प्रश्न का उत्तर देने में पूरी तरह से व्यापक है, इसका उपयोग स्थैतिक विश्लेषण उपकरणों के विक्रेताओं के लिए विज्ञापन की नकल के रूप में भी किया जा सकता है ...
davidbak

4
@The_Sympathizer: यूबी को उन ग्राहकों को व्यवहार करने की अनुमति देने के लिए शामिल किया गया था जो अपने ग्राहकों के लिए सबसे उपयोगी होंगे । यह सुझाव देने का इरादा नहीं था कि सभी व्यवहारों को समान रूप से उपयोगी माना जाना चाहिए।
सुपरकैट

56

संकलक ग्रहण करने के लिए है कि एक बूलियन मान एक तर्क के रूप में पारित एक मान्य बूलियन मूल्य (यानी एक जो initialised कर दिया गया है या करने के लिए परिवर्तित है की अनुमति दी है trueया false)। true, वास्तव में वहाँ के विभिन्न अभ्यावेदन हो सकता है - मूल्य पूर्णांक 1 के रूप में ही नहीं है trueऔर false- लेकिन पैरामीटर उन दो मूल्यों, जहां "वैध प्रतिनिधित्व" कार्यान्वयन है में से एक में से कुछ वैध प्रतिनिधित्व होना चाहिए परिभाषित।

इसलिए यदि आप किसी को प्रारंभ करने में विफल रहते हैं bool, या यदि आप इसे किसी भिन्न प्रकार के कुछ सूचक के माध्यम से अधिलेखित करने में सफल होते हैं, तो संकलक की धारणा गलत होगी और अपरिभाषित व्यवहार को सुनिश्चित करेगा। आपको चेतावनी दी गई थी:

50) इस अंतर्राष्ट्रीय मानक द्वारा वर्णित अपरिभाषित तरीकों को "अपरिभाषित" के रूप में उपयोग करते हुए, जैसे कि एक असिंचित स्वचालित वस्तु के मूल्य की जांच करके, यह व्यवहार करने का कारण हो सकता है जैसे कि यह न तो सही है और न ही गलत है। (फुटनोट के पैरा 6 से note6.9.1, मौलिक प्रकार)


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

4
@ShadowRanger आप हमेशा ऑब्जेक्ट प्रतिनिधित्व का सीधे निरीक्षण कर सकते हैं।
तृकां

7
@ शादोव्रेंजर: मेरा कहना है कि कार्यान्वयन प्रभारी है। यदि यह trueबिट प्रतिमान के मान्य अभ्यावेदन को सीमित करता है 1, तो यह इसका विशेषाधिकार है। यदि यह अभ्यावेदन का कुछ और सेट चुनता है, तो यह वास्तव में यहां बताए गए अनुकूलन का उपयोग नहीं कर सकता है। यदि यह उस विशेष प्रतिनिधित्व को चुनता है, तो यह कर सकता है। यह केवल आंतरिक रूप से सुसंगत होना चाहिए। आप इसे बाइट सरणी में कॉपी करके एक के प्रतिनिधित्व की जांच कर सकते हैंbool ; वह यूबी नहीं है (लेकिन यह कार्यान्वयन-परिभाषित है)
रिसी

3
हाँ, के अनुकूलन compilers (यानी वास्तविक दुनिया सी ++ कार्यान्वयन) अक्सर कभी कभी कोड है कि एक पर निर्भर करता है उत्सर्जित करेगा boolका एक सा पैटर्न होने 0या 1। वे boolहर बार जब वे इसे मेमोरी से पढ़ते हैं (या एक फ़ंक्शन arg धारण करने वाला रजिस्टर) फिर से नहीं करते हैं। यही बात जवाब दे रही है। उदाहरण : gcc4.7 + अनुकूलन कर सकते हैं return a||bकरने के लिए or eax, ediफ़ंक्शन में bool, या MSVC अनुकूलन कर सकते हैं a&bकरने के लिए test cl, dl। x86 testएक बिटवाइज़ है and , इसलिए अगर cl=1और dl=2परीक्षण के अनुसार झंडे सेट करता है cl&dl = 0
पीटर कॉर्डेस

5
अपरिभाषित व्यवहार के बारे में बात यह है कि संकलक को इसके बारे में कहीं अधिक निष्कर्ष निकालने की अनुमति है, उदाहरण के लिए यह मान लेना कि एक कोड पथ जो एक असंवैधानिक मूल्य तक पहुंचने के लिए नेतृत्व करेगा, कभी भी ऐसा नहीं किया जाता है, यह सुनिश्चित करने के लिए कि प्रोग्रामर की जिम्मेदारी ठीक है। । तो यह सिर्फ इस संभावना के बारे में नहीं है कि निम्न स्तर के मान शून्य या एक से भिन्न हो सकते हैं।
होल्गर

52

फ़ंक्शन स्वयं सही है, लेकिन आपके परीक्षण कार्यक्रम में, फ़ंक्शन को कॉल करने वाला स्टेटमेंट एक असमान परिवर्तनशील चर के मान का उपयोग करके अपरिभाषित व्यवहार का कारण बनता है।

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

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

एनबी। "अपरिभाषित व्यवहार का कारण _____ हो सकता है?" हमेशा "हाँ" है। यह वस्तुतः अपरिभाषित व्यवहार की परिभाषा है।


2
क्या पहला खंड सत्य है? क्या केवल एक अनइंस्टाल्यूटेड ट्रिगर यूबी की नकल करना bool?
जोशुआ ग्रीन

10
@JoshuaGreen देखें [dcl.init] / 12 "यदि एक मूल्यांकन द्वारा एक अनिश्चित मूल्य का उत्पादन किया जाता है, तो निम्न मामलों को छोड़कर व्यवहार अपरिभाषित है:" (और उन मामलों में से कोई भी अपवाद नहीं है bool)। कॉपी करने के लिए स्रोत का मूल्यांकन करने की आवश्यकता होती है
MM

8
@ जोशुआग्रीन और इसका कारण यह है कि आपके पास एक ऐसा प्लेटफ़ॉर्म हो सकता है जो कुछ प्रकारों के लिए कुछ अमान्य मानों तक पहुँचने पर हार्डवेयर फ़ॉल्ट को ट्रिगर करता है। इन्हें कभी-कभी "ट्रैप प्रतिनिधित्व" कहा जाता है।
डेविड श्वार्ट्ज

7
इटेनियम, जबकि अस्पष्ट, एक सीपीयू है जो अभी भी उत्पादन में है, ट्रैप मान हैं, और इसमें दो कम से कम अर्ध-आधुनिक सी ++ कंपाइलर (इंटेल / एचपी) हैं। इसका शाब्दिक अर्थ है true, falseऔर not-a-thingबुलियन के लिए मूल्य।
20

3
दूसरी तरफ, "क्या किसी मानक को किसी निश्चित तरीके से संसाधित करने के लिए मानक को सभी कंपाइलरों की आवश्यकता होती है" का उत्तर आम तौर पर "नहीं" है, यहां तक ​​कि / विशेषकर उन मामलों में जहां यह स्पष्ट है कि किसी भी गुणवत्ता के कंपाइलर को ऐसा करना चाहिए; अधिक स्पष्ट कुछ है, मानक के लेखकों के लिए वास्तव में यह कहने के लिए कम आवश्यकता होनी चाहिए।
सुपरकैट

23

एक बूल को केवल कार्यान्वयन-निर्भर मूल्यों को आंतरिक रूप से trueऔर उसके लिए उपयोग किए जाने वाले होल्ड को रखने की अनुमति है false, और उत्पन्न कोड मान सकता है कि यह केवल इन दो मूल्यों में से एक को धारण करेगा।

आमतौर पर, कार्यान्वयन पूर्णांक का उपयोग करेगा 0 के लिए falseऔर 1के लिए true, के बीच रूपांतरण सरल करने के लिए boolऔर int, और बनाने के if (boolvar)रूप में एक ही कोड उत्पन्न if (intvar)। उस स्थिति में, कोई कल्पना कर सकता है कि असाइनमेंट में टर्नेरी के लिए उत्पन्न कोड मूल्य को दो स्ट्रिंग्स के संकेत बिंदुओं के सूचकांक के रूप में उपयोग करेगा, अर्थात इसे कुछ इस तरह परिवर्तित किया जा सकता है:

// the compile could make asm that "looks" like this, from your source
const static char *strings[] = {"false", "true"};
const char *whichString = strings[boolValue];

यदि boolValueयह असिंचित है, तो यह वास्तव में किसी भी पूर्णांक मूल्य को पकड़ सकता है, जो तब stringsसरणी की सीमा के बाहर पहुंच का कारण होगा ।


1
@ सीड्स थैंक्स सैद्धांतिक रूप से, आंतरिक अभ्यावेदन इसके विपरीत हो सकते हैं कि वे कैसे पूर्णांक से / के लिए आते हैं, लेकिन यह विकृत होगा।
बमर

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

3
@Remz मैं केवल यह दिखाने के लिए सरणी का उपयोग कर रहा हूं कि उत्पन्न कोड क्या के बराबर हो सकता है, यह सुझाव नहीं कि कोई भी वास्तव में यह लिख देगा।
बरमार

1
@Remz मरम्मत boolकरने intके साथ *(int *)&boolValueऔर डिबगिंग उद्देश्यों के लिए इसे प्रिंट, अगर यह अलावा और कुछ है देखने 0या 1जब यह दुर्घटनाओं। अगर ऐसा है, तो यह इस सिद्धांत की बहुत पुष्टि करता है कि कंपाइलर इनलाइन को अनुकूलित कर रहा है-अगर एक सरणी के रूप में जो यह बताता है कि यह दुर्घटनाग्रस्त क्यों है।
हेवनार्ड

2
@MSalters: std::bitset<8>मुझे मेरे सभी अलग झंडे के लिए अच्छे नाम नहीं देते। वे जो हैं, उसके आधार पर, यह महत्वपूर्ण हो सकता है।
मार्टिन बोनर

15

अपने प्रश्न को बहुत अधिक संक्षेप में, आप पूछ रहे हैं कि क्या C ++ मानक एक संकलक को boolकेवल '0' या '1' का आंतरिक संख्यात्मक प्रतिनिधित्व दे सकता है और इसे इस तरह से उपयोग करने की अनुमति देता है?

मानक एक के आंतरिक प्रतिनिधित्व के बारे में कुछ नहीं कहता है bool। यह केवल परिभाषित करता है कि एक boolको कास्टिंग करते समय क्या होता हैint (या इसके विपरीत)। अधिकतर, इन अभिन्न रूपांतरणों के कारण (और तथ्य यह है कि लोग उन पर बहुत अधिक भरोसा करते हैं), संकलक 0 और 1 का उपयोग करेगा, लेकिन यह करने के लिए नहीं है (हालांकि यह किसी भी निम्न स्तर की एबीआई की बाधाओं का सम्मान करना है जो इसका उपयोग करता है) )।

इसलिए, संकलक, जब यह देखता boolहै कि यह मानने का हकदार है कि कहा गया है कि boolदोनों में true'या false' बिट पैटर्न हैं और कुछ भी ऐसा महसूस करते हैं। के लिए मान तो अगर trueऔर false1 और 0, क्रमशः, संकलक वास्तव में अनुकूलन करने के लिए अनुमति दी है कर रहे हैं strlenकरने के लिए 5 - <boolean value>। अन्य मजेदार व्यवहार संभव हैं!

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

  • आपका कोड काम कर रहा है जैसे आपने उससे अपेक्षा की थी
  • आपका कोड यादृच्छिक समय में विफल हो रहा है
  • आपका कोड बिल्कुल नहीं चलाया जा रहा है।

देखें कि प्रत्येक प्रोग्रामर को अपरिभाषित व्यवहार के बारे में क्या जानना चाहिए

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