PROFDINFO.COM

Votre enseignant d'informatique en ligne

Déboguons!

Le débogueur intégré à Visual Studio est un outil puissant qui vous sera très utile tout au long de votre carrière. Il permet de voir ce qui se passe dans notre programme, une étape à la fois, en examinant le contenu des variables pendant l'exécution. C'est idéal pour résoudre des bogues étranges qu'on n'arrive pas à s'expliquer en regardant simplement le résultat de l'exécution.

Les points d'arrêt

Un point d'arrêt (breakpoint) est une ligne dans le code où vous voulez arrêter l'exécution normale du programme pour commencer à déboguer. Vous ne pourrez rien déboguer si vous n'avez pas au moins placé un point d'arrêt. On place un point d'arrêt simplement en cliquant dans la marge de gauche de notre code.

Un point d'arrêt dans du code

Il est possible de placer autant de points d'arrêt que désiré. On peut évidemment les enlever si nécessaire, simplement en cliquant dessus.

Exécuter avec ou sans débogage

Normalement, nous utilisons par défaut l'exécution sans débogage (Ctrl-F5), puisque ce mode fait une pause une fois l'exécution terminée, ce qui nous permet de voir le contenu de la console avant qu'elle soit fermée.

Si on veut utiliser les points d'arrêt, il nous faudra toutefois utiliser "Démarrer le débogage" (F5), sinon ils seront ignorés. On peut donc laisser les points d'arrêt en place et choisir de les utiliser ou non.

Le pas à pas

L'idée de mettre un point d'arrêt est de pouvoir ensuite suivre l'exécution de votre programme un pas à la fois. Pour ce faire, on appuie sur F5 (démarrer le débogage) et le programme s'exécute normalement jusqu'à ce qu'il atteigne le premier point d'arrêt. Ensuite, l'exécution est suspendue, comme mise à pause. Votre code revient à l'avant-plan et une flèche jaune apparaît sur le point d'arrêt -- la ligne de code est également surlignée:

L'exécution suspendue sur un point d'arrêt

La flèche jaune indique la ligne qui sera la prochaine à être exécutée. L'exécution est donc suspendue juste avant l'exécution de la ligne où se trouve le point d'arrêt. Vous pouvez alors:

  • Passer votre pointeur de souris sur une variable pour voir son contenu dans une bulle -- on peut cliquer sur la punaise dans la bulle pour la fixer à cet endroit;
  • Appuyer sur F10 pour faire un pas principal, c'est-à-dire avancer d'un pas dans l'exécution du code, mais sans entrer dans le code des fonctions. Par exemple, lorsqu'on passe sur un cout ou sur un sqrt, on n'a pas besoin d'exécuter leur code un pas à la fois -- on veut simplement exécuter la fonction au complet d'un coup et continuer à partir de là. Ce sera généralement l'option qu'on utilisera;
  • Appuyer sur F11 pour faire un pas détaillé, c'est-à-dire avancer d'un pas dans l'exécution du code en entrant dans le code des procédures et fonctions appelées. Ce sera pratique lorsque l'on codera nos propres fonctions;
  • Appuyer sur Shift-F11 pour faire un pas sortant, c'est-à-dire terminer d'un seul coup l'exécution de la fonction dans laquelle on est entré (avec F11), puis revenir au niveau supérieur (là où on se trouvait avant d'entrer dans la fonction). Pratique si on a appuyé sur F11 par erreur et qu'on ne sait plus trop où on est rendu, ou si on a vu ce qu'on voulait voir dans la fonction.
  • Appuyer sur F5 pour relancer l'exécution normalement. Elle sera de nouveau suspendue au prochain point d'arrêt rencontré, s'il y a lieu.
  • Appuyer sur Shift-F5 pour arrêter complètement l'exécution.
  • Déplacer la ligne jaune en la glissant (pour aller exécuter immédiatement une partie du code qui se trouve ailleurs, ou réexécuter une partie de code déjà exécutée précédemment -- il y a quand même des risques si on fait n'importe quoi).

Notez qu'il existe des icônes dans la barre de boutons qui permettent aussi de réaliser ces tâches:

Les espions

Plutôt que de devoir constamment passer sa souris sur une variable pour la voir changer, on peut utiliser la fenêtre d'espions pour qu'elle soit affichée pour nous en permanence. La fenêtre d'espions apparaît normalement en dessous de la fenêtre du code pendant qu'on est en débogage. Si ce n'est pas le cas, on peut la faire apparaître en faisant Déboguer -> Fenêtre -> Espions -> Espion 1 (notez qu'on peut en ouvrir 4 différentes, toutes équivalentes).

On ajoute un espion en:

  • sélectionnant du code et en le glissant dans la fenêtre d'espions;
  • écrivant quelque chose directement dans la fenêtre d'espions.

On peut y mettre:

  • une variable;
  • une expression arithmétique;
  • une expression relationnelle;
  • une expression logique;
  • un savoureux mélange de tout ça.

Notez que les expressions n'ont pas besoin d'être des expressions directement tirées du code. Les variables, par contre, doivent être visibles au point du code où on est rendu en ce moment (pensez à la portée des variables locales).

Lorsqu'une valeur vient de changer (au dernier pas), elle devient rouge. Lorsqu'elle est pareille à ce qu'elle était au pas précédent, elle est noire.

Les points d'arrêt conditionnels

En cliquant sur un point d'arrêt avec le bouton de droite, on peut choisir "Condition" et définir une condition pour que le point d'arrêt s'active. Tant que la condition n'est pas vraie, le point d'arrêt est ignoré. Idéal pour s'arrêter à un moment précis d'une boucle!

On peut placer dans la boîte n'importe quelle expression relationnelle, qui peut utiliser des variables visibles à cet endroit du code. À chaque passage au point d'arrêt, la condition sera évaluée. Si elle est fausse, le point sera ignoré et l'exécution continuera. Si elle est vraie, le point d'arrêt s'activera et l'exécution sera suspendue.

Optionnellement, on peut également inscrire uniquement le nom d'une variable dans la case "Condition" et cocher "a changé". Le point d'arrêt s'activera si la variable donnée n'a pas la même valeur que lors du dernier passage au point d'arrêt.

Un point d'arrêt conditionnel a l'air de ça:

Point d'arrêt conditionnel

Les assertions

Une assertion est la vérification d'une condition essentielle à la bonne marche du programme, vérification qui ne sera faite que pendant la phase de développement et pas pendant l'utilisation régulière du programme terminé.

L'idée est de valider une certaine condition sans laquelle rien ne peut fonctionner et tout arrêter si jamais la condition est fausse. Mais attention! On ne doit pas utiliser les assertions pour valider les intrants fournis par l'usager, puisque les assertions seront ignorées lorsque le programme sera entièrement terminé. On validera plutôt l'état de variables internes, dont nous sommes seuls responsables du contenu, afin de nous aider à identifier les situations problématiques pendant que l'on débogue et qu'on teste notre programme.

Une assertion, en C++, s'écrit simplement comme ceci:

assert(condition);

La condition peut être une expression relative ou une expression logique, qui peut être réduite à true ou false, exactement comme la condition qu'on placerait dans un if. Par exemple:

assert(Compteur < 100);

Ou:

assert(Diviseur != 0);

Au moment où la ligne est exécutée, la condition est vérifiée. Si elle est fausse, le programme s'arrête immédiatement et affichera un message d'erreur correspondant à la condition:

Assertion failed: Diviseur != 0

N'oubliez pas d'inclure la bibliothèque assert.h si vous désirez utiliser les assertions:

#include <assert.h>

Le mode Debug et le mode Release

Depuis le début, nous compilons toujours en mode Debug, simplement parce que c'est l'option par défaut. Il y a des différences importantes entre les deux modes:

Debug:

  • Compilation intégrant des informations pour le débogage
  • Aucune optimisation de l'exécutable
  • Les assertions sont exécutées

Release:

  • Compilation n'intégrant aucune information de débogage
  • Exécutable optimisé et plus rapide
  • Les assertions sont ignorées

On utilise normalement le mode Release une fois que le débogage est entièrement terminé, que les tests sont satisfaisants et que l'on s'apprête à remettre le programme aux utilisateurs. Comme les assertions sont ignorées, on peut simplement les laisser là et compiler en mode Release comme si de rien n'était.

On change le mode simplement en utilisant la liste déroulable au centre de la barre d'outils:

Questions

1- Lorsque le code suivant est exécuté, au moment où i atteint la valeur -850, que valent les variables j, k et x? Expliquez comment vous pouvez trouver la réponse aisément à l'aide du débogueur, et donnez les valeurs des variables.

#include <iostream>
using namespace std;

int main()
{
	int j = 2;
	int k = 0;
	int x = 2000;

	for (int i = -9000; i < 950; i = i + j)
	{
		k = i * 2;
		while (x > i)
		{
			if (j % 2 == 0)
			{
				x = x - j;
				i--;
			}
			else if (j % 3 == 0)
			{
				x = x + j;
				i++;
			}
			else
			{
				x++;
			}
			k *= 2;
		}
	}
}

2- Voici une solution à l'exercice 3.9 du cours d'algorithmique. En bonus, elle calcule la moyenne du groupe, en plus de la moyenne de chaque étudiant. Malheureusement, elle ne fonctionne pas très bien. Saurez-vous trouver les 11 erreurs qui s'y sont glissées? Aidez-vous du débogueur pour voir ce qui se passe! (et vous pouvez diminuer le nombre d'étudiants dans la classe pour fins de tests!)

Identifiez chaque erreur par un commentaire.

#include <iostream>
using namespace std;

int main()
{
	float Note1, Note2, Note3;    
	float Moyenne = 0;               
	float MoyenneGroupe = 0;          
	float SommeGroupe = 0;        

	const float Passage = 0.60f;
	const float Bien = 0.70f;
	const float TresBien = 0.85f;
	const float NoteMin = 0.0f;
	const float NoteMax = 100.0f;

	const int NbEtudiants = 18;   
	const int NbExam = 3;         
	int Compteur = 1;           

	while (Compteur <= NbEtudiants)  
	{
		Compteur = Compteur + 1; 
		cout << "Entrez la note 1 de l'etudiant " << endl;
		cin >> Note1;
		while (Note1 < NoteMin && Note1 > NoteMax)  
		{
			cout << "Note invalide, veuillez entrer une note entre 0 et 100:  ";
			cin >> Note1;
		}

		cout << "Entrez la note 2 de l'etudiant " << endl;
		cin >> Note1;
		while (Note1 < NoteMin && Note1 > NoteMax)
		{
			cout << "Note invalide, veuillez entrer une note entre 0 et 100:  ";
			cin >> Note2;
		}

		cout << "Entrez la note 3 de l'etudiant " << endl;
		cin >> Note3;
		while (NoteMin < Note3 || Note3 > NoteMax)
		{
			cout << "Note invalide, veuillez entrer une note entre 0 et 100:  ";
			cin >> Note3;
		}

		SommeGroupe = SommeGroupe + Moyenne; 
		Moyenne = (Note1 + Note2 + Note3) / NbEtudiants;  
		cout << "Moyenne de l'etudiant: " << Moyenne << ".  "; 

		if (Moyenne < Passage) 
		{
			cout << "Echec!" << endl;
		}
		if (Moyenne < Bien) 
		{
			cout << "Bien!" << endl;
		}
		if (Moyenne < TresBien)
		{
			cout << "Tres bien!" << endl;
		}
		else 
		{
			cout << "Formidable!" << endl;
		}
	}

	MoyenneGroupe = SommeGroupe / Compteur; 
	cout << endl << "Moyenne du groupe: " << MoyenneGroupe << endl;

	return 0;
}

3- Ce programme est censé trouver le plus grand commun diviseur de deux nombres donnés par l'usager. On suppose que l'usager ne donne que des nombres entiers positifs (pas nécessaire de le valider). Malheureusement, plusieurs des tests fournis dans le jeu de tests échouent et son créateur vous demande de l'aider à déboguer.

Utilisez le débogueur pour tenter d'identifier tous les problèmes. Corrigez-les, mais identifiez avec un commentaire ce que vous avez changé et pourquoi afin que le créateur du programme puisse apprendre.

// Programme trouvant le plus grand commun diviseur (PGCD) à deux nombres
// Il tente d'être le plus efficace possible
// Le plus grand commun diviseur à deux nombres est le plus grand nombre qui divise
// les deux nombres sans reste tous les deux.
//
// Jeu de tests:
// Intrants             Extrants
// Nombre1   Nombre2    PGCD
// 97        54         Aucun
// 114       42         6
// 100       200        100
// 200       100        100
// 98        35         7

#include <iostream>
using namespace std;

int main()
{
	int Nombre1;
	int Nombre2;
	int PGCD; // Plus grand commun diviseur de Nombre1 et Nombre2
	int PlusPetit; // Le plus petit des deux nombres
	int Compteur;
	cin >> Nombre1;
	cin >> Nombre2; 
	
	if (Nombre1 > Nombre2 && Nombre1 % Nombre2 == 0)
	{
		PGCD = Nombre1;
	}
	else if (Nombre2 > Nombre1 && Nombre2 % Nombre1 == 0)
	{
		PGCD = Nombre1;
	}
	else if (Nombre1 == Nombre2)
	{
		PGCD = Nombre1;
	}
	else
	{
		if (Nombre1 < Nombre2)
		{
			PlusPetit = Nombre1;
		}
		else
		{
			PlusPetit = Nombre2;
		}

		Compteur = 2;
		while (Compteur <= PlusPetit / 2)
		{
			if (Nombre1 % Compteur == 0 || Nombre2 % Compteur == 0)
			{
				PGCD = Compteur;
			}
		}
	}

	if (PGCD == 1)
	{
		cout << "Aucun diviseur commun a ces nombres" << endl;
	}
	else
	{
		cout << "Le plus grand commun diviseur a ces nombres est " << PGCD << endl;
	}
}