45 votes

Résultat inattendu lorsque C++ stocke un élément dans std::vector à partir de la valeur de retour de la fonction

Lorsque la fonction implique une réallocation, j'ai constaté que certains compilateurs peuvent enregistrer l'adresse avant l'appel de la fonction. Cela conduit la valeur de retour stockée dans l'adresse invalide.

Il y a un exemple pour expliquer le comportement dans la description ci-dessus.

#include <stdio.h>
#include <vector> 
using namespace std;

vector<int> A; 
int func() { 
    A.push_back(3);
    A.push_back(4);
    return 5; 
} 
int main() { 
    A.reserve(2);
    A.push_back(0);
    A.push_back(1);
    A[1] = func();
    printf("%d\n", A[1]);
    return 0;
}

Il y a quelques compilateurs C++ courants, et le résultat du test est le suivant.

  • GCC(GNU Compiler Collection) : Erreur d'exécution ou sortie 1
  • Clang : sortie 5
  • VC++ : sortie 5

Est-ce un comportement indéfini ?

51voto

Ulrich Eckhardt Points 5381

Ce comportement est indéfini dans toutes les versions de C++ antérieures à C++17. La raison en est simple : les deux côtés de l'opérateur d'affectation peuvent être évalués dans n'importe quel ordre :

  • En supposant que A[1] est évalué en premier, vous obtenez un int& se référant au deuxième élément de A à ce moment-là.
  • Ensuite, le func() est évaluée, ce qui peut réallouer l'espace de stockage pour le vecteur, en laissant le vecteur int& une référence pendante.
  • Enfin, l'affectation est effectuée, en écrivant dans le stockage non alloué. Comme les allocateurs standard mettent la mémoire en cache, le système d'exploitation ne détecte pas toujours cette erreur.

Seulement en C++17, le règle spéciale 20 pour l'affectation a été faite :

Dans chaque expression d'affectation simple E1=E2 et chaque expression d'affectation composée d'affectation composée E1@=E2, chaque calcul de valeur et effet secondaire de E2 est séquencé avant chaque calcul de valeur et chaque effet secondaire de E1

Avec C++17, A[1] doit être évalué après l'appel à func() qui fournit alors un comportement défini et fiable.

-5voto

Mikaela Szekely Points 104

Si vous vérifiez le documentation sous "Iterator Invalidation", vous verrez que push_back() peut invalider chaque itérateur s'il change de capacité, car il devrait réallouer de la mémoire. Rappelez-vous que, pour un std::vector un pointeur est également un itérateur valide. Parce que push_back() peut ou non réallouer, et vous n'avez aucun moyen de savoir s'il le fera, le comportement est indéfini.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X