Eu encontrei um comportamento muito estranho (no clang e no GCC) na seguinte situação. Eu tenho um vetor, nodes
com um elemento, uma instância de classe Node
. Então, chamo uma função nodes[0]
que adiciona um novo Node
ao vetor. Quando o novo Nó é adicionado, os campos do objeto de chamada são redefinidos! No entanto, eles parecem voltar ao normal novamente quando a função é concluída.
Eu acredito que este é um exemplo reprodutível mínimo:
#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;
}
Quais saídas
Before, X = 3
After, X = 0
Finally, X = 3
Embora você espere que o X permaneça inalterado pelo processo.
Outras coisas que eu tentei:
- Se eu remover a linha que adiciona um
Node
interiorset()
, ela gera X = 3 sempre. - Se eu criar um novo
Node
e chamar assim (Node p = nodes[0]
), a saída será 3, 3, 3 - Se eu criar uma referência
Node
e chamar isso de (Node &p = nodes[0]
), então a saída será 3, 0, 0 (talvez essa seja porque a referência é perdida quando o vetor é redimensionado?)
Esse comportamento é indefinido por algum motivo? Por quê?
reserve(2)
o vetor antes de chamarset()
isso, seria um comportamento definido. Mas escrever uma função comoset
essa requer que o usuário tenhareserve
tamanho suficiente antes de chamá-la, a fim de evitar um comportamento indefinido é um design ruim, portanto, não faça isso.Respostas:
Seu código tem um comportamento indefinido. No
O acesso a
X
é realmentethis->X
ethis
é um ponteiro para o membro do vetor. Quando vocênodes.push_back(Node());
adiciona um novo elemento ao vetor e esse processo é realocado, o que invalida todos os iteradores, ponteiros e referências a elementos no vetor. Que significaestá usando um
this
que não é mais válido.fonte
push_back
comportamento já indefinido (já que estamos em uma função de membro com invalidaçãothis
) ou o UB ocorre na primeira vez que usamos othis
ponteiro? Seria possível ou sejareturn 42;
?nodes
é independente de umaNode
instância, portanto, não há UB na chamadapush_back
. O UB está usando o ponteiro inválido posteriormente.void set(Node* this)
, não é indefinido transmitir a ela um ponteiro inválido ou afree()
ela na função. Não tenho certeza, mas imagino que mesmo((Node*) nullptr)->set()
seja definido se você não usarthis
e o método não for virtual.((Node *) nullptr)->set()
esteja bem, pois isso desreferencia um ponteiro nulo (você vê isso claramente quando escrevê-lo de maneira equivalente(*((Node *) nullptr)).set();
).realocará o vetor, alterando o endereço de
nodes[0]
, masthis
não será atualizado.tente substituir o
set
método por este código:observe como
&nodes[0]
é diferente depois de ligarpush_back
.-fsanitize=address
perceberá isso e até informará em qual linha a memória foi liberada se você também compilar-g
.fonte