std :: vector에 추가 할 때 클래스 필드의 이상한 동작


31

다음 상황에서 매우 이상한 동작 (clang 및 GCC에서)이 발견되었습니다. nodes하나의 요소가있는 class의 인스턴스 가있는 벡터가 있습니다 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는 프로세스에 의해 변경되지 않은 상태로 유지 될 것으로 예상합니다.

내가 시도한 다른 것들 :

  • Nodeinside 추가하는 줄을 제거하면 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->Xthis벡터의 멤버에 대한 포인터입니다. 당신이 할 때 nodes.push_back(Node());당신은 벡터와 그 과정의 재 할당에 새로운 요소를 추가 모든 반복자, 포인터와 참조를 무효화 벡터의 요소에. 그 의미는

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

this더 이상 유효하지 않은를 사용하고 있습니다.


push_back이미 정의되지 않은 동작을 호출하고 있습니까 (우리는 invalidated 멤버 함수에 있기 때문에 this) this포인터를 처음 사용할 때 UB가 발생 합니까? 즉 가능 return 42;합니까?
n314159

3
@ n314159 nodesNode인스턴스와 독립적 이므로 호출 할 때 UB가 없습니다 push_back. UB가 이후에 유효하지 않은 포인터를 사용하고 있습니다.
NathanOliver

@ n314159 이것을 개념화하는 좋은 방법은 함수를 상상하는 void set(Node* this)것입니다. 유효하지 않은 포인터 또는 free()함수 에 전달하는 것은 정의되지 않았습니다 . 확실 ((Node*) nullptr)->set()하지 않지만 사용하지 않고 this방법이 가상이 아닌 경우 에도 정의 된다고 생각합니다 .
DutChen18

((Node *) nullptr)->set()이것이 null 포인터를 역 참조하기 때문에 괜찮다고 생각하지 않습니다 (와 같이 쓸 때 명확하게 알 수 있습니다 (*((Node *) nullptr)).set();).
n314159

1
@ 중복 제거기 나는 문구를 업데이트했습니다.
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.