Bir std :: vector eklerken sınıf alanlarıyla garip davranış


31

Aşağıdaki durumda bazı garip davranışlar (clang ve GCC) buldum. nodesBir elemanım, bir sınıf örneği olan bir vektörüm var Node. Daha sonra vektöre nodes[0]yeni eklenen bir fonksiyon çağırıyorum Node. Yeni Düğüm eklendiğinde, çağıran nesnenin alanları sıfırlanır! Bununla birlikte, işlev bittiğinde tekrar normale dönüyor gibi görünüyorlar.

Bunun minimal tekrarlanabilir bir örnek olduğuna inanıyorum:

#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;
}

Hangi çıktılar

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

Yine de X'in süreç tarafından değişmeden kalmasını beklersiniz.

Denediğim diğer şeyler:

  • Nodeİçerisini ekleyen çizgiyi kaldırırsam, set()her seferinde X = 3 verir.
  • Yeni bir tane oluşturup Nodebu ( Node p = nodes[0]) üzerine çağırırsam çıkış 3, 3, 3 olur.
  • Bir başvuru oluşturur Nodeve bunu ( Node &p = nodes[0]) olarak çağırırsam , çıkış 3, 0, 0 olur (belki de bunun nedeni, vektör yeniden boyutlandırıldığında referansın kaybolmasıdır?)

Bu herhangi bir nedenle tanımsız bir davranış mı? Neden?


4
Bkz. En.cppreference.com/w/cpp/container/vector/push_back . Eğer reserve(2)çağırmadan önce vektör üzerinde çağırmış olsaydınız, set()bu davranış tanımlanacaktır. Ancak böyle bir işlev yazmak set, kullanıcının reservetanımlanmamış davranışlardan kaçınmak için çağırmadan önce uygun boyutta olmasını gerektirir , bu yüzden bunu yapmayın.
JohnFilleau

Yanıtlar:


39

Kodunuzda tanımlanmamış davranış var. İçinde

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

Erişim Xgerçekten this->Xve thisvektörün üyesine bir göstericidir. Bunu yaptığınızda nodes.push_back(Node());, vektöre yeni bir öğe eklersiniz ve bu işlem, tüm yineleyicileri, işaretçileri ve vektördeki öğelere yapılan başvuruları geçersiz kılan yeniden tahsis eder. Bunun anlamı

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

thisartık geçerli olmayan bir kullanıyor .


push_backZaten tanımlanmamış davranışı çağırıyor (o zaman geçersiz kılınmış bir üye işlevinde olduğumuzdan this) veya UB thisişaretçiyi ilk kullandığımızda mı oluşuyor? Yani mümkün olabilir mi return 42;?
n314159

3
@ n314159 nodesbir Nodeörnekten bağımsızdır, bu nedenle çağrıda UB yoktur push_back. UB daha sonra geçersiz işaretçiyi kullanıyor.
NathanOliver

@ n314159 Bunu kavramsallaştırmanın iyi bir yolu bir işlev hayal void set(Node* this)etmektir, geçersiz bir işaretçi veya işlevde ona tanımlanmak için tanımlanmamıştır free(). Emin değilim ama ((Node*) nullptr)->set()eğer kullanmıyorsanız thisve yöntem sanal değilse bile tanımlandığını hayal ediyorum .
DutChen18

Ben ((Node *) nullptr)->set()tamam olduğunu sanmıyorum , çünkü bu boş bir işaretçi dereferences (aynı kore yazarken açıkça kore görüyorum (*((Node *) nullptr)).set();).
n314159

1
@ Duplicalicator ifadelerini güncelledim.
NathanOliver

15
nodes.push_back(Node());

vektörünü yeniden tahsis eder, böylece adresini değiştirir nodes[0], ancak thisgüncellenmez. yöntemi bu kodla
değiştirmeyi deneyin 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]aradıktan sonra nasıl farklı olduğuna dikkat edin push_back.

-fsanitize=addressbunu yakalayacak ve hatta derlemeniz durumunda belleğin hangi satırda serbest bırakıldığını bile söyleyecektir -g.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.