C ++ स्फ़ुरियस कॉपी ऑपरेशंस कैसे खोजें?


11

हाल ही में, मैं निम्नलिखित था

struct data {
  std::vector<int> V;
};

data get_vector(int n)
{
  std::vector<int> V(n,0);
  return {V};
}

इस कोड के साथ समस्या यह है कि जब संरचना बनाई जाती है तो एक प्रति उत्पन्न होती है और समाधान के बदले रिटर्न {std :: move (V)} लिखना होता है।

क्या ऐसे लिंटर या कोड विश्लेषक हैं जो इस तरह के नकली कॉपी ऑपरेशन का पता लगाते हैं? न तो कैपचेक, कैप्लिंट, न ही क्लैंग-टिड्डी इसे कर सकते हैं।

संपादित करें: मेरे प्रश्न को स्पष्ट करने के लिए कई बिंदु:

  1. मुझे पता है कि एक कॉपी ऑपरेशन हुआ, क्योंकि मैंने कंपाइलर एक्सप्लोरर का इस्तेमाल किया था और यह मेम्ची को कॉल दिखाता है ।
  2. मैं पहचान सकता था कि मानक हाँ को देखकर एक कॉपी ऑपरेशन हुआ। लेकिन मेरा शुरुआती गलत विचार यह था कि कंपाइलर इस प्रति को हटा देगा। मैं गलत था।
  3. यह (संभावना) एक संकलक समस्या नहीं है क्योंकि क्लैंग और जीसीसी दोनों एक कोड का उत्पादन करते हैं जो एक मेम्पी का उत्पादन करते हैं ।
  4. मेमस्किप सस्ता हो सकता है, लेकिन मैं ऐसी परिस्थितियों की कल्पना नहीं कर सकता, जहां मेमोरी की प्रतिलिपि बनाना और मूल को हटाना एक std :: चाल से पॉइंटर पास करने की तुलना में सस्ता है ।
  5. एसटीडी :: मूव को जोड़ना एक प्राथमिक ऑपरेशन है। मुझे लगता है कि एक कोड विश्लेषक इस सुधार का सुझाव देने में सक्षम होगा।

2
मैं यह जवाब नहीं दे सकता कि "नकली" कॉपी ऑपरेशंस का पता लगाने के लिए कोई विधि / उपकरण मौजूद है या नहीं, हालांकि, मेरी ईमानदार राय में, मैं असहमत हूं कि std::vectorकिसी भी माध्यम से कॉपी करना वह नहीं है जो उसे होना चाहिए । आपका उदाहरण एक स्पष्ट प्रतिलिपि दिखाता है, और यह केवल प्राकृतिक है, और std::moveफ़ंक्शन को लागू करने के लिए सही दृष्टिकोण, (फिर से imho) जैसा कि आप खुद को सुझाव देते हैं कि यदि कोई कॉपी नहीं है तो आप क्या चाहते हैं। ध्यान दें कि कुछ कंप्रेशर्स नकल को छोड़ सकते हैं यदि अनुकूलन झंडे को चालू किया जाता है, और वेक्टर अपरिवर्तित होता है।
मैग्नस

मुझे डर है कि इस लिटर नियम को प्रयोग करने योग्य बनाने के लिए बहुत अधिक अनावश्यक प्रतियाँ (जो शायद असर न कर रही हों): - / ( जंग डिफ़ॉल्ट रूप से चलती है इसलिए स्पष्ट प्रति की आवश्यकता है :))
Jarod42

कोड ऑप्टिमाइज़ करने के लिए मेरे सुझाव मूल रूप से उस फ़ंक्शन को
डिसाइड

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

2
जिस तकनीक का उपयोग मैं नकली प्रतियों को खोजने के लिए करता हूं, वह अस्थायी रूप से कॉपी कंस्ट्रक्टर को निजी बनाने के लिए होती है, और फिर जांच करती है कि एक्सेस प्रतिबंधों के कारण कंपाइलर कहां है। (इसी तरह के टैगिंग का समर्थन करने वाले
कंपाइलरों के

जवाबों:


2

मेरा मानना ​​है कि आपके पास सही अवलोकन है लेकिन गलत व्याख्या है!

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

ठीक है, चलो std::vectorनिर्माण के दौरान या इसे चरण दर चरण भरने के साथ थोड़ा सा खेल देगा।

सबसे पहले, एक डेटा प्रकार उत्पन्न करने देता है जो हर कॉपी या इस तरह दिखाई देता है:

template <typename DATA >
struct VisibleCopy
{
    private:
        DATA data;

    public:
        VisibleCopy( const DATA& data_ ): data{ data_ }
        {
            std::cout << "Construct " << data << std::endl;
        }

        VisibleCopy( const VisibleCopy& other ): data{ other.data }
        {
            std::cout << "Copy " << data << std::endl;
        }

        VisibleCopy( VisibleCopy&& other ) noexcept : data{ std::move(other.data) }
        {
            std::cout << "Move " << data << std::endl;
        }

        VisibleCopy& operator=( const VisibleCopy& other )
        {
            data = other.data;
            std::cout << "copy assign " << data << std::endl;
        }

        VisibleCopy& operator=( VisibleCopy&& other ) noexcept
        {
            data = std::move( other.data );
            std::cout << "move assign " << data << std::endl;
        }

        DATA Get() const { return data; }

};

और अब कुछ प्रयोग शुरू करते हैं:

using T = std::vector< VisibleCopy<int> >;

T Get1() 
{   
    std::cout << "Start init" << std::endl;
    std::vector< VisibleCopy<int> > vec{ 1,2,3,4 };
    std::cout << "End init" << std::endl;
    return vec;
}   

T Get2()
{   
    std::cout << "Start init" << std::endl;
    std::vector< VisibleCopy<int> > vec(4,0);
    std::cout << "End init" << std::endl;
    return vec;
}

T Get3()
{
    std::cout << "Start init" << std::endl;
    std::vector< VisibleCopy<int> > vec;
    vec.emplace_back(1);
    vec.emplace_back(2);
    vec.emplace_back(3);
    vec.emplace_back(4);
    std::cout << "End init" << std::endl;

    return vec;
}

T Get4()
{
    std::cout << "Start init" << std::endl;
    std::vector< VisibleCopy<int> > vec;
    vec.reserve(4);
    vec.emplace_back(1);
    vec.emplace_back(2);
    vec.emplace_back(3);
    vec.emplace_back(4);
    std::cout << "End init" << std::endl;

    return vec;
}

int main()
{
    auto vec1 = Get1();
    auto vec2 = Get2();
    auto vec3 = Get3();
    auto vec4 = Get4();

    // All data as expected? Lets check:
    for ( auto& el: vec1 ) { std::cout << el.Get() << std::endl; }
    for ( auto& el: vec2 ) { std::cout << el.Get() << std::endl; }
    for ( auto& el: vec3 ) { std::cout << el.Get() << std::endl; }
    for ( auto& el: vec4 ) { std::cout << el.Get() << std::endl; }
}

हम क्या देख सकते हैं:

उदाहरण 1) हम एक प्रारंभिक सूची से एक वेक्टर बनाते हैं और शायद हम उम्मीद करते हैं कि हम 4 बार निर्माण और 4 चाल देखेंगे। लेकिन हमें 4 प्रतियाँ मिलती हैं! यह थोड़ा रहस्यमय लगता है, लेकिन इसका कारण इनिशियलाइज़र सूची का कार्यान्वयन है! बस इसे सूची से स्थानांतरित करने की अनुमति नहीं है क्योंकि सूची से पुनरावृत्त एक ऐसा है const T*जो इससे तत्वों को स्थानांतरित करना असंभव बनाता है। इस विषय पर एक विस्तृत जवाब यहां पाया जा सकता है: initializer_list और शब्दार्थ को स्थानांतरित करें

उदाहरण 2) इस मामले में, हमें एक प्रारंभिक निर्माण और मूल्य की 4 प्रतियां मिलती हैं। यह कुछ खास नहीं है और हम यही उम्मीद कर सकते हैं।

उदाहरण 3) यहाँ भी, हम निर्माण और उम्मीद के अनुसार कुछ चालें हैं। मेरे stl कार्यान्वयन के साथ वेक्टर हर बार कारक 2 से बढ़ता है। इसलिए हम एक पहला निर्माण देखते हैं, दूसरा एक और क्योंकि वेक्टर 1 से 2 तक आकार लेता है, हम पहले तत्व की चाल देखते हैं। 3 एक को जोड़ते समय, हम 2 से 4 का आकार बदलते हैं, जिसमें पहले दो तत्वों की एक चाल की आवश्यकता होती है। जैसी कि उम्मीद थी!

उदाहरण 4) अब हम स्थान आरक्षित करते हैं और बाद में भरते हैं। अब हमारे पास कोई प्रतिलिपि नहीं है और कोई कदम नहीं है!

सभी मामलों में, हम किसी भी कदम को नहीं देखते हैं और न ही वेक्टर को वापस कॉल करने वाले पर वापस करके कॉपी करते हैं! (एन) आरवीओ जगह ले रहा है और इस कदम में आगे की कार्रवाई की आवश्यकता नहीं है!

अपने प्रश्न पर वापस जाएं:

"कैसे सी + + नकली प्रतिलिपि संचालन खोजने के लिए"

जैसा कि ऊपर देखा गया है, आप डिबगिंग उद्देश्य के लिए बीच में एक प्रॉक्सी क्लास लगा सकते हैं।

कॉपी-कॉटर को निजी बनाना कई मामलों में काम नहीं कर सकता है, क्योंकि आपके पास कुछ वांछित प्रतियां और कुछ छिपे हुए हो सकते हैं। जैसा कि ऊपर, उदाहरण के लिए केवल 4 कोड एक निजी कॉपी-कॉटर के साथ काम करेगा! और मैं इस सवाल का जवाब नहीं दे सकता, अगर उदाहरण 4 सबसे तेज़ है, क्योंकि हम शांति से शांति भरते हैं।

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

मेरा संकेत ऐसी छोटी समस्या पर ध्यान केंद्रित करना नहीं है। यदि आपके पास वास्तविक प्रदर्शन के मुद्दे हैं, तो एक प्रोफाइलर लें और मापें। बहुत सारे संभावित प्रदर्शन हत्यारे हैं, जो कि बहुत अधिक समय के लिए memcpyउपयोग पर खर्च करते हैं, ऐसा विचार योग्य नहीं है।


मेरा सवाल एकेडमिक की तरह है। हां, धीमे कोड के बहुत सारे तरीके हैं और यह मेरे लिए तत्काल समस्या नहीं है। हालांकि, हम पा सकते हैं memcpy संकलक एक्सप्लोरर का उपयोग करके संचालन। तो, एक रास्ता जरूर है। लेकिन यह केवल छोटे कार्यक्रमों के लिए संभव है। मेरा कहना है कि कोड का हित है जो कोड को बेहतर बनाने के बारे में सुझाव देगा। कोड विश्लेषक हैं जो बग्स और मेमोरी लीक पाते हैं, ऐसी समस्याएं क्यों नहीं हैं?
मैथ्यू डटौर सिकिरिक

"कोड जो कोड को बेहतर बनाने के बारे में सुझाव देगा।" यह पहले से ही कंपाइलरों में किया और कार्यान्वित किया गया है। (एन) आरवीओ ऑप्टिमाइज़ेशन केवल एक ही उदाहरण है और ऊपर दिखाए गए अनुसार सही काम कर रहा है। कैचिंग मेम्ची को मदद नहीं मिली क्योंकि आप "अवांछित मेमची" की खोज कर रहे हैं। "कोड एनालाइज़र हैं जो बग्स और मेमोरी लीक पाते हैं, ऐसी समस्याएं क्यों नहीं हैं?" शायद यह एक (आम) समस्या नहीं है। और "गति" समस्याओं को खोजने के लिए बहुत अधिक सामान्य उपकरण भी पहले से मौजूद है: प्रोफाइलर! मेरी व्यक्तिगत भावना यह है, कि आप एक अकादमिक चीज़ की खोज कर रहे हैं, जो आज वास्तविक सॉफ़्टवेयर में कोई समस्या नहीं है।
क्लॉस

1

मुझे पता है कि एक कॉपी ऑपरेशन हुआ, क्योंकि मैंने कंपाइलर एक्सप्लोरर का इस्तेमाल किया था और यह मेम्ची को कॉल दिखाता है।

क्या आपने कंपाइलर एक्सप्लोरर में अपना पूरा आवेदन डाला, और क्या आपने अनुकूलन को सक्षम किया? यदि नहीं, तो आपने संकलक एक्सप्लोरर में जो देखा वह आपके आवेदन के साथ हो रहा है या नहीं हो सकता है।

आपके द्वारा पोस्ट किए गए कोड के साथ एक समस्या यह है कि आप पहले एक बनाते हैं std::vector, और फिर इसे एक उदाहरण में कॉपी करते हैं data। वेक्टर के साथ आरंभ करना बेहतर होगा data:

data get_vector(int n)
{
  return {std::vector<int> V(n,0)};
}

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


मैंने केवल कंप्यूटर एक्सप्लोरर को उपरोक्त कोड में रखा है (जिसमें मेमसीपी है ) अन्यथा प्रश्न का कोई मतलब नहीं होगा। कहा जा रहा है कि बेहतर कोड का उत्पादन करने के लिए विभिन्न तरीकों को दिखाने में आपका उत्तर उत्कृष्ट है। आप दो तरीके प्रदान करते हैं: स्टैटिक का उपयोग और कंस्ट्रक्टर को सीधे आउटपुट में डालना। तो, उन तरीकों को एक कोड विश्लेषक द्वारा सुझाया जा सकता है।
मथिउ डटौर सिकिरिक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.