क्या C का std के बराबर :: C ++ से कम है?


26

मैं हाल ही p < qमें सी में करने के अपरिभाषित व्यवहार पर एक सवाल का जवाब दे रहा था pऔर qविभिन्न वस्तुओं / सरणियों में संकेत कर रहे थे। मुझे यह सोचकर मिला: C ++ <में इस मामले में एक ही (अपरिभाषित) व्यवहार है , लेकिन यह मानक पुस्तकालय टेम्पलेट भी प्रदान करता है std::lessजो कि उसी चीज़ को वापस करने की गारंटी देता है <जब बिंदुओं की तुलना की जा सकती है, और जब वे नहीं कर सकते हैं तो कुछ सुसंगत क्रम वापस कर सकते हैं।

क्या C समान कार्यक्षमता के साथ कुछ प्रदान करता है जो मनमाने ढंग से पॉइंटर्स (एक ही प्रकार) से सुरक्षित रूप से तुलना करने की अनुमति देगा? मैंने C11 मानक को देखने की कोशिश की और कुछ भी नहीं पाया, लेकिन C में मेरा अनुभव C ++ की तुलना में छोटे परिमाण का आदेश है, इसलिए मैं आसानी से कुछ याद कर सकता था।


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

जवाबों:


20

एक फ्लैट मेमोरी मॉडल (मूल रूप से सब कुछ) के साथ कार्यान्वयन पर, uintptr_tबस काम करना होगा।

(लेकिन यह देखना चाहिए कि 64-बिट x86 में पॉइंटर तुलनाओं पर हस्ताक्षर किए गए या अहस्ताक्षरित होने चाहिए , इस बात की चर्चा के लिए कि क्या आपको साइन इन के रूप में संकेत करना चाहिए या नहीं, जिसमें वस्तुओं के बाहर पॉइंटर्स बनाने के मुद्दे शामिल हैं जो सी में यूबी है।)

लेकिन गैर फ्लैट स्मृति मॉडल के साथ सिस्टम मौजूद हैं, और उनके बारे में सोच मदद कर सकते हैं वर्तमान स्थिति स्पष्ट, सी की तरह ++ के लिए विभिन्न चश्मा होने <बनाम std::less


बिंदुओं के बिंदु का एक हिस्सा <C में UB होने वाली वस्तुओं को अलग करने के लिए (या कुछ C ++ संशोधनों में कम से कम अनिर्दिष्ट) गैर-फ्लैट मेमोरी मॉडल सहित अजीब मशीनों के लिए अनुमति देने के लिए है।

एक प्रसिद्ध उदाहरण x86-16 वास्तविक मोड है जहां संकेत खंड हैं: ऑफसेट, 20-बिट रैखिक पते के माध्यम से बनाते हैं (segment << 4) + offset। एक ही रेखीय पते को कई अलग-अलग seg: ऑफ कॉम्बिनेशन द्वारा दर्शाया जा सकता है।

std::lessअजीब आईएसए पर पॉइंटर्स पर सी ++ को महंगा होने की आवश्यकता हो सकती है , उदाहरण के लिए एक सेगमेंट को "सामान्य" करें: ऑफसेट करने के लिए x86-16 पर ऑफसेट <= 15. हालांकि, इसे लागू करने का कोई पोर्टेबल तरीका नहीं है । एक uintptr_t(या पॉइंटर ऑब्जेक्ट के ऑब्जेक्ट-प्रतिनिधित्व) को सामान्य करने के लिए आवश्यक हेरफेर कार्यान्वयन-विशिष्ट है।

लेकिन यहां तक ​​कि सिस्टम पर जहां C ++ std::lessमहंगा होना चाहिए, <नहीं होना चाहिए। उदाहरण के लिए, एक "बड़े" मेमोरी मॉडल को मानते हुए, जहां कोई वस्तु एक सेगमेंट के भीतर फिट होती है, <बस ऑफसेट भाग की तुलना कर सकती है और खंड भाग के साथ परेशान भी नहीं कर सकती है। (एक ही ऑब्जेक्ट के अंदर पॉइंटर्स का एक ही सेगमेंट होगा, और अन्यथा यह सी। सी। + + 17 में यूबी है जो केवल "अनिर्दिष्ट" में बदल गया है, जो अभी भी सामान्यीकरण की अनुमति दे सकता है और केवल ऑफसेट की तुलना कर सकता है।) यह किसी भी हिस्से को सभी पॉइंटर्स मान रहा है। एक वस्तु हमेशा एक ही segमूल्य का उपयोग करें , कभी सामान्य नहीं। यह वह है जिसे आप "विशाल" मेमोरी मॉडल के विपरीत "बड़े" की आवश्यकता के लिए एक एबीआई की अपेक्षा करेंगे। ( टिप्पणियों में चर्चा देखें )।

(इस तरह के मेमोरी मॉडल में अधिकतम ऑब्जेक्ट का आकार 64kiB हो सकता है, उदाहरण के लिए, लेकिन बहुत अधिक अधिकतम कुल पता स्थान जिसमें ऐसी कई अधिकतम आकार की वस्तुओं के लिए जगह है। आईएसओ सी कार्यान्वयनों को ऑब्जेक्ट आकार पर सीमा की अनुमति देता है जो कि तुलना में कम है। अधिकतम मान (अहस्ताक्षरित) size_tका प्रतिनिधित्व कर सकते हैं, SIZE_MAXउदाहरण के लिए, यहां तक ​​कि फ्लैट मेमोरी मॉडल सिस्टम पर भी, GNU C अधिकतम आकार की सीमा को आकार देता है PTRDIFF_MAXताकि गणना पर हस्ताक्षर किए गए अतिप्रवाह को अनदेखा किया जा सके।) इस उत्तर और टिप्पणियों में चर्चा देखें ।

यदि आप किसी खंड से बड़ी वस्तुओं को अनुमति देना चाहते हैं, तो आपको एक "विशाल" मेमोरी मॉडल की p++आवश्यकता होती है, जिसमें किसी सरणी के माध्यम से लूप करते समय, या इंडेक्सिंग / पॉइंटर अंकगणित करते समय एक पॉइंटर के ऑफसेट भाग को ओवरफ्लो करने के बारे में चिंता करने की आवश्यकता होती है। यह हर जगह धीमी कोड की ओर जाता है, लेकिन शायद इसका मतलब यह p < qहोगा कि अलग-अलग ऑब्जेक्ट्स के लिए पॉइंटर्स के लिए काम करना होगा, क्योंकि एक "विशाल" मेमोरी मॉडल को लक्षित करने वाला कार्यान्वयन सामान्य रूप से सभी पॉइंटर्स को हर समय सामान्य रखने का चयन करेगा। देखें कि पास, दूर और विशाल संकेत क्या हैं? - x86 वास्तविक मोड के लिए कुछ वास्तविक सी कंपाइलरों के पास "विशाल" मॉडल के लिए संकलन करने का एक विकल्प था जहां सभी पॉइंटर्स "विशाल" तक डिफ़ॉल्ट रूप से घोषित किए गए जब तक कि अन्यथा घोषित न हो।

x86 रियल-मोड सेगमेंटेशन केवल गैर-फ्लैट मेमोरी मॉडल संभव नहीं है , यह केवल यह बताने के लिए एक उपयोगी ठोस उदाहरण है कि इसे C / C ++ कार्यान्वयन द्वारा कैसे नियंत्रित किया जाता है। वास्तविक जीवन में, कार्यान्वयन ने आईएसओ सी को farबनाम nearपॉइंटर्स की अवधारणा के साथ विस्तारित किया , प्रोग्रामर को यह चुनने की अनुमति दी कि वे कुछ सामान्य डेटा सेगमेंट के सापेक्ष 16-बिट ऑफ़सेट भाग के आसपास बस स्टोरिंग / पासिंग से दूर हो सकते हैं।

लेकिन एक शुद्ध आईएसओ सी कार्यान्वयन के लिए एक छोटे मेमोरी मॉडल (16-बिट पॉइंटर्स के साथ समान 64kiB में कोड को छोड़कर) या बड़े या विशाल सभी पॉइंटर्स 32-बिट होने के बीच चुनना होगा। कुछ लूप केवल ऑफ़सेट भाग को बढ़ाकर अनुकूलित कर सकते हैं, लेकिन पॉइंटर ऑब्जेक्ट्स को छोटे होने के लिए अनुकूलित नहीं किया जा सकता है।


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

या शायद नहीं: इसमें एक विशेष सेगमेंट टेबल या कुछ से कुछ देखना शामिल हो सकता है, जैसे कि वास्तविक मोड के बजाय x86 संरक्षित मोड जहां पते का सेगमेंट हिस्सा एक इंडेक्स है, न कि छोड़ा जाने वाला मान। आप संरक्षित मोड में आंशिक रूप से ओवरलैपिंग सेगमेंट सेट कर सकते हैं, और पते के सेगमेंट चयनकर्ता भागों को आवश्यक रूप से संबंधित खंड आधार पते के समान क्रम में भी आदेश नहीं दिया जा सकता है। यदि कोई GDT और / या LDT आपकी प्रक्रिया में पठनीय पृष्ठों पर मैप नहीं किया गया है, तो एक xg से संरक्षित मोड में xg से संरक्षित मोड में पॉइंटर: एक पॉइंटर को प्राप्त करना सिस्टम कॉल को शामिल कर सकता है।

(निश्चित रूप से मुख्यधारा के OS के लिए x86 एक फ्लैट मेमोरी मॉडल का उपयोग करते हैं ताकि सेगमेंट बेस हमेशा 0 हो (थ्रेड-लोकल स्टोरेज का उपयोग करके fsया gsसेगमेंट को छोड़कर ), और केवल 32-बिट या 64-बिट "ऑफ़सेट" भाग को पॉइंटर के रूप में उपयोग किया जाता है ।)

आप मैन्युअल रूप से विभिन्न विशिष्ट प्लेटफार्मों के लिए कोड जोड़ सकते हैं, जैसे डिफ़ॉल्ट मान फ्लैट, या #ifdefx86 वास्तविक मोड का पता लगाने के लिए कुछ और फिर uintptr_t16-बिट हिस्सों में विभाजित करने के लिए seg -= off>>4; off &= 0xf;उन हिस्सों को वापस 32-बिट संख्या में संयोजित करें।


यदि खंड बराबर नहीं है तो यह यूबी क्यों होगा?
एकोर्न

@ आंसर: कहने का मतलब है कि दूसरे तरीके से; तय की। एक ही ऑब्जेक्ट में पॉइंटर्स का एक ही सेगमेंट होगा, बाकी UB।
पीटर कॉर्ड्स

लेकिन आपको क्यों लगता है कि यह किसी भी मामले में यूबी है? (उल्टे तर्क या नहीं, वास्तव में मैंने या तो नोटिस नहीं किया था)
एकोर्न

p < qक्या C में UB है यदि वे विभिन्न वस्तुओं की ओर इशारा करते हैं, है न? मुझे पता p - qहै
पीटर कॉर्ड्स

1
@Acorn: वैसे भी, मैं एक ऐसा तंत्र नहीं देखता जो एलबी के बिना एक कार्यक्रम में उपनाम (अलग सेग: बंद, समान रैखिक पता) उत्पन्न करेगा। इसलिए ऐसा नहीं है कि कंपाइलर को उससे बचने के लिए अपने रास्ते से बाहर जाना पड़े; किसी वस्तु तक हर पहुंच उस वस्तु के segमूल्य और ऑफसेट का उपयोग करती है> = खंड के भीतर ऑफसेट जहां वह वस्तु शुरू होती है। C विभिन्न वस्तुओं की ओर संकेत करने वालों के बीच कुछ भी करने के लिए UB बनाता है, जैसे सामान tmp = a-bऔर फिर b[tmp]एक्सेस करना a[0]। खंडित सूचक अलियासिंग के बारे में यह चर्चा इस बात का एक अच्छा उदाहरण है कि डिजाइन-विकल्प क्यों समझ में आता है।
पीटर कॉर्ड्स

17

मैंने एक बार इसके चारों ओर एक रास्ता खोजने की कोशिश की और मुझे एक समाधान मिला जो वस्तुओं को ओवरलैप करने के लिए काम करता है और अधिकांश अन्य मामलों में संकलक "सामान्य" बात करता है।

आप पहली बार सुझाव को कैसे लागू कर सकते हैं कि बिना इंटरमीडिएट कॉपी के मानक सी में मेमोव को कैसे लागू किया जाए? और फिर अगर वह काम नहीं करता है uintptr( uintptr_tया तो उपलब्ध है या उपलब्ध है या unsigned long longनहीं पर निर्भर करता है के लिए एक आवरण प्रकार uintptr_t) और सबसे अधिक सटीक सटीक परिणाम प्राप्त करें (हालांकि यह शायद वैसे भी कोई फर्क नहीं पड़ेगा):

#include <stdint.h>
#ifndef UINTPTR_MAX
typedef unsigned long long uintptr;
#else
typedef uintptr_t uintptr;
#endif

int pcmp(const void *p1, const void *p2, size_t len)
{
    const unsigned char *s1 = p1;
    const unsigned char *s2 = p2;
    size_t l;

    /* Check for overlap */
    for( l = 0; l < len; l++ )
    {
        if( s1 + l == s2 || s1 + l == s2 + len - 1 )
        {
            /* The two objects overlap, so we're allowed to
               use comparison operators. */
            if(s1 > s2)
                return 1;
            else if (s1 < s2)
                return -1;
            else
                return 0;
        }
    }

    /* No overlap so the result probably won't really matter.
       Cast the result to `uintptr` and hope the compiler
       does the "usual" thing */
    if((uintptr)s1 > (uintptr)s2)
        return 1;
    else if ((uintptr)s1 < (uintptr)s2)
        return -1;
    else
        return 0;
}

5

क्या C समान कार्यक्षमता के साथ कुछ प्रदान करता है जो मनमाने ढंग से पॉइंटर्स की तुलना करने की अनुमति देता है।

नहीं


पहले हमें केवल ऑब्जेक्ट पॉइंटर्स पर विचार करना चाहिए । समारोह संकेत चिंताओं के एक पूरे अन्य सेट में लाते हैं।

2 पॉइंटर्स p1, p2में अलग-अलग एनकोडिंग हो सकते हैं और एक ही पते पर इंगित हो सकते हैं p1 == p2, हालांकि memcmp(&p1, &p2, sizeof p1)0. 0. ऐसे आर्किटेक्चर दुर्लभ नहीं हैं।

फिर भी इन पॉइंटर के रूपांतरण के uintptr_tलिए समान पूर्णांक परिणाम की आवश्यकता नहीं होती है (uintptr_t)p1 != (uinptr_t)p2

(uintptr_t)p1 < (uinptr_t)p2 स्वयं अच्छी तरह से कानूनी कोड है, इसके द्वारा कार्यक्षमता के लिए आशा व्यक्त नहीं की जा सकती है।


यदि कोड को वास्तव में असंबंधित बिंदुओं की तुलना करने की आवश्यकता है, तो एक सहायक फ़ंक्शन बनाएं less(const void *p1, const void *p2)और वहां मंच विशिष्ट कोड निष्पादित करें।

शायद:

// return -1,0,1 for <,==,> 
int ptrcmp(const void *c1, const void *c1) {
  // Equivalence test works on all platforms
  if (c1 == c2) {
    return 0;
  }
  // At this point, we know pointers are not equivalent.
  #ifdef UINTPTR_MAX
    uintptr_t u1 = (uintptr_t)c1;
    uintptr_t u2 = (uintptr_t)c2;
    // Below code "works" in that the computation is legal,
    //   but does it function as desired?
    // Likely, but strange systems lurk out in the wild. 
    // Check implementation before using
    #if tbd
      return (u1 > u2) - (u1 < u2);
    #else
      #error TBD code
    #endif
  #else
    #error TBD code
  #endif 
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.