Techniques de débogage avancées
Dans cette dernière étape, nous allons explorer des techniques plus avancées pour diagnostiquer et résoudre les erreurs de symbole non défini.
Utilisation des options du compilateur et de l'éditeur de liens
Les options du compilateur et de l'éditeur de liens peuvent fournir plus d'informations sur ce qui ne va pas. Créez un fichier nommé debug_example.cpp :
#include <iostream>
// Forward declaration without implementation
void missingFunction();
int main() {
std::cout << "Calling missing function..." << std::endl;
missingFunction();
return 0;
}
Compilons ceci avec une sortie verbeuse :
g++ debug_example.cpp -o debug_example -v
Cela vous donnera des informations détaillées sur le processus de compilation et de liaison. Vous verrez une erreur de référence non définie pour missingFunction.
Utilisation de l'outil nm
L'outil nm affiche les symboles dans les fichiers objets et les bibliothèques. Cela peut être utile pour vérifier si un symbole est réellement défini.
Créons un programme simple avec un fichier d'implémentation. Tout d'abord, créez functions.h :
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
void sayHello();
void sayGoodbye();
#endif
Ensuite, créez functions.cpp :
#include <iostream>
#include "functions.h"
void sayHello() {
std::cout << "Hello, world!" << std::endl;
}
// Notice: sayGoodbye is not implemented
Maintenant, créez greetings.cpp :
#include "functions.h"
int main() {
sayHello();
sayGoodbye(); // This will cause an undefined symbol error
return 0;
}
Compilez le fichier d'implémentation en un fichier objet :
g++ -c functions.cpp -o functions.o
Utilisons maintenant nm pour voir quels symboles sont définis dans le fichier objet :
nm functions.o
Vous devriez voir une sortie similaire à :
U __cxa_atexit
U __dso_handle
0000000000000000 T _Z8sayHellov
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
0000000000000000 r _ZStL19piecewise_construct
0000000000000000 b _ZStL8__ioinit
Notez que sayHello est défini (indiqué par le T pour la section texte/code), mais il n'y a pas de symbole pour sayGoodbye. Cela confirme que l'implémentation de la fonction est manquante.
Diagnostic avec l'outil ldd
L'outil ldd affiche les dépendances de bibliothèque pour un exécutable. Ceci est utile lorsque vous avez des problèmes de liaison de bibliothèque.
Créons un exemple simple qui utilise la bibliothèque pthread. Créez un fichier nommé thread_example.cpp :
#include <iostream>
#include <pthread.h>
void* threadFunction(void* arg) {
std::cout << "Thread running" << std::endl;
return nullptr;
}
int main() {
pthread_t thread;
int result = pthread_create(&thread, nullptr, threadFunction, nullptr);
if (result != 0) {
std::cerr << "Failed to create thread" << std::endl;
return 1;
}
pthread_join(thread, nullptr);
std::cout << "Thread completed" << std::endl;
return 0;
}
Compilez avec la bibliothèque pthread :
g++ thread_example.cpp -o thread_example -pthread
Utilisez maintenant ldd pour vérifier les dépendances de bibliothèque :
ldd thread_example
Vous devriez voir une sortie listant toutes les bibliothèques partagées dont l'exécutable dépend, y compris la bibliothèque pthread.
Causes et solutions courantes des erreurs de symbole non défini
Résumons les causes courantes des erreurs de symbole non défini et leurs solutions :
| Cause |
Solution |
| Implémentation de fonction manquante |
Implémenter la fonction ou lier au fichier contenant l'implémentation |
| Liaison de bibliothèque manquante |
Ajouter l'option -l appropriée (par exemple, -lm pour math) |
| Problèmes d'espace de noms |
Utiliser des noms qualifiés (Namespace::function) ou des directives/déclarations using |
| Limitations de portée |
S'assurer que les symboles sont accessibles depuis la portée appelante |
| Mangling des noms de symboles |
Utiliser extern "C" pour l'interopérabilité C/C++ ou un démangling approprié |
| Erreurs d'instanciation de modèles |
Fournir une instanciation explicite de modèle ou déplacer l'implémentation vers l'en-tête |
Création d'une liste de contrôle pour le débogage
Voici une approche systématique pour le débogage des erreurs de symbole non défini :
-
Identifier le symbole non défini exact
- Regardez attentivement le message d'erreur
- Utilisez
nm pour vérifier si le symbole existe dans les fichiers objets
-
Vérifier les problèmes d'implémentation
- S'assurer que toutes les fonctions déclarées ont des implémentations
- S'assurer que les fichiers d'implémentation sont inclus dans la compilation
-
Vérifier la liaison de la bibliothèque
- Ajouter les options de bibliothèque nécessaires (par exemple,
-lm, -lpthread)
- Utiliser
ldd pour vérifier les dépendances de bibliothèque
-
Examiner l'espace de noms et la portée
- Vérifier la qualification de l'espace de noms
- Vérifier la visibilité et la portée des symboles
-
Rechercher les problèmes de mangling de noms
- Ajouter
extern "C" pour l'interopérabilité C/C++
-
Gérer les erreurs liées aux modèles
- Déplacer les implémentations de modèles vers les fichiers d'en-tête
- Fournir une instanciation explicite si nécessaire
Exemple final : tout mettre en œuvre
Créons un exemple complet qui démontre les meilleures pratiques pour éviter les erreurs de symbole non défini. Nous allons créer un petit projet avec une organisation appropriée :
- Tout d'abord, créez une structure de répertoire :
mkdir -p library/include library/src app
- Créez des fichiers d'en-tête dans le répertoire include. Tout d'abord, créez
library/include/calculations.h :
#ifndef CALCULATIONS_H
#define CALCULATIONS_H
namespace Math {
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);
double divide(double a, double b);
}
#endif
- Créez l'implémentation dans
library/src/calculations.cpp :
#include "calculations.h"
namespace Math {
double add(double a, double b) {
return a + b;
}
double subtract(double a, double b) {
return a - b;
}
double multiply(double a, double b) {
return a * b;
}
double divide(double a, double b) {
return a / b;
}
}
- Créez une application principale dans
app/calculator.cpp :
#include <iostream>
#include "calculations.h"
int main() {
double a = 10.0;
double b = 5.0;
std::cout << a << " + " << b << " = " << Math::add(a, b) << std::endl;
std::cout << a << " - " << b << " = " << Math::subtract(a, b) << std::endl;
std::cout << a << " * " << b << " = " << Math::multiply(a, b) << std::endl;
std::cout << a << " / " << b << " = " << Math::divide(a, b) << std::endl;
return 0;
}
- Compilez le tout correctement :
g++ -c library/src/calculations.cpp -I library/include -o calculations.o
g++ app/calculator.cpp calculations.o -I library/include -o calculator
- Exécutez l'application :
./calculator
Vous devriez voir la sortie correcte :
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2
Cet exemple démontre une séparation appropriée de la déclaration et de l'implémentation, des espaces de noms et une compilation et une liaison correctes. En suivant ces pratiques, vous pouvez éviter la plupart des erreurs de symbole non défini.