Je suis étudiant en mathématiques et novice en C++. J'ai suivi une partie du cours de C++ ici même et j'y ai découvert la fuite de mémoire. Pour être franc je ne suis pas rassuré. Dans l'un de mes cours je dois créer une classe vecteur et une classe matrice (Je vous l'accord c'est un peu idiot sachant que ça existe mais ce n'est pas ma décision ^^') et je suis sur que l'une de mes fonctions créé de la fuite de mémoire. Pourriez-vous me le confirmer (ou pas) svp?
Les champs privés de la classe matrice sont :
int size1; //nb de lignes
int size2; //nb de colonnes
La fonction en question me permet d'ajouter une ligne à la matrice à la fin (contenant des 0) :
Je dois t'annoncer une mauvaise nouvelle. Le cours d'OC est obsolète (c'est du très vieux C++) et il est rempli d'inepties. Je te conseille fortement de te pencher sur d'autres cours. Il y en 3 qui sortent du lot :
Le cours de @gbdivers-> ici (il est en cours d'écriture mais bien mieux que celui d'OC et à jour)
Le cours sur le site ZdS (en cours d'écriture il me semble)
Le bouquin C++ Primer 5th Edition de S. Lippmann (en anglais, un poil moins à jour que les autres mais complet)
Bon déjà tu te rends compte par toi-même que l'utilisation des pointeurs "nus" (new/delete) et quelque peu périlleuse voir dangereuse. Ce cours d'Openclassrooms n'ensigne pas la bonne manière d'utiliser les pointeurs en C++, plus précisemment l'utilisation des pointeurs "intelligents" (ou smart pointer en anglais). Grâce à eux tu n'as pas à te soucier de savoir si tu dois libérer la mémoire, ils le font pour toi.
Je te conseille fortement de te renseigner sur leur utilisation. Et si il y a des choses que tu ne comprends pas avec l'utilisation des pointeurs intelligents n'hésite pas.
Sinon au niveau de ton code, je n'ai cherché plus loin quand j'ai vu les pointeurs nus et les tableaux à la C (encore un truc enseigné par ce maudit cours). En C++, on utilise principalement std::vector (pour du dynamique) ou std::array pour du statique. Et aussi, il n'y a pas de raisons particulières d'utiliser des pointeurs dans ton cas.
J'imagine que tu n'as pas le contrôle sur le type de structure utilisé -- sache pour information, que l'on évite comme la peste le double** pour préférer des mono-allocation linéarisée (meilleures perfs tout ça)
Bref. Retient que quand on modifie la structure courante il y a une règle absolue à suivre pour que les choses marchent (en général on fait ça avec l'affectation)
1. on réalise les opérations qui peuvent échouer, genre allouer 2. on duplique/copie 3. on commite nos changements (libérations, mise à jour des pointeurs membres)
ton code est trop compilé, il y a une alloc de trop
Cela ressemblerait à:
void resize(int newSize) {
// 1. ce qui peut échouer
vecteur * newRows = new vecteur[newSize];
// 2. la copie
std::copy_n(&rows[0], std::min(newSize, nbRows), newRows); // remplace la boucle de copie
// les éventuels trucs en plus
if (newSize > nbRows) {
for ( ; nbRows < newSize ; ++nbRows) {
newRows[nbRows] = new vecteur();
}
} else if (newSize < nbRows) {
for ( ; nbRows > newSize ; --nbRows) {
// TODO condition de fin à vérifier
delete[] rows[nbRows];
}
}
// 3. le commit
delete[] rows;
rows = newRows;
nbRows = newSize; // à priori redondant
}
void add_line() {
resize(nbRows+1);
}
PS: renvoyer la matrice courante par copie en sortie de l'ajout de ligne est une mauvaise idée
Je vais me réorienter dans ce cas vers un autre cours de C++, merci pour ces précieux conseils!
Je suis en master et j'ai un projet "informatique" à faire qui consiste à créer une intelligence artificielle pour le jeu othello. J'ai déjà créé le jeu avec la SFML (jeu très très très modeste bien sûr) qui comporte déjà plusieurs milliers de lignes de code. Je n'aurai pas le temps de tout réapprendre et tout refaire malheureusement. Pensez-vous qu'ajouter un delete[] mrows et un delete[] stock aux bons endroits pourrait me permettre de ne pas avoir de fuites de mémoire?
///////////
Merci lmghs pour ton aide! Si je peux te demander des précisions :
-Comment cela pourrait échouer? Une allocation peut-elle échouer? (en dehors du fait que les tailles ne correspondent pas)
-Si j'ai bien compris la ligne 6 du programme, elle permet de copier min(newSize, nbRows) éléments dans newRows, à partir de rows[0] c'est bien ça?
-Que veut dire le commit?
- Edité par PierreJesuspret 12 avril 2019 à 11:53:25
-Comment cela pourrait échouer? Une allocation peut-elle échouer? (en dehors du fait que les tailles ne correspondent pas)
Il faut comprendre que C++ est un langage "à exception" : quand quelque chose ne se passe pas bien (comme, par exemple, la demande de mémoire supplémentaire qui est faite au système d'exploitation lors d'un new), une exception est lancée, ce qui nous fait carrément sortir du "flux normal d'exécution".
Comme c'est le système d'exploitation qui décide de fournir (ou non) la mémoire supplémentaire qu'on lui demande, "il se peut" qu'il décide, tout simplement, de ne pas la fournir.
Le risque a beau être de plus en plus faible, au vu des systèmes actuels (surtout quand on demande de "petites" quantités de mémoire), il est -- malgré tout -- toujours bel et bien présent. Et le simple fait que le risque soit présent nous oblige à en tenir compte. Même si (et j'aurais presque tendance à dire "surtout si" et "malgré le fait que") le problème risque de ne "presque jamais" survenir.
Car la loi de l'emmerdement maximum fera toujours que, quand le problème surviendra (parce qu'il surviendra forcément à un moment ou à un autre), ce sera toujours "au pire moment possible" (justement lorsque tu essayera de faire la démonstration de ton programme, par exemple)
PierreJesuspret a écrit:
-Que veut dire le commit?
C'est le terme (anglais) utiliser pour désigner le fait d'appliquer les changements que nous avons mis en oeuvre.
L'idée, quand plusieurs changements doivent être effectués, c'est que l'on doit produire ce que l'on appelle une "transaction" (un peu comme "une transaction financière", lorsque tu vire des sous de ton compte sur celui d'un de tes fournisseurs): soit tous les changements s'effectue correctement (les sous sont retirés de ton compte et versés sur le compte de ton fournisseur), et tout le monde est content, soit aucun changement n'est appliqués (les sous ne sortent pas de ton compte, mais il n'arrivent pas non plus sur le compte de ton fournisseur), et, si personne n'est vraiment content, il n'y a -- au moins -- aucune partie "lésée" (car les gens seraient encore moins contents), et on peut essayer de réappliquer les changements "plus tard"
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
C'est ça. Et "commiter" une transaction c'est la valider. On la prépare et on dernier moment, quand tous les monde est d'accord, on signe, et c'est parti.
Je crois que c'est assez classique du monde des bases de données. Il y a deux approche: commiter pour valider à la fin, ou le rollback pour revenir en arrière sur ce qui a déjà été altéré. Si on peut, on préfère commiter, que de revenir en arrière
Tout dépend : - de l'opérateur d'affectation de vecteur, - du constructeur par défaut de vecteur, - du constructeur avec paramètre de vecteur - du constructeur de copie de matrice (vu que tu renvoies *this par valeur)
Sinon, intrinsèquement, la ligne 8 peut causer une fuite, et pire un dangling pointer si l'allocation (l.8 donc) échoue.
Les lignes 4 et 5 ne servent à rien.
C'est pour toi ou pour un prof?
PS: la dérivation vecteur < matrice est une très mauvaise idée à cause de plein de choses qui prennent tout un (bon!) cours pour expliquer.
OK. Fais les choses bien alors si c'est pour toi vu que tu n'es pas prisonnier de mauvaises pratiques pédagogiques.
Casse ton héritage public. Repose sur un std::vector<> en interne. Rien que ça, ça va te simplifier la vie et corriger de nombreuses choses.
Et si tu ne veux pas d'un vecteur en interne, pour information, dans les cours que je donne, je fais utiliser un `std::unique_ptr<double[]>` pour stocker les éléments. A la différence du vecteur je suis obligé de spécifier (en plus) construction par copie et affectation par copie, mais je gagne la 0-initialisation dont je ne veux pas par défaut.
Note que le redimensionnement (ajout de lignes), en général on s'en fout royalement. Surtout pour du machine learning -- n'en parlons même pas si tu utilises les classiques minmax/negamax qui s'en sortent très bien pour ce genre d'IA et qui ne reposent pas sur du calcul matriciel (contrairement à RdN/DL)
Donc si j'ai bien compris, dans ma classe matrice je ne considère pas du tout ma classe vecteur mais je fais tout avec std::vector<> c'est bien ça?
Enfin, pour le redimensionnement, j'ai pensé une fonction (qui permet l'évaluation des coups lors d'une partie d'Othello) qui, si telle condition est respectée, on ajoute un coup à la matrice "mat_coups" qui permet ensuite d'avoir la liste des coups disponibles. D'où le fait que, au point où j'en suis, j'ai besoin du redimensionnement.
Dans la fonction que tu avais écrit :
void resize(int newSize) {
// 1. ce qui peut échouer
vecteur * newRows = new vecteur[newSize];
// 2. la copie
std::copy_n(&rows[0], std::min(newSize, nbRows), newRows); // remplace la boucle de copie
// les éventuels trucs en plus
if (newSize > nbRows) {
for ( ; nbRows < newSize ; ++nbRows) {
newRows[nbRows] = new vecteur();
}
} else if (newSize < nbRows) {
for ( ; nbRows > newSize ; --nbRows) {
// TODO condition de fin à vérifier
delete[] rows[nbRows];
}
}
// 3. le commit
delete[] rows;
rows = newRows;
nbRows = newSize; // à priori redondant
}
void add_line() {
resize(nbRows+1);
}
Par exemple pour add_line(), si je mets cette méthode dans la classe matrice, tu confirmes que je dois bien faire void matrice::add_line() { ..} ?
- Edité par PierreJesuspret 12 avril 2019 à 16:08:37
Y-a-t-il fuite de mémoire?
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
...