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.
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.
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) :
La priorité parmi les règles de syntaxe de traduction est spécifiée par les phases suivantes [voir note de bas de page] .
- Les caractères du fichier source physique sont mappés, d'une manière définie par l'implémentation, au jeu de caractères source de base (en introduisant des caractères de nouvelle ligne pour les indicateurs de fin de ligne) si nécessaire. [COUPER]
- Chaque instance d'un caractère barre oblique inverse (\) immédiatement suivie d'un caractère de nouvelle ligne est supprimée, épissant les lignes source physiques pour former des lignes source logiques. [COUPER]
- Le fichier source est décomposé en jetons de prétraitement (2.5) et en séquences de caractères d'espaces blancs (y compris les commentaires). [COUPER]
- Les directives de prétraitement sont exécutées, les invocations de macro sont développées et les expressions d'opérateur unaire _Pragma sont exécutées. [COUPER]
- Chaque membre du jeu de caractères source dans un littéral de caractère ou un littéral de chaîne, ainsi que chaque séquence d'échappement et nom de caractère universel dans un littéral de caractère ou un littéral de chaîne non brut, est converti en membre correspondant du jeu de caractères d'exécution; [COUPER]
- Les jetons littéraux de chaîne adjacents sont concaténés.
- Les caractères d'espace blanc séparant les jetons ne sont plus significatifs. Chaque jeton de prétraitement est converti en jeton. (2.7). Les jetons résultants sont analysés syntaxiquement et sémantiquement et traduits en tant qu'unité de traduction. [COUPER]
- Les unités de traduction traduites et les unités d'instanciation sont combinées comme suit: [SNIP]
- Toutes les références d'entités externes sont résolues. Les composants de bibliothèque sont liés pour satisfaire des références externes à des entités non définies dans la traduction actuelle. Toutes ces sorties de traducteur sont collectées dans une image de programme qui contient les informations nécessaires à l'exécution dans son environnement d'exécution. (c'est moi qui souligne)
[note] Les implémentations doivent se comporter comme si ces phases distinctes se produisaient, bien qu'en pratique différentes phases puissent être regroupées.
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
dans a.cpp
. Maintenant, a b.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 correctement b.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 et undefined reference to
symbolName pour GCC .
Le code:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
struct A
{
virtual ~A() = 0;
};
struct B: A
{
virtual ~B(){}
};
extern int x;
void foo();
int main()
{
x = 0;
foo();
Y y;
B b;
}
générera les erreurs suivantes avec GCC :
/home/AbiSfw/ccvvuHoX.o: In function `main':
prog.cpp:(.text+0x10): undefined reference to `x'
prog.cpp:(.text+0x19): undefined reference to `foo()'
prog.cpp:(.text+0x2d): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD1Ev[B::~B()]+0xb): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD0Ev[B::~B()]+0x12): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1Y[typeinfo for Y]+0x8): undefined reference to `typeinfo for X'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1B[typeinfo for B]+0x8): undefined reference to `typeinfo for A'
collect2: ld returned 1 exit status
et erreurs similaires avec Microsoft Visual Studio :
1>test2.obj : error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
1>test2.obj : error LNK2001: unresolved external symbol "int x" (?x@@3HA)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall A::~A(void)" (??1A@@UAE@XZ)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall X::foo(void)" (?foo@X@@UAEXXZ)
1>...\test2.exe : fatal error LNK1120: 4 unresolved externals
Les causes courantes incluent:
#pragma
(Microsoft Visual Studio)UNICODE
Définitions incohérentesvirtual
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):
struct X
{
virtual ~X() = 0;
};
struct Y : X
{
~Y() {}
};
int main()
{
Y y;
}
//X::~X(){} //uncomment this line for successful definition
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:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
int main()
{
Y y; //linker error although there was no call to X::foo
}
Pour que cela fonctionne, déclarez X::foo()
pur:
struct X
{
virtual void foo() = 0;
};
virtual
Membres hors classeCertains membres doivent être définis même s'ils ne sont pas utilisés explicitement:
struct A
{
~A();
};
Les éléments suivants produiraient l'erreur:
A a; //destructor undefined
L'implémentation peut être en ligne, dans la définition de classe elle-même:
struct A
{
~A() {}
};
ou à l'extérieur:
A::~A() {}
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.
struct A
{
void foo();
};
void foo() {}
int main()
{
A a;
a.foo();
}
La définition devrait être
void A::foo() {}
static
les membres de données doivent être définis en dehors de la classe dans une seule unité de traduction :struct X
{
static int x;
};
int main()
{
int x = X::x;
}
//int X::x; //uncomment this line to define X::x
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 tous static const
les membres de données.
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.
g++ -o test objectFile1.o objectFile2.o -lLibraryName
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és libfoo.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, dans Input -> Additional Dependencies
. (le chemin d'accès au lib
fichier doit être ajouté Linker -> General -> Additional Library Directories
) Lorsque vous utilisez une bibliothèque tierce qui est fournie avec un lib
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".
gcc main.c
lieu de gcc 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).
Une déclaration de variable typique est
extern int x;
Comme il ne s'agit que d'une déclaration, une définition unique est nécessaire. Une définition correspondante serait:
int x;
Par exemple, ce qui suit générerait une erreur:
extern int x;
int main()
{
x = 0;
}
//int x; // uncomment this line for successful definition
Des remarques similaires s'appliquent aux fonctions. Déclarer une fonction sans la définir conduit à l'erreur:
void foo(); // declaration only
int main()
{
foo();
}
//void foo() {} //uncomment this line for successful definition
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:
void foo(int& x);
int main()
{
int x;
foo(x);
}
void foo(const int& x) {} //different function, doesn't provide a definition
//for void foo(int& x)
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.
#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 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èque B
, alors libA
DOIT apparaître avant libB
dans les indicateurs de l'éditeur de liens.
Par exemple:
// B.h
#ifndef B_H
#define B_H
struct B {
B(int);
int x;
};
#endif
// B.cpp
#include "B.h"
B::B(int xx) : x(xx) {}
// A.h
#include "B.h"
struct A {
A(int x);
B b;
};
// A.cpp
#include "A.h"
A::A(int x) : b(x) {}
// main.cpp
#include "A.h"
int main() {
A a(5);
return 0;
};
Créez les bibliothèques:
$ g++ -c A.cpp
$ g++ -c B.cpp
$ ar rvs libA.a A.o
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o
ar: creating libB.a
a - B.o
Compiler:
$ g++ main.cpp -L. -lB -lA
./libA.a(A.o): In function `A::A(int)':
A.cpp:(.text+0x1c): undefined reference to `B::B(int)'
collect2: error: ld returned 1 exit status
$ g++ main.cpp -L. -lA -lB
$ ./a.out
Donc , pour répéter encore une fois, l'ordre DOES affaire!
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".
note: j'utilise g ++ et Linux et tous les exemples sont pour ça
Par exemple, nous avons du code
// src1.cpp
void print();
static int local_var_name; // 'static' makes variable not visible for other modules
int global_var_name = 123;
int main()
{
print();
return 0;
}
et
// src2.cpp
extern "C" int printf (const char*, ...);
extern int global_var_name;
//extern int local_var_name;
void print ()
{
// printf("%d%d\n", global_var_name, local_var_name);
printf("%d\n", global_var_name);
}
Créer des fichiers objets
$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o
Après la phase d'assemblage, nous avons un fichier objet, qui contient tous les symboles à exporter. Regardez les symboles
$ readelf --symbols src1.o
Num: Value Size Type Bind Vis Ndx Name
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 _ZL14local_var_name # [1]
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_var_name # [2]
J'ai rejeté certaines lignes de la sortie, car elles n'ont pas d'importance
Ainsi, nous voyons suivre les symboles à exporter.
[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable
src2.cpp n'exporte rien et nous n'avons vu aucun de ses symboles
Liez nos fichiers objets
$ g++ src1.o src2.o -o prog
et l'exécuter
$ ./prog
123
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
// src2.cpp
extern "C" int printf (const char*, ...);
extern int global_var_name;
extern int local_var_name;
void print ()
{
printf("%d%d\n", global_var_name, local_var_name);
}
et reconstruire un fichier objet
$ g++ -c src2.cpp -o src2.o
OK (pas d'erreurs), car nous ne construisons que le fichier objet, la liaison n'est pas encore effectuée. Essayez de lier
$ g++ src1.o src2.o -o prog
src2.o: In function `print()':
src2.cpp:(.text+0x6): undefined reference to `local_var_name'
collect2: error: ld returned 1 exit status
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
$ g++ -S src1.cpp -o src1.s
// src1.s
look src1.s
.file "src1.cpp"
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
.globl global_var_name
.data
.align 4
.type global_var_name, @object
.size global_var_name, 4
global_var_name:
.long 123
.text
.globl main
.type main, @function
main:
; assembler code, not interesting for us
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
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
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
à
.globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
c'est à dire que vous devriez avoir comme ci-dessous
.file "src1.cpp"
.globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
.globl global_var_name
.align 4
.type global_var_name, @object
.size global_var_name, 4
global_var_name:
.long 123
.text
.globl main
.type main, @function
main:
; ...
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
$ g++ -c src1.s -o src2.o
ok, voir sortie readelf (symboles)
$ readelf --symbols src1.o
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 local_var_name
maintenant local_var_name a Bind GLOBAL (était LOCAL)
lien
$ g++ src1.o src2.o -o prog
et l'exécuter
$ ./prog
123456789
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 fonction (ou variable) a void foo()
été définie dans un programme C et vous essayez de l'utiliser dans un programme C ++:
void foo();
int main()
{
foo();
}
L'éditeur de liens C ++ s'attend à ce que les noms soient modifiés, vous devez donc déclarer la fonction comme suit:
extern "C" void foo();
int main()
{
foo();
}
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:
extern "C" void foo();
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;
extern "C" {
#include "cheader.h"
}
#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é.
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:
#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif
La macro THIS_MODULE
ne serait définie que dans le module qui exporte la fonction. De cette façon, la déclaration:
DLLIMPEXP void foo();
s'étend à
__declspec(dllexport) void foo();
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 à
__declspec(dllimport) void foo();
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:
class DLLIMPEXP X
{
};
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:
int foo()
{
return 0;
}
Dans b.cpp, nous voulons appeler la fonction foo, donc nous ajoutons
void foo();
pour déclarer la fonction foo (), et l'appeler dans un autre corps de fonction, dites bar()
:
void bar()
{
foo();
}
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.
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:
template<class T>
struct X
{
void foo();
};
int main()
{
X<int> x;
x.foo();
}
//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}
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 .
référence indéfinie à une référence de point d'entrée "inhabituelle"WinMain@16
similaire main()
(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 .
WinMain
. Les programmes C ++ valides ont besoin d'un main
.
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 trouvant packagename\build\native\packagename.targets
et à l'intérieur de ce fichier, en copiant toutes les v110
sections. J'ai changé v110
pour v120
dans les champs de l' état que d' être très prudent de laisser les chemins de nom de fichier tout comme v110
. Cela a simplement permis à Visual Studio 2013 de se lier aux bibliothèques pour 2012, et dans ce cas, cela a fonctionné.
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ésJ'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 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 .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.
É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.
Si vous obtenez des erreurs de l'éditeur de liens sur les références non définies aux symboles qui impliquent des types dans l'espace de noms std :: __ cxx11 ou la balise [abi: cxx11], cela indique probablement que vous essayez de lier ensemble des fichiers objets qui ont été compilés avec des valeurs différentes pour _GLIBCXX_USE_CXX11_ABI macro. Cela se produit généralement lors de la liaison à une bibliothèque tierce qui a été compilée avec une ancienne version de GCC. Si la bibliothèque tierce ne peut pas être reconstruite avec le nouvel ABI, vous devrez recompiler votre code avec l'ancien ABI.
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.
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:
INPUT (libtbb.so.2)
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
cp libtbb.so.2 libtbb.so
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
libfoo
dépend libbar
, alors votre lien passe correctement libfoo
avant libbar
.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 ++
my_lib.c
#include "my_lib.h"
#include <stdio.h>
void hw(void)
{
puts("Hello World");
}
my_lib.h
#ifndef MY_LIB_H
#define MT_LIB_H
extern void hw(void);
#endif
eg1.c
#include <my_lib.h>
int main()
{
hw();
return 0;
}
Vous construisez votre bibliothèque statique:
$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o
Vous compilez votre programme:
$ gcc -I. -c -o eg1.o eg1.c
Vous essayez de le lier avec libmy_lib.a
et échouez:
$ gcc -o eg1 -L. -lmy_lib eg1.o
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
Le même résultat si vous compilez et liez en une seule étape, comme:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
libz
eg2.c
#include <zlib.h>
#include <stdio.h>
int main()
{
printf("%s\n",zlibVersion());
return 0;
}
Compilez votre programme:
$ gcc -c -o eg2.o eg2.c
Essayez de lier votre programme avec libz
et échouez:
$ gcc -o eg2 -lz eg2.o
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status
Même chose si vous compilez et liez en une seule fois:
$ gcc -o eg2 -I. -lz eg2.c
/tmp/ccxCiGn7.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status
Et une variante de l'exemple 2 impliquant pkg-config
:
$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
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:
$ gcc -o eg1 eg1.o -L. -lmy_lib
Succès:
$ ./eg1
Hello World
Lien exemple 2 correctement:
$ gcc -o eg2 eg2.o -lz
Succès:
$ ./eg2
1.2.8
Liez pkg-config
correctement la variante de l'exemple 2 :
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib)
$ ./eg2
1.2.8
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 statiquemy_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
, à savoir my_lib.o
, et il n'y a qu'une seule chose définie dans my_lib.o
, à savoir la fonction hw
.
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 pour hw
.
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 pour hw
, donc ses références à hw
sont résolues .
Lorsque vous essayez de lier le programme comme:
$ gcc -o eg1 -L. -lmy_lib eg1.o
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 vu eg1.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 dedans eg1.o
.
L'éditeur de liens n'ajoute donc pas my_lib.o
au programme et n'a plus aucune utilité pour libmy_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 de hw
; 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éelibz
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 de libz
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 fonction zlibVersion
, qui n'est mentionnée qu'une seule fois, dans eg2.c
. Si l'éditeur de liens ajoute cette référence à votre programme, puis trouve la définition exportée par libz
, la référence est résolue
Mais lorsque vous essayez de lier le programme comme:
gcc -o eg2 -lz eg2.o
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 dedans eg2.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 atteint eg2.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:
gcc -o eg2 $(pkg-config --libs zlib) eg2.o
devient:
gcc -o eg2 -lz eg2.o
qui est juste l'exemple 2 à nouveau.
Le lien:
gcc -o eg2 -lz eg2.o
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.
Si je fais juste:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
gcc doit sûrement d' eg1.c
abord compiler , puis lier le fichier objet résultant avec libmy_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:
$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o
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
/tmp/ccQk1tvs.o: In function `main'
au lieu de:
eg1.o: In function `main':
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.
Étant donné l'extrait de code d'un type de modèle avec un opérateur ami (ou une fonction);
template <typename T>
class Foo {
friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};
Le operator<<
est déclaré comme une fonction non modèle. Pour chaque type T
utilisé avec Foo
, il doit y avoir un modèle sans modèle operator<<
. Par exemple, s'il y a un type Foo<int>
déclaré, alors il doit y avoir une implémentation d'opérateur comme suit;
std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}
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;
// forward declare the Foo
template <typename>
class Foo;
// forward declare the operator <<
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&);
template <typename T>
class Foo {
friend std::ostream& operator<< <>(std::ostream& os, const Foo<T>& a);
// note the required <> ^^^^
// ...
};
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&)
{
// ... implement the operator
}
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 de Foo<int>
.
Les alternatives incluent;
Permettre à l'amitié de s'étendre à toutes les instanciations des modèles, comme suit;
template <typename T>
class Foo {
template <typename T1>
friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
// ...
};
Ou, l'implémentation de la operator<<
peut être effectuée en ligne dans la définition de classe;
template <typename T>
class Foo {
friend std::ostream& operator<<(std::ostream& os, const Foo& a)
{ /*...*/ }
// ...
};
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 ;
Un nom déclaré pour la première fois dans une déclaration d'amis dans la classe ou le modèle de classe X devient membre de l'espace de noms englobant le plus intérieur de X, mais n'est pas accessible pour la recherche (sauf la recherche dépendante de l'argument qui prend en compte X) à moins qu'une déclaration correspondante à la portée de l'espace de noms ne soit à condition de...
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
warning: friend declaration 'std::ostream& operator<<(...)' declares a non-template function [-Wnon-template-friend]
note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)
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:
// header1.h
typedef int Number;
void foo(Number);
// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically
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'utiliser header2.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.
J'ai deux projets: graphics.lib
et main.exe
. Les deux projets en dépendent common_math.h
. Supposons que la bibliothèque exporte la fonction suivante:
// graphics.lib
#include "common_math.h"
void draw(vec3 p) { ... } // vec3 comes from common_math.h
Et puis vous allez de l'avant et incluez la bibliothèque dans votre propre projet.
// main.exe
#include "other/common_math.h"
#include "graphics.h"
int main() {
draw(...);
}
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).
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!
UNICODE
Définitions incohérentesUne construction Windows UNICODE est construite avec TCHAR
etc. étant définie comme wchar_t
etc. Lorsqu'elle n'est pas construite avec UNICODE
défini comme construit avec TCHAR
défini comme char
etc. Ces définitions UNICODE
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ù il UNICODE
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 de TCHAR
; char
par rapport à wchar_t
.
L'erreur comprend généralement une fonction une valeur avec un type char
ou wchar_t
dérivé, ceux-ci peuvent également inclure std::basic_string<>
etc. Lorsque vous parcourez la fonction affectée dans le code, il y aura souvent une référence à TCHAR
ou std::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;
#define UNICODE
#define _UNICODE
Ou dans les paramètres du projet;
Propriétés du projet> Général> Paramètres par défaut du projet> Jeu de caractères
Ou sur la ligne de commande;
/DUNICODE /D_UNICODE
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".
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).
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 implicitement extern
(c'est-à-dire lorsque le static
mot-clé est manquant).
Exemple:
// file1.cpp
const int test = 5; // in C++ same as "static const int test = 5"
int test2 = 5;
// file2.cpp
extern const int test;
extern int test2;
void foo()
{
int x = test; // linker error in C++ , no error in C
int y = test2; // no problem
}
correct serait d'utiliser un fichier d'en-tête et de l'inclure dans file2.cpp et file1.cpp
extern const int test;
extern int test2;
Alternativement, on pourrait déclarer la const
variable dans file1.cpp avec expliciteextern
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 à".
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:
#if (defined _GLIBCXX_EXPERIMENTAL_FILESYSTEM) //is the included filesystem library experimental? (C++14 and newer: <experimental/filesystem>)
using path_t = std::experimental::filesystem::path;
#elif (defined _GLIBCXX_FILESYSTEM) //not experimental (C++17 and newer: <filesystem>)
using path_t = std::filesystem::path;
#endif
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.o
De cette façon, toute fonction contenue dans file.o et utilisée dans main.o qui nécessitait despath_t
erreurs de "référence non définie" car main.o faisait référence std::filesystem::path
mais file.o à std::experimental::filesystem::path
.
Pour résoudre ce problème, je devais simplement changer <experimental :: filesystem> dans file.h en <filesystem> .
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:
# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL
les symboles cachés / locaux sont représentés par nm
un type de symbole en minuscule, par exemple t
au lieu de `T pour la section de code:
nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL
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:
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
DLL_PUBLIC int my_public_function(){
...
}
Ce qui correspond à peu près à la version Windows / MSVC:
#ifdef BUILDING_DLL
#define DLL_PUBLIC __declspec(dllexport)
#else
#define DLL_PUBLIC __declspec(dllimport)
#endif
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 par nm
) 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:
>>> objdump -t XXXX.o | grep hidden
0000000000000000 g F .text 000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g F .text 000000000000000b .hidden HIDDEN_SYMBOL2
nm -CD
ou nm -gCD
pour afficher les symboles externes. Voir également Visibilité sur le wiki GCC.
Différentes architectures
Vous pouvez voir un message comme:
library machine type 'x64' conflicts with target machine type 'X86'
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 de lib64
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
/ dans lib64
lequel la bibliothèque est récupérée.