2010-12-01 27 views
8

J'ai trouvé récemment que la plupart des erreurs dans mon C++ programmes sont de une forme comme l'exemple suivant:Comment détecter la référence const à des problèmes temporaires lors de la compilation ou de l'exécution?

#include <iostream> 

class Z 
{ 
public: 
Z(int n) : n(n) {} 
int n; 
}; 

class Y 
{ 
public: 
Y(const Z& z) : z(z) {} 
const Z& z; 
}; 

class X 
{ 
public: 
X(const Y& y) : y(y) {} 
Y y; 
}; 

class Big 
{ 
public: 
Big() 
{ 
    for (int i = 0; i < 1000; ++i) { a[i] = i + 1000; } 
} 
int a[1000]; 
}; 

X get_x() { return X(Y(Z(123))); } 

int main() 
{ 
X x = get_x(); 
Big b; 
std::cout << x.y.z.n << std::endl; 
} 

SORTIE: 1000

Je me attends à ce programme à la sortie 123 (la valeur de xyzn définie dans get_x()) mais la création de "Big b" écrase le Z temporaire. En tant que résultat , la référence au Z temporaire dans l'objet Y est maintenant écrasée avec Big b, et donc la sortie Ce n'est pas ce que j'attendrais de . Lorsque j'ai compilé ce programme avec gcc 4.5 avec l'option "-Wall", n'a donné aucun avertissement.

Le correctif est évidemment de supprimer la référence de l'élément Z dans la classe Y. Cependant, souvent la classe Y fait partie d'une bibliothèque que je n'ai pas développé (boost :: fusion la plus récente), et en plus la situation est beaucoup plus compliquée que cet exemple que j'ai donné.

Y at-il une sorte d'option à gcc, ou tout autre logiciel que me permettrait de détecter de tels problèmes de préférence au moment de la compilation, mais même runtime serait mieux que rien?

Merci,

Clinton

+0

Je suis un peu surpris que vous obteniez 1000 au lieu de 1123. – Gabe

+0

Gabe: Pourquoi 1123? Le constructeur de Big définit l'élément zeroth = 1000. Il n'ajoute pas 1000 à l'élément zeroth. Si c'était {a [i] + = i + 1000; } Je pouvais voir d'où tu venais. – Clinton

+0

En relation: https://stackoverflow.com/q/42340073/946850 – krlmlr

Répondre

2

J'ai soumis de tels cas sur la liste de diffusion de clang-dev il y a quelques mois, mais personne n'avait le temps de travailler dessus (et moi non plus, malheureusement).

Argyrios Kyrtzidis travaille actuellement sur ce bien, et voici sa dernière mise à jour sur la question (30 novembre 23h04 GMT):

Je reverted la commettras précédente, beaucoup mieux fixer dans http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20101129/036875.html. par exemple.pour

struct S { int x; }; 

int &get_ref() { S s; S &s2 = s; int &x2 = s2.x; return x2; } 

nous obtenons

t3.cpp:9:10: warning: reference to stack memory associated with local variable 's' returned 
    return x2; 
     ^~ 
t3.cpp:8:8: note: binding reference variable 'x2' here 
    int &x2 = s2.x; 
    ^ ~~ 
t3.cpp:7:6: note: binding reference variable 's2' here 
    S &s2 = s; 
    ^ ~ 
1 warning generated. 

La précédente tentative a échoué le test d'auto-hébergement, donc j'espère que cette tentative passera. Je suis plutôt content qu'Argyrios s'y penche de toute façon :)

Ce n'est pas encore parfait, c'est un problème assez compliqué à aborder (ça me rappelle un alias de pointeur en quelque sorte), mais ça n'en est pas moins un un grand pas dans la bonne direction.

Pourriez-vous tester votre code par rapport à cette version de Clang? Je suis sûr que Argyrios apprécierait les commentaires (si elle est détectée ou non).

0

[Edité troisième point pour démontrer une technique qui peut aider] Ceci est le trou du lapin que vous allez vers le bas quand une langue permet de passage d'arguments par valeur ou référence avec le même appelant syntaxe. Vous disposez des options suivantes:

  • Modifiez les arguments en références non const. Une valeur temporaire ne correspond pas à un type de référence non-const.

  • Supprimez complètement les références dans les cas où cela est possible. Si vos références const n'indiquent pas l'état logiquement partagé entre l'appelant et l'appelé (si ce problème ne se produisait pas très fréquemment), elles ont probablement été insérées dans le but d'éviter la copie naïve de types complexes. Les compilateurs modernes ont avancé des optimisations d'ellision de copie qui rendent la valeur de passe aussi efficace que la référence de passage dans la plupart des cas; voir http://cpp-next.com/archive/2009/08/want-speed-pass-by-value pour une excellente explication. L'ellision de copie ne sera clairement pas effectuée si vous transmettez les valeurs à des fonctions de bibliothèque externes qui pourraient modifier les temporaires, mais si tel était le cas, vous ne les transmettez pas en tant que références const ou vous rejetez délibérément le const. -ness dans la version originale. C'est ma solution préférée car elle permet au compilateur de s'inquiéter de l'optimisation de la copie et me libère de m'inquiéter des autres sources d'erreur dans le code.

  • Si votre compilateur prend en charge les références rvalue, utilisez-les.Si vous pouvez au moins modifier les types de paramètres des fonctions où vous soucier de ce problème, vous pouvez définir une métaclasse wrapper comme ceci:

modèle < typename T> need_ref {

T & ref_ ;

public:

need_ref (T & & x) {/ * rien * /}

need_ref (T & x): ref_ (x) {/ * rien * /}

opérateur T &() {return ref_; }

};

puis de remplacer les arguments de type T & par des arguments de type need_ref. Par exemple, si vous définissez les éléments suivants

utilisateur classe {

int & z;

public:

utilisateur (need_ref < int> arg): z (arg) {/ * rien * /}

};

alors vous pouvez initalize en toute sécurité un objet de type utilisateur avec le code de la forme "int a = 1, b = 2; utilisateur ua (a);", mais si vous essayez d'initialiser comme "user sum (a + b) "ou" utilisateur cinq (5) "votre compilateur doit générer une erreur de référence non initialisée dans la première version du constructeur need_ref(). La technique n'est évidemment pas limitée aux constructeurs, et n'impose aucun temps d'exécution.

+0

Je ne peux pas modifier le code de la bibliothèque. Ma question n'était pas comment résoudre le problème (je sais comment). La question était de savoir comment détecter le problème. Un problème comme celui-ci m'a pris un certain nombre d'heures à détecter et une minute à réparer. C'est la détection dont j'ai besoin d'aide, pas le correctif. – Clinton

+0

J'ai édité ma réponse pour montrer une technique qui générera des erreurs de compilation si vous pouvez modifier les types d'arguments à au moins un niveau de la pile d'appels (par exemple les constructeurs X, Y,/Z dans votre exemple ci-dessus). Si vous ne pouvez pas éditer de code alors je suppose que vous êtes bloqué avec des versions expérimentales de clang, car je ne connais pas de commutateurs de ligne de commande qui permettent ce type de test dans les compilateurs grand public. – spillner

-1

Le problème ici est le code

Y(const Z& z) : z(z) {} 

comme membre « z » est initialisé avec une référence au paramètre formel « z ». Une fois le constructeur renvoyé, la référence fait référence à un objet qui n'est plus valide.

Je ne pense pas que le compilateur détectera ou pourra dans de nombreux cas détecter de telles failles logiques. Le correctif puis IMO est évidemment d'être conscient de ces classes et de les utiliser d'une manière appropriée à leur conception. Cela devrait vraiment être documenté par le vendeur de la bibliothèque. Par ailleurs, il est préférable de nommer le membre «Y :: z» comme «Y :: mz» si possible. L'expression 'z (z)' n'est pas très attrayante

+0

Chubsdad: Vous venez de répéter ce que j'ai demandé dans la question ici. J'ai déjà mentionné: "Le correctif est évidemment de supprimer la référence du membre Z dans la classe Y. Cependant, souvent la classe Y fait partie d'une bibliothèque que je n'ai pas développée (boost :: fusion plus récemment), et dans De plus, la situation est beaucoup plus compliquée que cet exemple que j'ai donné. " – Clinton