Que sont les erreurs de référence externe non définies / symboles non résolus? Quelles sont les causes courantes et comment les corriger / les prévenir?
N'hésitez pas à modifier / ajouter le vôtre.
c++
linker-errors
undefined-reference
c++-faq
unresolved-external
Luchian Grigore
la source
la source
Réponses:
La compilation d'un programme C ++ se déroule en plusieurs étapes, comme spécifié par 2.2 (crédits à Keith Thompson pour la référence) :
Les erreurs spécifiées se produisent au cours de cette dernière étape de la compilation, plus communément appelée liaison. Cela signifie essentiellement que vous avez compilé un tas de fichiers d'implémentation dans des fichiers objets ou des bibliothèques et que vous souhaitez maintenant les faire fonctionner ensemble.
Supposons que vous ayez défini le symbole
a
dansa.cpp
. Maintenant, ab.cpp
déclaré ce symbole et l'a utilisé. Avant de lier, il suppose simplement que ce symbole a été défini quelque part , mais il ne se soucie pas encore où. La phase de liaison est chargée de trouver le symbole et de le lier correctementb.cpp
(enfin, en fait à l'objet ou à la bibliothèque qui l'utilise).Si vous utilisez Microsoft Visual Studio, vous verrez que les projets génèrent des
.lib
fichiers. Ceux-ci contiennent un tableau des symboles exportés et un tableau des symboles importés. Les symboles importés sont résolus par rapport aux bibliothèques que vous liez et les symboles exportés sont fournis pour les bibliothèques qui l'utilisent.lib
(le cas échéant).Des mécanismes similaires existent pour d'autres compilateurs / plates-formes.
Messages d'erreur courants sont
error LNK2001
,error LNK1120
,error LNK2019
pour Microsoft Visual Studio etundefined reference to
symbolName pour GCC .Le code:
générera les erreurs suivantes avec GCC :
et erreurs similaires avec Microsoft Visual Studio :
Les causes courantes incluent:
#pragma
(Microsoft Visual Studio)UNICODE
Définitions incohérentesla source
Membres de la classe:
Un
virtual
destructeur pur a besoin d'une implémentation.Pour déclarer un destructeur pur, vous devez toujours le définir (contrairement à une fonction régulière):
Cela se produit car les destructeurs de classe de base sont appelés lorsque l'objet est détruit implicitement, une définition est donc requise.
virtual
les méthodes doivent être implémentées ou définies comme pures.Ceci est similaire aux non-
virtual
méthodes sans définition, avec le raisonnement supplémentaire que la déclaration pure génère une vtable factice et vous pouvez obtenir l'erreur de l'éditeur de liens sans utiliser la fonction:Pour que cela fonctionne, déclarez
X::foo()
pur:virtual
Membres hors classeCertains membres doivent être définis même s'ils ne sont pas utilisés explicitement:
Les éléments suivants produiraient l'erreur:
L'implémentation peut être en ligne, dans la définition de classe elle-même:
ou à l'extérieur:
Si l'implémentation est en dehors de la définition de classe, mais dans un en-tête, les méthodes doivent être marquées de manière
inline
à empêcher une définition multiple.Toutes les méthodes membres utilisées doivent être définies si elles sont utilisées.
Une erreur courante consiste à oublier de qualifier le nom:
La définition devrait être
static
les membres de données doivent être définis en dehors de la classe dans une seule unité de traduction :Un initialiseur peut être fourni pour un
static
const
membre de données de type intégral ou énumération dans la définition de classe; cependant, l'odr-utilisation de ce membre nécessitera toujours une définition de portée d'espace de noms comme décrit ci-dessus. C ++ 11 permet l'initialisation à l'intérieur de la classe pour tousstatic const
les membres de données.la source
Échec de la liaison avec les bibliothèques / fichiers objets appropriés ou la compilation des fichiers d'implémentation
Généralement, chaque unité de traduction génère un fichier objet qui contient les définitions des symboles définis dans cette unité de traduction. Pour utiliser ces symboles, vous devez établir un lien avec ces fichiers objets.
Sous gcc, vous devez spécifier tous les fichiers objets qui doivent être liés ensemble dans la ligne de commande, ou compiler les fichiers d'implémentation ensemble.
le
libraryName
ici est juste le nom nu de la bibliothèque, sans ajouts spécifiques à la plate-forme. Ainsi, par exemple, sous Linux, les fichiers de bibliothèque sont généralement appeléslibfoo.so
mais vous n'écrivez que-lfoo
. Sous Windows, ce même fichier peut être appeléfoo.lib
, mais vous utiliseriez le même argument. Vous devrez peut-être ajouter le répertoire où ces fichiers peuvent être trouvés à l'aide-L‹directory›
. Assurez-vous de ne pas écrire d'espace après-l
ou-L
.Pour XCode : ajoutez les chemins de recherche d'en-tête utilisateur -> ajoutez le chemin de recherche de bibliothèque -> faites glisser et déposez la référence de bibliothèque réelle dans le dossier du projet.
Sous MSVS , les fichiers ajoutés à un projet ont automatiquement leurs fichiers objets liés ensemble et un
lib
fichier est généré (en usage courant). Pour utiliser les symboles dans un projet distinct, vous devez inclure lelib
fichiers dans les paramètres du projet. Cela se fait dans la section Linker des propriétés du projet, dansInput -> Additional Dependencies
. (le chemin d'accès aulib
fichier doit être ajoutéLinker -> General -> Additional Library Directories
) Lorsque vous utilisez une bibliothèque tierce qui est fournie avec unlib
fichier, l'échec de cette opération entraîne généralement l'erreur.Il peut également arriver que vous oubliez d'ajouter le fichier à la compilation, auquel cas le fichier objet ne sera pas généré. Dans gcc, vous ajouteriez les fichiers à la ligne de commande. Dans MSVS, l' ajout du fichier au projet le fera le compiler automatiquement (bien que les fichiers puissent, manuellement, être exclus individuellement de la construction).
Dans la programmation Windows, le signe révélateur que vous n'avez pas lié une bibliothèque nécessaire est que le nom du symbole non résolu commence par
__imp_
. Recherchez le nom de la fonction dans la documentation, et il devrait indiquer quelle bibliothèque vous devez utiliser. Par exemple, MSDN place les informations dans une boîte au bas de chaque fonction dans une section intitulée "Bibliothèque".la source
gcc main.c
lieu degcc main.c other.c
(ce que les débutants font souvent avant que leurs projets ne deviennent trop volumineux pour créer des fichiers .o).Déclaré mais n'a pas défini de variable ou de fonction.
Une déclaration de variable typique est
Comme il ne s'agit que d'une déclaration, une définition unique est nécessaire. Une définition correspondante serait:
Par exemple, ce qui suit générerait une erreur:
Des remarques similaires s'appliquent aux fonctions. Déclarer une fonction sans la définir conduit à l'erreur:
Veillez à ce que la fonction que vous implémentez corresponde exactement à celle que vous avez déclarée. Par exemple, vous pouvez avoir des qualificatifs cv incompatibles:
D'autres exemples de disparités comprennent
Le message d'erreur du compilateur vous donnera souvent la déclaration complète de la variable ou de la fonction qui a été déclarée mais jamais définie. Comparez-le étroitement à la définition que vous avez fournie. Assurez-vous que chaque détail correspond.
la source
#includes
non ajoutés au répertoire source entrent également dans la catégorie des définitions manquantes.L'ordre dans lequel les bibliothèques liées interdépendantes sont spécifiées est incorrect.
L'ordre dans lequel les bibliothèques sont liées importe si les bibliothèques dépendent les unes des autres. En général, si la bibliothèque
A
dépend de la bibliothèqueB
, alorslibA
DOIT apparaître avantlibB
dans les indicateurs de l'éditeur de liens.Par exemple:
Créez les bibliothèques:
Compiler:
Donc , pour répéter encore une fois, l'ordre DOES affaire!
la source
qu'est-ce qu'un "référence externe non définie / symbole externe non résolu"
Je vais essayer d'expliquer ce qu'est un "symbole externe de référence non défini / non résolu".
Par exemple, nous avons du code
et
Créer des fichiers objets
Après la phase d'assemblage, nous avons un fichier objet, qui contient tous les symboles à exporter. Regardez les symboles
J'ai rejeté certaines lignes de la sortie, car elles n'ont pas d'importance
Ainsi, nous voyons suivre les symboles à exporter.
src2.cpp n'exporte rien et nous n'avons vu aucun de ses symboles
Liez nos fichiers objets
et l'exécuter
L'éditeur de liens voit les symboles exportés et les relie. Maintenant, nous essayons de décommenter les lignes dans src2.cpp comme ici
et reconstruire un fichier objet
OK (pas d'erreurs), car nous ne construisons que le fichier objet, la liaison n'est pas encore effectuée. Essayez de lier
Cela s'est produit parce que notre nom_var_local est statique, c'est-à-dire qu'il n'est pas visible pour les autres modules. Maintenant, plus profondément. Obtenez la sortie de la phase de traduction
Donc, nous avons vu qu'il n'y a pas d'étiquette pour local_var_name, c'est pourquoi l'éditeur de liens ne l'a pas trouvé. Mais nous sommes des pirates :) et nous pouvons y remédier. Ouvrez src1.s dans votre éditeur de texte et changez
à
c'est à dire que vous devriez avoir comme ci-dessous
nous avons modifié la visibilité de local_var_name et défini sa valeur sur 456789. Essayez de créer un fichier objet à partir de celui-ci
ok, voir sortie readelf (symboles)
maintenant local_var_name a Bind GLOBAL (était LOCAL)
lien
et l'exécuter
ok, on le pirate :)
Par conséquent, une "erreur de référence non définie / symbole externe non résolu" se produit lorsque l'éditeur de liens ne peut pas trouver de symboles globaux dans les fichiers d'objets.
la source
Les symboles ont été définis dans un programme C et utilisés dans le code C ++.
La fonction (ou variable) a
void foo()
été définie dans un programme C et vous essayez de l'utiliser dans un programme C ++:L'éditeur de liens C ++ s'attend à ce que les noms soient modifiés, vous devez donc déclarer la fonction comme suit:
De manière équivalente, au lieu d'être définie dans un programme C, la fonction (ou variable) a
void foo()
été définie en C ++ mais avec la liaison C:et vous essayez de l'utiliser dans un programme C ++ avec une liaison C ++.
Si une bibliothèque entière est incluse dans un fichier d'en-tête (et a été compilée en code C); l'inclusion devra être la suivante;
la source
#ifdef __cplusplus [\n] extern"C" { [\n] #endif
et#ifdef __cplusplus [\n] } [\n] #endif
([\n]
étant un vrai retour chariot mais je ne peux pas écrire cela correctement en commentaire).extern "C" { #include <myCppHeader.h> }
.Si tout le reste échoue, recompilez.
J'ai récemment pu me débarrasser d'une erreur externe non résolue dans Visual Studio 2012 simplement en recompilant le fichier incriminé. Quand j'ai reconstruit, l'erreur a disparu.
Cela se produit généralement lorsque deux bibliothèques (ou plus) ont une dépendance cyclique. La bibliothèque A tente d'utiliser des symboles dans B.lib et la bibliothèque B tente d'utiliser des symboles d'A.lib. Ni existent pour commencer. Lorsque vous essayez de compiler A, l'étape de liaison échouera car il ne peut pas trouver B.lib. A.lib sera généré, mais pas de dll. Vous compilez ensuite B, qui réussira et générera B.lib. Recompiler A fonctionnera maintenant parce que B.lib est maintenant trouvé.
la source
Importation / exportation incorrecte des méthodes / classes à travers les modules / dll (spécifiques au compilateur).
MSVS vous oblige à spécifier quels symboles exporter et importer à l'aide de
__declspec(dllexport)
et__declspec(dllimport)
.Cette double fonctionnalité est généralement obtenue grâce à l'utilisation d'une macro:
La macro
THIS_MODULE
ne serait définie que dans le module qui exporte la fonction. De cette façon, la déclaration:s'étend à
et indique au compilateur d'exporter la fonction, car le module actuel contient sa définition. En incluant la déclaration dans un module différent, il s'élargirait à
et indique au compilateur que la définition se trouve dans l'une des bibliothèques auxquelles vous avez lié (voir également 1) ).
Vous pouvez comparer des classes d'import / export:
la source
visibility
les.def
fichiers de GCC et Windows , car ceux-ci influencent également le nom et la présence du symbole..def
fichiers depuis des lustres. N'hésitez pas à ajouter une réponse ou à modifier celle-ci.C'est l'un des messages d'erreur les plus déroutants que tous les programmeurs VC ++ ont vus maintes et maintes fois. Clarifions d'abord les choses.
A. Qu'est-ce que le symbole? Bref, un symbole est un nom. Il peut s'agir d'un nom de variable, d'un nom de fonction, d'un nom de classe, d'un nom de typedef ou de tout autre élément que les noms et les signes qui appartiennent au langage C ++. Il est défini par l'utilisateur ou introduit par une bibliothèque de dépendances (une autre définie par l'utilisateur).
B. Qu'est-ce qui est externe? Dans VC ++, chaque fichier source (.cpp, .c, etc.) est considéré comme une unité de traduction, le compilateur compile une unité à la fois et génère un fichier objet (.obj) pour l'unité de traduction actuelle. (Notez que chaque fichier d'en-tête inclus dans ce fichier source sera prétraité et sera considéré comme faisant partie de cette unité de traduction) Tout dans une unité de traduction est considéré comme interne, tout le reste est considéré comme externe. En C ++, vous pouvez référencer un symbole externe en utilisant des mots clés comme
extern
,__declspec (dllimport)
etc.C. Qu'est-ce que la «résolution»? Resolve est un terme de liaison. En temps de liaison, l'éditeur de liens tente de trouver la définition externe pour chaque symbole dans les fichiers objet qui ne peuvent pas trouver sa définition en interne. La portée de ce processus de recherche, y compris:
Ce processus de recherche est appelé résoudre.
D. Enfin, pourquoi un symbole externe non résolu? Si l'éditeur de liens ne peut pas trouver la définition externe d'un symbole qui n'a pas de définition en interne, il signale une erreur de symbole externe non résolu.
E. Causes possibles de LNK2019 : erreur de symbole externe non résolue . Nous savons déjà que cette erreur est due au fait que l'éditeur de liens n'a pas trouvé la définition des symboles externes, les causes possibles peuvent être triées comme suit:
Par exemple, si nous avons une fonction appelée foo définie dans a.cpp:
Dans b.cpp, nous voulons appeler la fonction foo, donc nous ajoutons
pour déclarer la fonction foo (), et l'appeler dans un autre corps de fonction, dites
bar()
:Maintenant, lorsque vous générez ce code, vous obtiendrez une erreur LNK2019 se plaignant que foo est un symbole non résolu. Dans ce cas, nous savons que foo () a sa définition dans a.cpp, mais différente de celle que nous appelons (valeur de retour différente). C'est le cas où cette définition existe.
Si nous voulons appeler certaines fonctions dans une bibliothèque, mais que la bibliothèque d'importation n'est pas ajoutée à la liste de dépendances supplémentaire (définie à partir
Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
de:) de la configuration de votre projet. Maintenant, l'éditeur de liens signalera un LNK2019 car la définition n'existe pas dans la portée de recherche actuelle.la source
Les implémentations de modèle ne sont pas visibles.
Les modèles non spécialisés doivent avoir leurs définitions visibles pour toutes les unités de traduction qui les utilisent. Cela signifie que vous ne pouvez pas séparer la définition d'un modèle d'un fichier d'implémentation. Si vous devez séparer l'implémentation, la solution habituelle consiste à avoir un
impl
fichier que vous incluez à la fin de l'en-tête qui déclare le modèle. Une situation courante est:Pour résoudre ce problème, vous devez déplacer la définition de
X::foo
dans le fichier d'en-tête ou à un endroit visible par l'unité de traduction qui l'utilise.Les modèles spécialisés peuvent être implémentés dans un fichier d'implémentation et l'implémentation n'a pas besoin d'être visible, mais la spécialisation doit être déclarée au préalable.
Pour plus d'explications et une autre solution possible (instanciation explicite), voir cette question et réponse .
la source
référence indéfinie à une référence de point d'entrée "inhabituelle"
WinMain@16
similairemain()
(en particulier pourVisual Studio).Vous avez peut-être manqué de choisir le bon type de projet avec votre IDE réel. L'IDE peut vouloir lier, par exemple, des projets d'application Windows à une telle fonction de point d'entrée (comme spécifié dans la référence manquante ci-dessus), au lieu de la
int main(int argc, char** argv);
signature couramment utilisée .Si votre IDE prend en charge les projets Plain Console, vous pouvez choisir ce type de projet au lieu d'un projet d'application Windows.
Voici les cas 1 et 2 traités plus en détail à partir d'un problème réel .
la source
WinMain
. Les programmes C ++ valides ont besoin d'unmain
.De plus, si vous utilisez des bibliothèques tierces, assurez-vous d'avoir les bons binaires 32/64 bits
la source
Microsoft propose
#pragma
de référencer la bonne bibliothèque au moment du lien;En plus du chemin d'accès à la bibliothèque, y compris le répertoire de la bibliothèque, il doit s'agir du nom complet de la bibliothèque.
la source
Le package Visual Studio NuGet doit être mis à jour pour la nouvelle version du jeu d'outils
Je viens d'avoir ce problème en essayant de lier libpng à Visual Studio 2013. Le problème est que le fichier de package ne contenait que des bibliothèques pour Visual Studio 2010 et 2012.
La bonne solution est d'espérer que le développeur publie un package mis à jour puis de mettre à niveau, mais cela a fonctionné pour moi en piratant un paramètre supplémentaire pour VS2013, en pointant les fichiers de la bibliothèque VS2012.
J'ai édité le package (dans le
packages
dossier dans le répertoire de la solution) en trouvantpackagename\build\native\packagename.targets
et à l'intérieur de ce fichier, en copiant toutes lesv110
sections. J'ai changév110
pourv120
dans les champs de l' état que d' être très prudent de laisser les chemins de nom de fichier tout commev110
. Cela a simplement permis à Visual Studio 2013 de se lier aux bibliothèques pour 2012, et dans ce cas, cela a fonctionné.la source
Supposons que vous ayez un gros projet écrit en c ++ qui contient un millier de fichiers .cpp et un millier de fichiers .h. Et disons que le projet dépend également de dix bibliothèques statiques. Disons que nous sommes sous Windows et que nous construisons notre projet dans Visual Studio 20xx. Lorsque vous appuyez sur Ctrl + F7 Visual Studio pour commencer à compiler la solution entière (supposons que nous ayons un seul projet dans la solution)
Quel est le sens de la compilation?
La deuxième étape de la compilation est effectuée par Linker.Linker doit fusionner tout le fichier objet et construire finalement la sortie (qui peut être un exécutable ou une bibliothèque)
Étapes pour lier un projet
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
Observation
Comment résoudre ce type d'erreur
Erreur de temps du compilateur:
Erreur de temps de l'éditeur de liens
#pragma once
pour permettre au compilateur de ne pas inclure un en-tête s'il était déjà inclus dans le .cpp actuel qui sont compilésla source
Un bug dans le compilateur / IDE
J'ai récemment rencontré ce problème et il s'est avéré qu'il s'agissait d'un bogue dans Visual Studio Express 2013 . J'ai dû supprimer un fichier source du projet et l'ajouter à nouveau pour surmonter le bogue.
Étapes à suivre si vous pensez que cela pourrait être un bogue dans le compilateur / IDE:
la source
Utilisez l'éditeur de liens pour diagnostiquer l'erreur
La plupart des éditeurs de liens modernes incluent une option détaillée qui imprime à des degrés divers;
Pour gcc et clang; vous ajouteriez généralement
-v -Wl,--verbose
ou-v -Wl,-v
à la ligne de commande. Plus de détails ici;Pour MSVC,
/VERBOSE
(en particulier/VERBOSE:LIB
) est ajouté à la ligne de commande de liaison./VERBOSE
option de l' éditeur de liens .la source
Le fichier .lib lié est associé à un .dll
J'ai eu le même problème. Disons que j'ai des projets MyProject et TestProject. J'avais effectivement lié le fichier lib pour MyProject au TestProject. Cependant, ce fichier lib a été produit lors de la création de la DLL du MyProject. De plus, je ne contenais pas le code source de toutes les méthodes du MyProject, mais seulement l'accès aux points d'entrée de la DLL.
Pour résoudre le problème, j'ai créé le MyProject en tant que LIB et lié TestProject à ce fichier .lib (je copie et colle le fichier .lib généré dans le dossier TestProject). Je peux ensuite reconstruire MyProject en tant que DLL. Il est en cours de compilation, car la bibliothèque à laquelle TestProject est lié contient du code pour toutes les méthodes des classes de MyProject.
la source
Étant donné que les gens semblent être dirigés vers cette question en ce qui concerne les erreurs de l'éditeur de liens, je vais ajouter ceci ici.
Une raison possible des erreurs de l'éditeur de liens avec GCC 5.2.0 est qu'une nouvelle bibliothèque libstdc ++ ABI est maintenant choisie par défaut.
Donc, si vous obtenez soudainement des erreurs de l'éditeur de liens lors du passage à un GCC après 5.1.0, ce serait une chose à vérifier.
la source
Un wrapper autour de GNU ld qui ne prend pas en charge les scripts de l'éditeur de liens
Certains fichiers .so sont en fait des scripts de l'éditeur de liens GNU ld , par exemple le fichier libtbb.so est un fichier texte ASCII avec ce contenu:
Certaines versions plus complexes peuvent ne pas prendre en charge cela. Par exemple, si vous incluez -v dans les options du compilateur, vous pouvez voir que le wrapper mainwin gcc mwdip supprime les fichiers de commandes du script de l'éditeur de liens dans la liste de sortie détaillée des bibliothèques à lier. Une solution simple consiste à remplacer la commande d'entrée du script de l'éditeur de liens fichier avec une copie du fichier à la place (ou un lien symbolique), par exemple
Ou vous pouvez remplacer l'argument -l par le chemin complet du .so, par exemple au lieu de
-ltbb
faire/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
la source
Votre liaison consomme des bibliothèques avant les fichiers objets qui s'y réfèrent
libfoo
dépendlibbar
, alors votre lien passe correctementlibfoo
avantlibbar
.undefined reference to
quelque chose d' erreurs.#include
d et sont en fait définies dans les bibliothèques que vous liez.Des exemples sont en C. Ils pourraient tout aussi bien être C ++
Un exemple minimal impliquant une bibliothèque statique que vous avez construite vous-même
my_lib.c
my_lib.h
eg1.c
Vous construisez votre bibliothèque statique:
Vous compilez votre programme:
Vous essayez de le lier avec
libmy_lib.a
et échouez:Le même résultat si vous compilez et liez en une seule étape, comme:
Un exemple minimal impliquant une bibliothèque système partagée, la bibliothèque de compression
libz
eg2.c
Compilez votre programme:
Essayez de lier votre programme avec
libz
et échouez:Même chose si vous compilez et liez en une seule fois:
Et une variante de l'exemple 2 impliquant
pkg-config
:Qu'est-ce que tu fais mal?
Dans la séquence de fichiers objets et bibliothèques que vous souhaitez lier pour créer votre programme, vous placez les bibliothèques avant les fichiers objets qui s'y réfèrent. Vous devez placer les bibliothèques après les fichiers objets qui s'y réfèrent.
Liez l'exemple 1 correctement:
Succès:
Lien exemple 2 correctement:
Succès:
Liez
pkg-config
correctement la variante de l'exemple 2 :L'explication
La lecture est facultative à partir d'ici .
Par défaut, une commande de liaison générée par GCC, sur votre distribution, consomme les fichiers dans la liaison de gauche à droite dans la séquence de ligne de commande. Lorsqu'il détecte qu'un fichier fait référence à quelque chose et ne contient pas de définition, il recherchera une définition dans les fichiers plus à droite. S'il finit par trouver une définition, la référence est résolue. Si des références restent non résolues à la fin, la liaison échoue: l'éditeur de liens ne recherche pas en arrière.
Tout d'abord, l' exemple 1 , avec la bibliothèque statique
my_lib.a
Une bibliothèque statique est une archive indexée de fichiers objets. Lorsque l'éditeur de liens trouve
-lmy_lib
dans la séquence de liens et comprend qu'il s'agit de la bibliothèque statique./libmy_lib.a
, il veut savoir si votre programme a besoin de l'un des fichiers objets delibmy_lib.a
.Il n'y a qu'un fichier objet dans
libmy_lib.a
, à savoirmy_lib.o
, et il n'y a qu'une seule chose définie dansmy_lib.o
, à savoir la fonctionhw
.L'éditeur de liens décidera que votre programme a besoin
my_lib.o
si et seulement s'il sait déjà qu'il fait référence àhw
, dans un ou plusieurs des fichiers objets qu'il a déjà ajoutés au programme, et qu'aucun des fichiers objets qu'il a déjà ajoutés ne contient définition pourhw
.Si cela est vrai, l'éditeur de liens extrait une copie de
my_lib.o
la bibliothèque et l'ajoute à votre programme. Ensuite, votre programme contient une définition pourhw
, donc ses références àhw
sont résolues .Lorsque vous essayez de lier le programme comme:
l'éditeur de liens n'a pas ajouté
eg1.o
au programme lorsqu'il le voit-lmy_lib
. Parce qu'à ce stade, il n'a pas vueg1.o
. Votre programme ne fait pas encore référence àhw
: il ne fait pas encore de référence du tout , car toutes les références qu'il fait sont dedanseg1.o
.L'éditeur de liens n'ajoute donc pas
my_lib.o
au programme et n'a plus aucune utilité pourlibmy_lib.a
.Ensuite, il le trouve
eg1.o
et l'ajoute au programme. Un fichier objet dans la séquence de liaison est toujours ajouté au programme. Maintenant, le programme fait référence àhw
, et ne contient pas de définition dehw
; mais il ne reste rien dans la séquence de liaison qui pourrait fournir la définition manquante. La référence àhw
finit non résolue , et le lien échoue.Seconde, exemple 2 , avec bibliothèque partagée
libz
Une bibliothèque partagée n'est pas une archive de fichiers objets ou quelque chose comme ça. C'est beaucoup plus comme un programme qui n'a pas de
main
fonction et expose à la place plusieurs autres symboles qu'il définit, afin que d'autres programmes puissent les utiliser au moment de l'exécution.Beaucoup aujourd'hui de Linux configurer leur GCC toolchain pour que ses pilotes linguistiques (
gcc
,g++
,gfortran
etc.) charger le groupe de liaison du système (ld
) pour lier les bibliothèques partagées sur un au besoin base. Vous avez une de ces distributions.Cela signifie que lorsque l'éditeur de liens trouve
-lz
dans la séquence de liaison, et comprend que cela fait référence à la bibliothèque partagée (par exemple)/usr/lib/x86_64-linux-gnu/libz.so
, il veut savoir si les références qu'il a ajoutées à votre programme qui ne sont pas encore définies ont des définitions qui sont exporté parlibz
Si cela est vrai, l'éditeur de liens ne copiera aucun morceau
libz
et ne les ajoutera pas à votre programme; au lieu de cela, il suffit de docteur le code de votre programme afin que: -Au moment de l'exécution, le chargeur de programme système charge une copie de
libz
dans le même processus que votre programme chaque fois qu'il charge une copie de votre programme, pour l'exécuter.Au moment de l'exécution, chaque fois que votre programme fait référence à quelque chose qui est défini dans
libz
, cette référence utilise la définition exportée par la copie delibz
dans le même processus.Votre programme veut se référer à une seule chose qui a une définition exportée par
libz
, à savoir la fonctionzlibVersion
, qui n'est mentionnée qu'une seule fois, danseg2.c
. Si l'éditeur de liens ajoute cette référence à votre programme, puis trouve la définition exportée parlibz
, la référence est résolueMais lorsque vous essayez de lier le programme comme:
l'ordre des événements est erroné de la même manière qu'avec l'exemple 1. Au moment où l'éditeur de liens le trouve
-lz
, il n'y a aucune référence à quoi que ce soit dans le programme: ils sont tous dedanseg2.o
, ce qui n'a pas encore été vu. L'éditeur de liens décide donc qu'il n'a aucune utilitélibz
. Quand il atteinteg2.o
, l'ajoute au programme et a ensuite une référence indéfinie àzlibVersion
, la séquence de liaison est terminée; cette référence n'est pas résolue et la liaison échoue.Enfin, la
pkg-config
variation de l'exemple 2 a une explication désormais évidente. Après expansion du shell:devient:
qui est juste l'exemple 2 à nouveau.
Je peux reproduire le problème dans l'exemple 1, mais pas dans l'exemple 2
Le lien:
fonctionne très bien pour vous!
(Ou: cette liaison a bien fonctionné pour vous, disons, Fedora 23, mais échoue sur Ubuntu 16.04)
En effet, la distribution sur laquelle fonctionne la liaison est l'une de celles qui ne configure pas sa chaîne d'outils GCC pour lier les bibliothèques partagées selon les besoins .
À l'époque, il était normal que des systèmes de type Unix relient des bibliothèques statiques et partagées par différentes règles. Les bibliothèques statiques dans une séquence de liaison ont été liées selon les besoins expliqués dans l'exemple 1, mais les bibliothèques partagées ont été liées sans condition.
Ce comportement est économique au moment du lien car l'éditeur de liens n'a pas à se demander si une bibliothèque partagée est nécessaire par le programme: s'il s'agit d'une bibliothèque partagée, liez-la. Et la plupart des bibliothèques dans la plupart des liens sont des bibliothèques partagées. Mais il y a aussi des inconvénients: -
Il n'est pas économique à l' exécution , car il peut entraîner le chargement de bibliothèques partagées avec un programme même s'il n'en a pas besoin.
Les différentes règles de liaison pour les bibliothèques statiques et partagées peuvent être source de confusion pour les programmeurs inexpérimentés, qui peuvent ne pas savoir si
-lfoo
leur liaison va se résoudre vers/some/where/libfoo.a
ou vers/some/where/libfoo.so
, et pourraient ne pas comprendre la différence entre les bibliothèques partagées et statiques de toute façon.Ce compromis a conduit à la situation schismatique d'aujourd'hui. Certaines distributions ont modifié leurs règles de liaison GCC pour les bibliothèques partagées afin que le principe selon les besoins s'applique à toutes les bibliothèques. Certaines distributions sont restées fidèles à l'ancienne.
Pourquoi est-ce que j'obtiens toujours ce problème même si je compile et lie en même temps?
Si je fais juste:
gcc doit sûrement d'
eg1.c
abord compiler , puis lier le fichier objet résultant aveclibmy_lib.a
. Alors, comment peut-il ne pas savoir que le fichier objet est nécessaire lors de la liaison?Parce que la compilation et la liaison avec une seule commande ne change pas l'ordre de la séquence de liaison.
Lorsque vous exécutez la commande ci-dessus,
gcc
vous comprenez que vous souhaitez une compilation + liaison. Donc, en arrière-plan, il génère une commande de compilation et l'exécute, puis génère une commande de liaison et l'exécute, comme si vous aviez exécuté les deux commandes:Ainsi , le lien ne comme il le fait si vous n'exécutez ces deux commandes. La seule différence que vous remarquez dans l'échec est que gcc a généré un fichier objet temporaire dans le cas de compilation + lien, car vous ne lui dites pas d'utiliser . Nous voyons:
eg1.o
au lieu de:
Voir également
L'ordre dans lequel les bibliothèques liées interdépendantes sont spécifiées est incorrect
Mettre les bibliothèques interdépendantes dans le mauvais ordre n'est qu'une façon d'obtenir des fichiers qui ont besoin de définitions de choses venant plus tard dans la liaison que les fichiers qui fournissent les définitions. Mettre les bibliothèques avant les fichiers objets qui s'y réfèrent est une autre façon de faire la même erreur.
la source
Modèles d'amitié ...
Étant donné l'extrait de code d'un type de modèle avec un opérateur ami (ou une fonction);
Le
operator<<
est déclaré comme une fonction non modèle. Pour chaque typeT
utilisé avecFoo
, il doit y avoir un modèle sans modèleoperator<<
. Par exemple, s'il y a un typeFoo<int>
déclaré, alors il doit y avoir une implémentation d'opérateur comme suit;Puisqu'il n'est pas implémenté, l'éditeur de liens ne parvient pas à le trouver et entraîne l'erreur.
Pour corriger cela, vous pouvez déclarer un opérateur de modèle avant le
Foo
type, puis déclarer en tant qu'ami, l'instanciation appropriée. La syntaxe est un peu maladroite, mais se présente comme suit;Le code ci-dessus limite l'amitié de l'opérateur à l'instanciation correspondante de
Foo
, c'est-à-dire que l'operator<< <int>
instanciation est limitée pour accéder aux membres privés de l'instanciation deFoo<int>
.Les alternatives incluent;
Permettre à l'amitié de s'étendre à toutes les instanciations des modèles, comme suit;
Ou, l'implémentation de la
operator<<
peut être effectuée en ligne dans la définition de classe;Notez que lorsque la déclaration de l'opérateur (ou de la fonction) apparaît uniquement dans la classe, le nom n'est pas disponible pour la recherche "normale", uniquement pour la recherche dépendante de l'argument, à partir de cppreference ;
Il y a plus de lecture sur les amis du modèle sur cppreference et la FAQ C ++ .
Liste de codes montrant les techniques ci-dessus .
En tant que note annexe à l'exemple de code défaillant; g ++ l'avertit comme suit
la source
Lorsque vos chemins d'inclusion sont différents
Des erreurs de l'éditeur de liens peuvent se produire lorsqu'un fichier d'en-tête et sa bibliothèque partagée associée (fichier .lib) sont désynchronisés. Laisse-moi expliquer.
Comment fonctionnent les linkers? L'éditeur de liens fait correspondre une déclaration de fonction (déclarée dans l'en-tête) avec sa définition (dans la bibliothèque partagée) en comparant leurs signatures. Vous pouvez obtenir une erreur de l'éditeur de liens si l'éditeur de liens ne trouve pas une définition de fonction qui correspond parfaitement.
Est-il possible d'obtenir toujours une erreur de l'éditeur de liens même si la déclaration et la définition semblent correspondre? Oui! Ils peuvent avoir la même apparence dans le code source, mais cela dépend vraiment de ce que le compilateur voit. Essentiellement, vous pourriez vous retrouver avec une situation comme celle-ci:
Notez comment même si les deux déclarations de fonction semblent identiques dans le code source, mais elles sont vraiment différentes selon le compilateur.
Vous pourriez vous demander comment on se retrouve dans une telle situation? Inclure des chemins bien sûr! Si lors de la compilation de la bibliothèque partagée, le chemin d'inclusion mène à
header1.h
et que vous finissez par l'utiliserheader2.h
dans votre propre programme, vous vous retrouvez à gratter votre en-tête en vous demandant ce qui s'est passé (jeu de mots).Un exemple de la façon dont cela peut se produire dans le monde réel est expliqué ci-dessous.
Poursuite de l'élaboration avec un exemple
J'ai deux projets:
graphics.lib
etmain.exe
. Les deux projets en dépendentcommon_math.h
. Supposons que la bibliothèque exporte la fonction suivante:Et puis vous allez de l'avant et incluez la bibliothèque dans votre propre projet.
Boom! Vous obtenez une erreur de l'éditeur de liens et vous ne savez pas pourquoi cela échoue. La raison en est que la bibliothèque commune utilise différentes versions du même include
common_math.h
(je l'ai rendu évident ici dans l'exemple en incluant un chemin différent, mais ce n'est peut-être pas toujours aussi évident. Peut-être que le chemin d'inclusion est différent dans les paramètres du compilateur) .Remarque dans cet exemple, l'éditeur de liens vous dirait qu'il n'a pas pu trouver
draw()
, alors qu'en réalité vous savez qu'il est évidemment exporté par la bibliothèque. Vous pourriez passer des heures à vous gratter la tête en vous demandant ce qui ne va pas. Le fait est que l'éditeur de liens voit une signature différente parce que les types de paramètres sont légèrement différents. Dans l'exemple,vec3
est un type différent dans les deux projets en ce qui concerne le compilateur. Cela peut se produire car ils proviennent de deux fichiers include légèrement différents (peut-être que les fichiers include proviennent de deux versions différentes de la bibliothèque).Débogage de l'éditeur de liens
DUMPBIN est votre ami, si vous utilisez Visual Studio. Je suis sûr que d'autres compilateurs ont d'autres outils similaires.
Le processus se déroule comme suit:
[1] Par projet, j'entends un ensemble de fichiers sources qui sont liés entre eux pour produire soit une bibliothèque soit un exécutable.
EDIT 1: Réécriture de la première section pour être plus facile à comprendre. Veuillez commenter ci-dessous pour me faire savoir si quelque chose d'autre doit être réparé. Merci!
la source
UNICODE
Définitions incohérentesUne construction Windows UNICODE est construite avec
TCHAR
etc. étant définie commewchar_t
etc. Lorsqu'elle n'est pas construite avecUNICODE
défini comme construit avecTCHAR
défini commechar
etc. Ces définitionsUNICODE
et_UNICODE
affectent tous les types de chaînes "T
" ;LPTSTR
,LPCTSTR
Et leurs élans.Construire une bibliothèque avec
UNICODE
défini et tenter de la lier dans un projet où ilUNICODE
n'est pas défini entraînera des erreurs de l'éditeur de liens car il y aura un décalage dans la définition deTCHAR
;char
par rapport àwchar_t
.L'erreur comprend généralement une fonction une valeur avec un type
char
ouwchar_t
dérivé, ceux-ci peuvent également inclurestd::basic_string<>
etc. Lorsque vous parcourez la fonction affectée dans le code, il y aura souvent une référence àTCHAR
oustd::basic_string<TCHAR>
etc. Il s'agit d'un signe révélateur que le code était initialement destiné à la fois à un UNICODE et à un caractère multi-octets (ou "étroit"). .Pour corriger cela, créez toutes les bibliothèques et tous les projets requis avec une définition cohérente de
UNICODE
(et_UNICODE
).Cela peut être fait avec l'un ou l'autre;
Ou dans les paramètres du projet;
Ou sur la ligne de commande;
L'alternative est également applicable, si UNICODE n'est pas destiné à être utilisé, assurez-vous que les définitions ne sont pas définies et / ou que le paramètre à plusieurs caractères est utilisé dans les projets et appliqué de manière cohérente.
N'oubliez pas d'être également cohérent entre les versions "Release" et "Debug".
la source
Nettoyer et reconstruire
Un «nettoyage» de la version peut supprimer le «bois mort» qui peut rester dans les versions précédentes, les versions en échec, les versions incomplètes et d'autres problèmes de génération liés au système de génération.
En général, l'EDI ou la construction inclura une certaine forme de fonction "propre", mais celle-ci peut ne pas être correctement configurée (par exemple dans un makefile manuel) ou peut échouer (par exemple, les binaires intermédiaires ou résultants sont en lecture seule).
Une fois le "nettoyage" terminé, vérifiez que le "nettoyage" a réussi et que tous les fichiers intermédiaires générés (par exemple un makefile automatisé) ont été supprimés avec succès.
Ce processus peut être considéré comme un dernier recours, mais est souvent une bonne première étape ; surtout si le code lié à l'erreur a été récemment ajouté (soit localement soit à partir du référentiel source).
la source
"Extern" manquant dans
const
les déclarations / définitions de variables (C ++ uniquement)Pour les personnes venant de C, il pourrait être surprenant qu'en C ++, les
const
variables globales aient un lien interne (ou statique). En C, ce n'était pas le cas, car toutes les variables globales le sont implicitementextern
(c'est-à-dire lorsque lestatic
mot-clé est manquant).Exemple:
correct serait d'utiliser un fichier d'en-tête et de l'inclure dans file2.cpp et file1.cpp
Alternativement, on pourrait déclarer la
const
variable dans file1.cpp avec expliciteextern
la source
Même s'il s'agit d'une assez vieille question avec plusieurs réponses acceptées, j'aimerais partager comment résoudre une obscure erreur de "référence non définie à".
Différentes versions de bibliothèques
J'utilisais un alias pour faire référence à
std::filesystem::path
: le système de fichiers est dans la bibliothèque standard depuis C ++ 17 mais mon programme devait également compiler en C ++ 14 , j'ai donc décidé d'utiliser un alias de variable:Disons que j'ai trois fichiers: main.cpp, file.h, file.cpp:
Notez les différentes bibliothèques utilisées dans main.cpp et file.h. Depuis main.cpp # include'd " file.h " après < système de fichiers >, la version du système de fichiers utilisée était celle de C ++ 17 . J'ai l'habitude de compiler le programme avec les commandes suivantes:
$
g++ -g -std=c++17 -c main.cpp
-> compile main.cpp en main.o$
g++ -g -std=c++17 -c file.cpp
-> compile file.cpp et file.h en file.o$
g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs
-> relie main.o et file.oDe cette façon, toute fonction contenue dans file.o et utilisée dans main.o qui nécessitait des
path_t
erreurs de "référence non définie" car main.o faisait référencestd::filesystem::path
mais file.o àstd::experimental::filesystem::path
.Résolution
Pour résoudre ce problème, je devais simplement changer <experimental :: filesystem> dans file.h en <filesystem> .
la source
Lors de la liaison avec des bibliothèques partagées, assurez-vous que les symboles utilisés ne sont pas masqués.
Le comportement par défaut de gcc est que tous les symboles sont visibles. Cependant, lorsque les unités de traduction sont construites avec option
-fvisibility=hidden
, seules les fonctions / symboles marqués avec__attribute__ ((visibility ("default")))
sont externes dans l'objet partagé résultant.Vous pouvez vérifier si les symboles que vous recherchez sont externes en appelant:
les symboles cachés / locaux sont représentés par
nm
un type de symbole en minuscule, par exemplet
au lieu de `T pour la section de code:Vous pouvez également utiliser
nm
avec l'option-C
pour démêler les noms (si C ++ a été utilisé).Semblable à Windows-dll, on marquerait les fonctions publiques avec un define, par exemple
DLL_PUBLIC
défini comme:Ce qui correspond à peu près à la version Windows / MSVC:
Plus d' informations sur la visibilité peuvent être trouvées sur le wiki gcc.
Lorsqu'une unité de traduction est compilée avec
-fvisibility=hidden
les symboles résultants, la liaison externe est toujours affichée (indiquée par le type de symbole majuscule parnm
) et peut être utilisée sans problème pour la liaison externe si les fichiers objet font partie d'une bibliothèque statique. La liaison ne devient locale que lorsque les fichiers objets sont liés dans une bibliothèque partagée.Pour trouver les symboles cachés dans un fichier objet, exécutez:
la source
nm -CD
ounm -gCD
pour afficher les symboles externes. Voir également Visibilité sur le wiki GCC.Différentes architectures
Vous pouvez voir un message comme:
Dans ce cas, cela signifie que les symboles disponibles sont pour une architecture différente de celle pour laquelle vous compilez.
Sur Visual Studio, cela est dû à la mauvaise «plate-forme», et vous devez soit sélectionner la bonne, soit installer la bonne version de la bibliothèque.
Sous Linux, cela peut être dû au mauvais dossier de bibliothèque (en utilisant
lib
au lieu delib64
par exemple).Sur MacOS, il est possible d'envoyer les deux architectures dans le même fichier. Il se peut que le lien s'attende à ce que les deux versions soient là, mais une seule l'est. Il peut également s'agir d'un problème avec le mauvais dossier
lib
/ danslib64
lequel la bibliothèque est récupérée.la source