พฤติกรรมแปลก ๆ กับฟิลด์คลาสเมื่อเพิ่มไปยัง std :: vector


31

ฉันได้พบพฤติกรรมแปลก ๆ บางอย่าง (บนเสียงดังกราวและ GCC) ในสถานการณ์ต่อไปนี้ ฉันมีเวกเตอร์ที่มีองค์ประกอบหนึ่งซึ่งเป็นตัวอย่างของการเรียน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()ออกมามันจะส่งสัญญาณ X = 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มีขนาดที่เหมาะสมอย่างเพียงพอก่อนที่จะโทรออกเพื่อหลีกเลี่ยงพฤติกรรมที่ไม่ได้กำหนดเป็นการออกแบบที่ไม่ดีดังนั้นอย่าทำ
JohnFilleau

คำตอบ:


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อินสแตนซ์จึงมี UB push_backในการเรียกร้องใด UB กำลังใช้ตัวชี้ที่ไม่ถูกต้องในภายหลัง
NathanOliver

@ n314159 วิธีที่ดีในการกำหนดแนวความคิดนี้คือการจินตนาการถึงฟังก์ชั่นvoid set(Node* this)มันไม่ได้ไม่ได้กำหนดให้ส่งตัวชี้ที่ไม่ถูกต้องหรือไปfree()ยังฟังก์ชัน ฉันไม่แน่ใจ แต่ฉันคิดว่าแม้((Node*) nullptr)->set()จะมีการกำหนดถ้าคุณไม่ได้ใช้thisและวิธีการไม่ได้เป็นเสมือน
DutChen18

ฉันไม่คิดว่า((Node *) nullptr)->set()มันโอเคเพราะนี่เป็นตัวชี้โมฆะ (คุณเห็นว่าเกาหลีชัดเจนเมื่อเขียนมันเท่ากัน(*((Node *) nullptr)).set();)
n314159

1
@Dupuplicator ฉันอัปเดตข้อความ
NathanOliver

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จะจับนี้และได้บอกคุณในบรรทัดหน่วยความจำเป็นอิสระถ้าคุณยังรวบรวม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.