Question Qu'est-ce qu'une référence non définie / une erreur de symbole externe non résolue et comment la réparer?


Quelles sont les références non définies / les erreurs de symboles externes non résolues? Quelles sont les causes communes et comment les réparer / les prévenir?

N'hésitez pas à modifier / ajouter les vôtres.


1194
2017-09-24 22:27


origine


Réponses:


Compiler 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   (introduction de caractères de nouvelle ligne pour les indicateurs de fin de ligne)   nécessaire. [COUPER]
  2. Chaque occurrence d'un caractère barre oblique inverse (\) immédiatement suivie d'un caractère de nouvelle ligne est supprimée, ce qui permet d'associer des lignes de source physiques à   former des lignes de source logiques. [COUPER]
  3. Le fichier source est décomposé en jetons de prétraitement (2.5) et en séquences de caractères en espace blanc (y compris les commentaires). [COUPER]
  4. Les directives de prétraitement sont exécutées, les invocations de macro sont étendues et les expressions d'opérateur _Pragma unaire sont exécutées. [COUPER]
  5. Chaque membre de 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 brute, est converti en   le 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). le   les jetons résultants sont analysés syntaxiquement et sémantiquement et   traduit comme une unité de traduction. [COUPER]
  8. Les unités de traduction traduites et les unités d'instanciation sont combinées comme suit: [COUPER]
  9. Toutes les références d'entité externe 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 le   traduction actuelle. Toutes ces sorties de traducteurs sont collectées dans un   image de programme qui contient des informations nécessaires à l'exécution dans son   environnement d'exécution. (emphase mienne)

[note de bas de page] Les implémentations doivent se comporter comme si ces phases séparées se produisent, 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 ou des bibliothèques d'objets et que vous voulez maintenant les faire fonctionner ensemble.

Dites que vous avez défini le symbole a dans a.cpp. À présent, b.cpp  déclaré ce symbole et l'a utilisé. Avant de créer un lien, 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 responsable 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 .lib des dossiers. Ceux-ci contiennent une table de symboles exportés et une table de symboles importés. Les symboles importés sont résolus par rapport aux bibliothèques auxquelles vous liez, et les symboles exportés sont fournis pour les bibliothèques qui l'utilisent .lib (si seulement).

Des mécanismes similaires existent pour d'autres compilateurs / plateformes.

Les 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;
}

va générer 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 des 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 communes incluent:


693
2017-09-24 22:27



Membres du cours

Un pur virtual destructor a besoin d'une implémentation.

Déclarer un destructeur pur nécessite toujours de le définir (contrairement à une fonction normale):

struct X
{
    virtual ~X() = 0;
};
struct Y : X
{
    ~Y() {}
};
int main()
{
    Y y;
}
//X::~X(){} //uncomment this line for successful definition

Cela se produit parce que les destructeurs de classe de base sont appelés lorsque l'objet est détruit implicitement, donc une définition est requise.

virtual les méthodes doivent être implémentées ou définies comme étant pures.

Ceci est similaire à nonvirtual 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() aussi pur:

struct X
{
    virtual void foo() = 0;
};

Non-virtual membres de la classe

Certains membres doivent être définis même s'ils ne sont pas utilisés explicitement:

struct A
{ 
    ~A();
};

Ce qui suit donnerait 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 comme inline pour empêcher une définition multiple.

Toutes les méthodes de membre utilisées doivent être définies si elles sont utilisées.

Une erreur courante est d'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() {}

static les membres de données doivent être définis en dehors de la classe dans un unité de traduction unique:

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égrale ou énumération dans la définition de classe; cependant, l'utilisation de ce membre nécessitera toujours une définition d'étendue d'espace de nommage comme décrit ci-dessus. C ++ 11 permet l'initialisation à l'intérieur de la classe pour tous static constmembres de données.


148
2017-09-24 23:38



Échec de la liaison avec les bibliothèques / fichiers objets appropriés ou 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 faire un lien avec ces fichiers objets.

En dessous de gcc vous devez spécifier tous les fichiers objet à lier ensemble dans la ligne de commande ou compiler les fichiers d'implémentation ensemble.

g++ -o test objectFile1.o objectFile2.o -lLibraryName

le libraryName voici juste le nom nu de la bibliothèque, sans ajouts spécifiques à la plate-forme. Ainsi, par exemple sur les fichiers de la bibliothèque Linux sont généralement appelés libfoo.so mais tu écrirais seulement -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 en utilisant -L‹directory›. Assurez-vous de ne pas écrire un espace après -l ou -L.

Pour XCode: Ajouter les chemins de recherche d'en-tête utilisateur -> ajouter le chemin de recherche de bibliothèque -> faire glisser et déposer la référence de bibliothèque réelle dans le dossier du projet.

En dessous de MSVS, les fichiers ajoutés à un projet ont automatiquement leurs fichiers objet liés et un lib le fichier serait généré (usage courant). Pour utiliser les symboles dans un projet distinct, vous besoin d'inclure le lib fichiers dans les paramètres du projet. Ceci est fait dans la section Linker des propriétés du projet, dans Input -> Additional Dependencies. (le chemin vers le lib le fichier devrait être ajouté dans Linker -> General -> Additional Library Directories) Lorsque vous utilisez une bibliothèque tierce fournie avec un lib fichier, l'échec de le faire 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 ajouter le 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 indiquant 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 l'information dans une zone au bas de chaque fonction dans une section appelée "Bibliothèque".


97
2017-09-24 23:37



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, 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 discordances comprennent

  • Fonction / variable déclarée dans un espace de nom, définie dans un autre.
  • Fonction / variable déclarée en tant que membre de classe, définie comme globale (ou vice versa).
  • Le type de retour de fonction, le nombre et les types de paramètre, 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 déclarée mais jamais définie. Comparez-le étroitement à la définition que vous avez fournie. Assurez-vous que chaque détail correspond.


91
2017-09-24 23:38



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 est important 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, puis libA  DOIT apparaître avant libB dans les drapeaux 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, l'ordre EST-CE QUE matière!


73
2017-07-10 11:46



qu'est-ce qu'une "référence non définie / symbole externe non résolu"

Je vais essayer d'expliquer ce qu'est une «référence non définie / symbole externe 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 objet

$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o

Après la phase assembleur, 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, parce qu'elles n'ont pas d'importance

Donc, 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 exporte rien et nous n'avons pas vu ses symboles

Lier nos fichiers objets

$ g++ src1.o src2.o -o prog

et lancez-le

$ ./prog
123

Linker voit les symboles exportés et les lie. 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), parce que nous ne construisons que le fichier objet, la liaison n'est pas encore terminé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 est dû au fait que notre nom_var_local est statique, c'est-à-dire qu'il n'est pas visible pour les autres modules. Maintenant plus profondément. Obtenir 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 linker ne l'a pas trouvé. Mais nous sommes des hackers :) et nous pouvons le réparer. Ouvrez src1.s dans votre éditeur de texte et modifiez

.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 lire sortie (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 lancez-le

$ ./prog 
123456789

D'accord, nous le piratons :)

Par conséquent, une "référence non définie / erreur de symbole externe non résolue" se produit lorsque l'éditeur de liens ne trouve pas de symboles globaux dans les fichiers objets.


63
2017-09-24 23:39



Les symboles ont été définis dans un programme C et utilisés dans le code C ++.

La fonction (ou variable) void foo() a été défini dans un programme C et vous essayez de l'utiliser dans un programme C ++:

void foo();
int main()
{
    foo();
}

Le lieur C ++ s'attend à ce que les noms soient tronqué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) void foo() a été défini en C ++ mais avec une liaison C:

extern "C" void foo();

et vous essayez de l'utiliser dans un programme C ++ avec un lien C ++.

Si une bibliothèque entière est incluse dans un fichier d'en-tête (et a été compilée en tant que code C); l'inclusion devra être comme suit;

extern "C" {
    #include "cheader.h"
}

60
2017-12-03 18:11



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 juste en recompilant le fichier incriminé. Quand j'ai reconstruit, l'erreur est partie.

Cela se produit généralement lorsque deux bibliothèques (ou plus) ont une dépendance cyclique. Bibliothèque A tente d'utiliser des symboles dans B.lib et la bibliothèque B tente d'utiliser des symboles de A.lib. Aucun n'existe pour commencer avec. Lorsque vous tentez de compiler A, l'étape de lien échouera car il ne peut pas trouver B.lib. A.lib sera généré, mais pas dll. Vous compilez ensuite B, qui va réussir et générer B.lib. Recompiler A fonctionnera maintenant parce que B.lib est maintenant trouvé.


58
2017-09-24 23:39



Importation / exportation incorrecte de méthodes / classes sur modules / dll (spécifique au compilateur).

MSVS vous oblige à spécifier quels symboles exporter et importer en utilisant __declspec(dllexport) et __declspec(dllimport).

Cette double fonctionnalité est généralement obtenue par 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éfini que dans le module qui exporte la fonction. De cette façon, la déclaration:

DLLIMPEXP void foo();

se développe pour

__declspec(dllexport) void foo();

et dit au compilateur d'exporter la fonction, car le module courant contient sa définition. Lorsque vous incluez la déclaration dans un module différent, elle s'étendra jusqu'à

__declspec(dllimport) void foo();

et dit au compilateur que la définition est dans l'une des bibliothèques avec lesquelles vous avez lié (voir aussi 1)).

Vous pouvez importer des classes d'import / export similaires:

class DLLIMPEXP X
{
};

50
2017-09-24 23:38