वर्ग क्षेत्रों के साथ अजीब व्यवहार जब एक एसटीडी :: वेक्टर में जोड़ते हैं


31

मैंने निम्नलिखित स्थिति में कुछ बहुत ही अजीब व्यवहार (क्लैंग और जीसीसी पर) पाया है। मेरे पास एक वेक्टर है, nodesजिसमें एक तत्व, कक्षा का एक उदाहरण है Node। मैं तब एक फ़ंक्शन को कॉल करता हूं जो वेक्टर में nodes[0]एक नया जोड़ता है Node। जब नया नोड जोड़ा जाता है, तो कॉलिंग ऑब्जेक्ट के फ़ील्ड रीसेट हो जाते हैं! हालाँकि, कार्य समाप्त होने के बाद वे फिर से सामान्य हो जाते हैं।

मेरा मानना ​​है कि यह एक न्यूनतम प्रजनन योग्य उदाहरण है:

#include <iostream>
#include <vector>

using namespace std;

struct Node;
vector<Node> nodes;

struct Node{
    int X;
    void set(){
        X = 3;
        cout << "Before, X = " << X << endl;
        nodes.push_back(Node());
        cout << "After, X = " << X << endl;
    }
};

int main() {
    nodes = vector<Node>();
    nodes.push_back(Node());

    nodes[0].set();
    cout << "Finally, X = " << nodes[0].X << endl;
}

जो आउटपुट देता है

Before, X = 3
After, X = 0
Finally, X = 3

हालाँकि आप उम्मीद करेंगे कि X प्रक्रिया से अपरिवर्तित रहेगा।

अन्य चीजें जो मैंने कोशिश की हैं:

  • अगर मैं उस लाइन को हटाता हूं जो Nodeअंदर जोड़ता है set(), तो यह हर बार एक्स = 3 को आउटपुट करता है।
  • यदि मैं एक नया बनाता हूं Nodeऔर उस पर कॉल करता हूं ( Node p = nodes[0]) तो आउटपुट 3, 3, 3 है
  • यदि मैं एक संदर्भ बनाता हूं Nodeऔर उस पर कॉल करता हूं ( Node &p = nodes[0]) तो आउटपुट 3, 0, 0 है (शायद यह एक है क्योंकि वेक्टर के आकार बदलने पर संदर्भ खो जाता है?)

क्या यह अपरिभाषित व्यवहार किसी कारण से है? क्यों?


4
En.cppreference.com/w/cpp/container/vector/push_back देखें । यदि आपको reserve(2)कॉल set()करने से पहले वेक्टर पर बुलाया गया था तो यह परिभाषित व्यवहार होगा। लेकिन ऐसा फ़ंक्शन लिखना, setजिसे reserveअपरिभाषित व्यवहार से बचने के लिए उपयोगकर्ता को कॉल करने से पहले उचित रूप से पर्याप्त आकार की आवश्यकता होती है, यह खराब डिज़ाइन है, इसलिए ऐसा न करें।

जवाबों:


39

आपके कोड में अपरिभाषित व्यवहार है। में

void set(){
    X = 3;
    cout << "Before, X = " << X << endl;
    nodes.push_back(Node());
    cout << "After, X = " << X << endl;
}

Xवास्तव में पहुंच this->Xऔर thisवेक्टर के सदस्य के लिए एक संकेतक है। जब आप nodes.push_back(Node());वेक्टर में एक नया तत्व जोड़ते हैं और उस प्रक्रिया को पुनः प्राप्त करते हैं, जो वेक्टर में तत्वों के सभी पुनरावृत्तियों, बिंदुओं और संदर्भों को अमान्य कर देता है। इसका मत

cout << "After, X = " << X << endl;

का उपयोग कर रहा है thisकि अब मान्य नहीं है।


क्या push_backपहले से ही अपरिभाषित व्यवहार को कॉल किया जा रहा है (क्योंकि हम तब एक सदस्य फ़ंक्शन में अमान्य हैं this) या UB पहली बार हम thisसूचक का उपयोग करते हैं ? क्या यह संभव होगा return 42;?
n314159

3
@ n314159 nodesएक Nodeउदाहरण से स्वतंत्र है इसलिए कॉलिंग में कोई यूबी नहीं है push_back। UB बाद में अमान्य सूचक का उपयोग कर रहा है।
नाथनऑलिवर

@ n314159 एक अवधारणा की कल्पना करने का एक अच्छा तरीका यह है कि किसी फ़ंक्शन की कल्पना करना void set(Node* this), यह अमान्य सूचक को पारित करने के लिए अपरिभाषित नहीं है, या free()फ़ंक्शन में इसे करने के लिए । मुझे यकीन नहीं है, लेकिन मुझे लगता है कि यहां तक ​​कि ((Node*) nullptr)->set()अगर आप का उपयोग नहीं करते हैं thisऔर विधि आभासी नहीं है परिभाषित किया गया है।
DutChen18

मुझे नहीं लगता कि ((Node *) nullptr)->set()यह ठीक है, क्योंकि यह एक शून्य सूचक है (आप इसे समान रूप से लिखते समय स्पष्ट रूप से देखते हैं (*((Node *) nullptr)).set();)।
n314159

1
@Deduplicator I ने शब्दांकन अपडेट किया।
नाथनऑलिवर

15
nodes.push_back(Node());

वेक्टर को फिर से विभाजित करेगा, इस प्रकार इसका पता बदल जाएगा nodes[0], लेकिन thisअपडेट नहीं किया गया है। इस कोड के साथ विधि को
बदलने का प्रयास setकरें:

    void set(){
        X = 3;
        cout << "Before, X = " << X << endl;
        cout << "Before, this = " << this << endl;
        cout << "Before, &nodes[0] = " << &nodes[0] << endl;
        nodes.push_back(Node());
        cout << "After, X = " << X << endl;
        cout << "After, this = " << this << endl;
        cout << "After, &nodes[0] = " << &nodes[0] << endl;
    }

ध्यान दें कि &nodes[0]कॉल करने के बाद कैसे अलग है push_back

-fsanitize=addressइसे पकड़ लेंगे, और यहां तक ​​कि आपको यह भी बताएंगे कि यदि आप भी संकलन करते हैं तो मेमोरी किस लाइन पर मुक्त हुई थी -g

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