Qu'est-ce qu'une erreur de référence externe non définie / symbole non résolu et comment puis-je la corriger?

1493

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.

Luchian Grigore
la source
@LuchianGrigore 'n'hésitez pas à ajouter une réponse' J'ai préféré ajouter le lien pertinent (à mon humble avis) votre réponse principale, si vous le souhaitez.
πάντα ῥεῖ
19
Cette question est beaucoup trop générale pour admettre une réponse utile. Je ne peux pas croire le nombre de plus de 1000 personnes de réputation qui commentent cela, sans abattre la question. En attendant, je vois beaucoup de questions qui sont sensées et très utiles pour moi qui sont fermées.
Albert van der Horst
10
@AlbertvanderHorst "Cette question est beaucoup trop générale pour admettre une réponse utile" Les réponses ci-dessous ne sont-elles pas utiles?
LF
4
Elles peuvent être utiles, tout comme de nombreuses réponses à des questions signalées comme trop générales.
Albert van der Horst
1
J'aimerais voir un exemple reproductible minimal comme quelque chose que nous demandons à la plupart des nouveaux utilisateurs, honnêtement. Je ne veux rien dire, c'est juste - nous ne pouvons pas nous attendre à ce que les gens suivent les règles que nous ne nous imposons pas.
Danilo

Réponses:

850

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] .

  1. 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]
  2. 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]
  3. 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]
  4. 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]
  5. 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]
  6. Les jetons littéraux de chaîne adjacents sont concaténés.
  7. 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]
  8. Les unités de traduction traduites et les unités d'instanciation sont combinées comme suit: [SNIP]
  9. 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 adans 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 .libfichiers. 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 LNK2019pour 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:

Luchian Grigore
la source
16
Personnellement, je pense que les messages d'erreur de l'éditeur de liens MS sont tout aussi lisibles que les erreurs GCC. Ils ont également l'avantage d'inclure les noms mutilés et démêlés pour l'extérieur non résolu. Le fait d'avoir le nom modifié peut être utile lorsque vous devez consulter les bibliothèques ou les fichiers d'objets directement pour voir quel pourrait être le problème (par exemple, une incompatibilité de convention d'appel). De plus, je ne sais pas quelle version de MSVC a produit les erreurs ici, mais les versions plus récentes incluent le nom (à la fois déformé et démêlé) de la fonction faisant référence au symbole externe non résolu.
Michael Burr
5
David Drysdale a écrit un excellent article sur le fonctionnement des linkers: Guide du débutant pour les linkers . Étant donné le sujet de cette question, j'ai pensé qu'elle pourrait s'avérer utile.
Pressacco
@TankorSmash Utiliser gcc? MinGW pour être plus précis.
doug65536
1
@luchian ce serait bien si vous ajoutez le correct, corrigeant les erreurs ci-dessus
Phalgun
178

Membres de la classe:

Un virtualdestructeur 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- virtualmé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;
};

virtualMembres hors classe

Certains 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.

Une erreur courante consiste à oublier de qualifier le nom:

struct A
{
   void foo();
};

void foo() {}

int main()
{
   A a;
   a.foo();
}

La définition devrait être

void A::foo() {}

staticles 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 constmembre 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 constles membres de données.

Luchian Grigore
la source
Je pensais juste que vous voudrez peut-être souligner que faire les deux est possible, et le détecteur n'est pas en fait une exception. (ce n'est pas évident d'après votre formulation à première vue.)
Déduplicateur
112

É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.

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.somais 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 -lou -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 libfichier 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 libfichier doit être ajouté Linker -> General -> Additional Library Directories) Lorsque vous utilisez une bibliothèque tierce qui est fournie avec un libfichier, 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".

Luchian Grigore
la source
1
Ce serait bien si vous pouviez couvrir explicitement l'erreur courante de gcc main.clieu 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).
MM
101

Déclaré mais n'a pas défini de variable ou de fonction.

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

  • Fonction / variable déclarée dans un espace de noms, définie dans un autre.
  • Fonction / variable déclarée comme membre de classe, définie comme globale (ou vice versa).
  • Le type de retour de fonction, le nombre et les types de paramètres et la convention d'appel ne sont pas tous exactement d'accord.

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.

Luchian Grigore
la source
Dans VS, les fichiers cpp correspondant à ceux de l'en-tête #includesnon ajoutés au répertoire source entrent également dans la catégorie des définitions manquantes.
Laurie Stearn
86

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 Adépend de la bibliothèque B, alors libA DOIT apparaître avant libBdans 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!

Svalorzen
la source
Le fait curieux est que dans mon cas, j'avais un fichier objet qui dépend d'une bibliothèque partagée. J'ai dû modifier le Makefile et mettre la bibliothèque APRÈS l'objet avec gcc 4.8.4 sur Debian. Sur Centos 6.5 avec gcc 4.4, le Makefile fonctionnait sans problème.
Marco Sulla
74

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.

Kastaneda
la source
71

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 ++:

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"
}
Luchian Grigore
la source
5
Ou inversement, si vous développez une bibliothèque C, une bonne règle est de protéger le (s) fichier (s) d'en-tête en entourant toutes les déclarations exportées avec #ifdef __cplusplus [\n] extern"C" { [\n] #endifet #ifdef __cplusplus [\n] } [\n] #endif( [\n]étant un vrai retour chariot mais je ne peux pas écrire cela correctement en commentaire).
Bentoy13
Comme dans le commentaire ci-dessus, la section «Création d'en-têtes en langues mixtes» a aidé ici: oracle.com/technetwork/articles/servers-storage-dev/…
zanbri
M'a vraiment aidé! C'était mon cas lorsque j'ai cherché cette question
knightofhorizon
Cela peut également se produire si vous incluez votre fichier d' en- tête C ++ ordinaire par accident entouré de extern C : extern "C" { #include <myCppHeader.h> }.
ComFreek
68

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é.

sgryzko
la source
1
Correct - cela se produit lorsque les bibliothèques ont une dépendance cyclique.
Luchian Grigore
1
J'ai développé votre réponse et j'ai intégré la réponse principale. Merci.
Luchian Grigore
57

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:

#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif

La macro THIS_MODULEne 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
{
};
Luchian Grigore
la source
2
Pour être complète, cette réponse doit mentionner visibilityles .deffichiers de GCC et Windows , car ceux-ci influencent également le nom et la présence du symbole.
rubenvb
@rubenvb Je n'ai pas utilisé de .deffichiers depuis des lustres. N'hésitez pas à ajouter une réponse ou à modifier celle-ci.
Luchian Grigore
57

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:

  • Tous les fichiers objets générés lors de la compilation
  • Toutes les bibliothèques (.lib) spécifiées explicitement ou implicitement comme dépendances supplémentaires de cette application de construction.

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:

  1. La définition existe

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.

  1. La définition n'existe pas

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 Dependencyde:) 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.

Nima Soroush
la source
1
Merci pour l'explication systématique. Je pourrais comprendre les autres réponses ici après avoir lu celle-ci.
displayName
56

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 implfichier 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::foodans 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 .

Luchian Grigore
la source
1
De plus amples informations sur "les modèles doivent être définis dans l'en-tête" peuvent être trouvées dans stackoverflow.com/questions/495021
PlasmaHH
41

référence indéfinie à une référence de point d'entrée "inhabituelle"WinMain@16 similaire main() (en particulier pour).

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
2
Je ne peux pas m'empêcher de souligner cette question et le fait que cela est plus souvent causé par le fait de n'avoir aucune fonction principale que de ne pas en avoir WinMain. Les programmes C ++ valides ont besoin d'un main.
chris
36

De plus, si vous utilisez des bibliothèques tierces, assurez-vous d'avoir les bons binaires 32/64 bits

Dula
la source
34

Microsoft propose #pragmade référencer la bonne bibliothèque au moment du lien;

#pragma comment(lib, "libname.lib")

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.

Niall
la source
34

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 packagesdossier dans le répertoire de la solution) en trouvant packagename\build\native\packagename.targetset à l'intérieur de ce fichier, en copiant toutes les v110sections. J'ai changé v110pour v120dans 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é.

Malvineous
la source
1
Cela semble trop spécifique - peut-être qu'un nouveau fil serait un meilleur endroit pour cette réponse.
Luchian Grigore
3
@LuchianGrigore: Je voulais publier ici car cette question était exactement ce problème, mais elle était marquée comme un double de cette question, donc je ne pouvais pas y répondre. J'ai donc affiché ma réponse ici à la place.
Malvineous
Cette question a déjà une réponse acceptée. Il est marqué en double car la cause générale est répertoriée ci-dessus. Que se passerait-il si nous avions une réponse ici pour chaque problème avec une bibliothèque qui n'est pas incluse?
Luchian Grigore
6
@LuchianGrigore: Ce problème n'est pas spécifique à une bibliothèque, il affecte toutes les bibliothèques utilisant le système de gestion de packages de Visual Studio. Il se trouve que j'ai trouvé l'autre question parce que nous avons tous les deux eu des problèmes avec libpng. J'ai également eu le même problème (avec la même solution) pour libxml2, libiconv et glew. Cette question concerne un problème avec le système de gestion des packages de Visual Studio, et ma réponse explique la raison et fournit une solution de contournement. Quelqu'un vient de voir "externe non résolu" et a supposé qu'il s'agissait d'un problème de l'éditeur de liens standard alors qu'il s'agissait en fait d'un problème de gestion de package.
Malvineous
34

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?

  • Visual Studio recherche dans le fichier .vcxproj et commence à compiler chaque fichier portant l'extension .cpp. L'ordre de compilation n'est pas défini, vous ne devez donc pas supposer que le fichier main.cpp est d'abord compilé
  • Si les fichiers .cpp dépendent de fichiers .h supplémentaires afin de trouver des symboles qui peuvent ou non être définis dans le fichier .cpp
  • S'il existe un fichier .cpp dans lequel le compilateur n'a pas pu trouver un symbole, une erreur de temps du compilateur déclenche le message Symbole x introuvable
  • Pour chaque fichier avec l'extension .cpp est généré un fichier objet .o et Visual Studio écrit également la sortie dans un fichier nommé ProjectName.Cpp.Clean.txt qui contient tous les fichiers objet qui doivent être traités par l'éditeur de liens.

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

  • Analyser tous les fichiers objets et trouver la définition qui n'a été déclarée que dans les en-têtes (par exemple: le code d'une méthode d'une classe comme mentionné dans les réponses précédentes, ou événement l'initialisation d'une variable statique qui est membre à l'intérieur d'une classe)
  • Si un symbole est introuvable dans les fichiers objets, il est également recherché dans les bibliothèques supplémentaires.Pour ajouter une nouvelle bibliothèque à un projet Propriétés de configuration -> Répertoires VC ++ -> Répertoires de bibliothèque et ici vous avez spécifié un dossier supplémentaire pour rechercher les bibliothèques et les propriétés de configuration -> Éditeur de liens -> Entrée pour spécifier le nom de la bibliothèque. -Si l' éditeur de liens n'a pas pu trouver le symbole que vous écrivez dans un .cpp, il déclenche une erreur de temps de l'éditeur de liens qui peut ressembler à error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

Observation

  1. Une fois que l'éditeur de liens a trouvé un symbole, il ne le recherche pas dans d'autres bibliothèques
  2. L'ordre de liaison des bibliothèques est important .
  3. Si Linker trouve un symbole externe dans une bibliothèque statique, il inclut le symbole dans la sortie du projet.Toutefois, si la bibliothèque est partagée (dynamique), il n'inclut pas le code (symboles) dans la sortie, mais les blocages au moment de l' exécution peuvent se produire

Comment résoudre ce type d'erreur

Erreur de temps du compilateur:

  • Assurez-vous d'écrire correctement votre syntaxe de projet c ++.

Erreur de temps de l'éditeur de liens

  • Définissez tous vos symboles que vous déclarez dans vos fichiers d'en-tête
  • Utilisez #pragma oncepour permettre au compilateur de ne pas inclure un en-tête s'il était déjà inclus dans le .cpp actuel qui sont compilés
  • Assurez-vous que votre bibliothèque externe ne contient pas de symboles pouvant entrer en conflit avec d'autres symboles que vous avez définis dans vos fichiers d'en-tête
  • Lorsque vous utilisez le modèle pour vous assurer d'inclure la définition de chaque fonction de modèle dans le fichier d'en-tête pour permettre au compilateur de générer le code approprié pour toutes les instanciations.
eFarzad
la source
N'est-ce pas votre réponse est spécifique pour Visual Studio? La question ne spécifie aucun outil IDE / compilateur, ce qui rend votre réponse inutile pour la partie non visuelle-studio.
Victor Polevoy
Tu as raison . Mais chaque processus de compilation / liaison IDE se fait légèrement différemment. Mais les fichiers sont traités exactement de la même manière (même g ++ fait la même chose quand il analyse les drapeaux ..)
Le problème n'est pas réellement lié à l'IDE mais à une réponse pour relier les problèmes. Les problèmes de liaison ne sont pas liés à l'EDI mais au compilateur et au processus de génération.
Victor Polevoy
Oui, mais le processus de génération / liaison se fait dans g ++ / Visual Studio (compilateur fourni par Microsoft pour VS) / Eclipse / Net Beans de la même manière
29

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:

  • Nettoyer le projet (certains IDE ont une option pour ce faire, vous pouvez également le faire manuellement en supprimant les fichiers objets)
  • Essayez de démarrer un nouveau projet en copiant tout le code source de celui d'origine.
developerbmw
la source
5
Croire que vos outils sont cassés va probablement vous éloigner de la véritable cause. Il est tellement plus probable que vous ayez fait une erreur qu'un compilateur a causé votre problème. Le nettoyage de votre solution ou la recréation de votre configuration de build peut corriger des erreurs de build, mais cela ne signifie pas qu'il y a un bogue dans le compilateur. Le lien "s'est avéré qu'il s'agissait d'un bogue" n'est pas confirmé par Microsoft et n'est pas reproductible.
JDiMatteo
4
@JDiMatteo Il y a 21 réponses à cette question et donc un nombre important de réponses ne seront pas une solution "probable". Si vous rejetez toutes les réponses qui sont en dessous de votre seuil de probabilité, cette page devient en fait inutile car la plupart des cas courants sont facilement repérables de toute façon.
developerbmw
27

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;

  • Invocation de lien (ligne de commande),
  • Données sur les bibliothèques incluses dans l'étape de liaison,
  • L'emplacement des bibliothèques,
  • Chemins de recherche utilisés.

Pour gcc et clang; vous ajouteriez généralement -v -Wl,--verboseou -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.

Niall
la source
26

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.

kiriloff
la source
25

É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.

Plankalkül
la source
20

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 -ltbbfaire/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2

JDiMatteo
la source
20

Votre liaison consomme des bibliothèques avant les fichiers objets qui s'y réfèrent

  • Vous essayez de compiler et de lier votre programme avec la chaîne d'outils GCC.
  • Votre lien spécifie toutes les bibliothèques et tous les chemins de recherche de bibliothèque nécessaires
  • Si cela libfoodépend libbar, alors votre lien passe correctement libfooavant libbar.
  • Votre liaison échoue avec undefined reference to quelque chose d' erreurs.
  • Mais tous les indéfinis quelque chose s sont déclarés dans les fichiers d' en- tête que vous avez #included 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

#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.aet é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

Un exemple minimal impliquant une bibliothèque système partagée, la bibliothèque de compression 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 libzet é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'

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:

$ 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-configcorrectement la variante de l'exemple 2 :

$ gcc -o eg2 eg2.o $(pkg-config --libs zlib) 
$ ./eg2
1.2.8

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 statiquemy_lib.a

Une bibliothèque statique est une archive indexée de fichiers objets. Lorsque l'éditeur de liens trouve -lmy_libdans 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.osi 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.ola bibliothèque et l'ajoute à votre programme. Ensuite, votre programme contient une définition pour hw, donc ses références à hwsont 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.oau programme et n'a plus aucune utilité pour libmy_lib.a.

Ensuite, il le trouve eg1.oet 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 mainfonction 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++, gfortranetc.) 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 -lzdans 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 libzet 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 libzdans 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 libzdans 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-configvariation 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.

Je peux reproduire le problème dans l'exemple 1, mais pas dans l'exemple 2

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 -lfooleur liaison va se résoudre vers /some/where/libfoo.aou 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 -o eg1 -I. -L. -lmy_lib eg1.c

gcc doit sûrement d' eg1.cabord 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, gccvous 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':

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.

Mike Kinghan
la source
18

Modèles d'amitié ...

É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 Tutilisé 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 Footype, 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)

Niall
la source
16

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:

// 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.het que vous finissez par l'utiliser header2.hdans 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.libet 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, vec3est 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. Notez le nom étrange mutilé donné dans l'erreur de l'éditeur de liens. (par exemple, draw @ graphics @ XYZ).
  2. Videz les symboles exportés de la bibliothèque dans un fichier texte.
  3. Recherchez le symbole d'intérêt exporté et notez que le nom modifié est différent.
  4. Faites attention à la raison pour laquelle les noms mutilés sont différents. Vous pourriez voir que les types de paramètres sont différents, même s'ils se ressemblent dans le code source.
  5. Raison pour laquelle ils sont différents. Dans l'exemple donné ci-dessus, ils sont différents en raison de différents fichiers d'inclusion.

[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!

fafaro
la source
15

UNICODEDéfinitions incohérentes

Une construction Windows UNICODE est construite avec TCHARetc. étant définie comme wchar_tetc. Lorsqu'elle n'est pas construite avec UNICODEdéfini comme construit avec TCHARdéfini comme charetc. Ces définitions UNICODEet _UNICODEaffectent tous les types de chaînes " T" ; LPTSTR, LPCTSTREt leurs élans.

Construire une bibliothèque avec UNICODEdéfini et tenter de la lier dans un projet où il UNICODEn'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; charpar rapport à wchar_t.

L'erreur comprend généralement une fonction une valeur avec un type charou wchar_tdé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 à TCHARou 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).

  1. Cela peut être fait avec l'un ou l'autre;

    #define UNICODE
    #define _UNICODE
  2. 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

  3. 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".

Niall
la source
14

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).

Niall
la source
10

"Extern" manquant dans constles déclarations / définitions de variables (C ++ uniquement)

Pour les personnes venant de C, il pourrait être surprenant qu'en C ++, les constvariables 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 staticmot-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 constvariable dans file1.cpp avec expliciteextern

Andreas H.
la source
8

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:

#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:

  • file.h # include's < experimental :: filesystem > et contient le code ci-dessus
  • file.cpp , l'implémentation de file.h, # include's " file.h "
  • main.cpp # include < système de fichiers > et " file.h "

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::pathmais 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> .

Stypox
la source
5

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:

# -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 nmun type de symbole en minuscule, par exemple tau lieu de `T pour la section de code:

nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL

Vous pouvez également utiliser nmavec l'option -Cpour démêler les noms (si C ++ a été utilisé).

Semblable à Windows-dll, on marquerait les fonctions publiques avec un define, par exemple DLL_PUBLICdé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=hiddenles 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
ead
la source
Vous devez utiliser nm -CDou nm -gCDpour afficher les symboles externes. Voir également Visibilité sur le wiki GCC.
jww
2

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 libau lieu de lib64par 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 lib64lequel la bibliothèque est récupérée.

Matthieu Brucher
la source