web-dev-qa-db-fra.com

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

Qu'est-ce qu'une erreur de symbole externe non définie/non définie? Quelles sont les causes courantes et comment les résoudre/les prévenir?

N'hésitez pas à éditer/ajouter le vôtre.

1309
Luchian Grigore

La compilation d’un programme C++ s’effectue en plusieurs étapes, comme spécifié par 2.2 _ ​​ (crédit de 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, sur le jeu de caractères source de base (introduisant des caractères de nouvelle ligne pour les indicateurs de fin de ligne) si nécessaire. [SNIP]
  2. Chaque occurrence d'une barre oblique inversée (\) immédiatement suivie d'un caractère de nouvelle ligne est supprimée, ce qui permet de lier les lignes source physiques à former des lignes de source logiques. [SNIP]
  3. Le fichier source est décomposé en jetons de pré-traitement (2.5) et en séquences de caractères d’espace blanc (y compris les commentaires). [SNIP]
  4. Les directives de prétraitement sont exécutées, les invocations de macros sont développées et les expressions d'opérateur unaire _Pragma sont exécutées. [SNIP]
  5. Chaque membre du jeu de caractères source dans un littéral de caractère ou 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 le membre correspondant du jeu de caractères d'exécution; [SNIP]
  6. Les jetons littéraux de chaîne adjacents sont concaténés.
  7. Les espaces blancs séparant les jetons ne sont plus significatifs. Chaque jeton de prétraitement est converti en un jeton. (2,7). Le les jetons résultants sont analysés syntaxiquement et sémantiquement et traduit comme une unité de traduction. [SNIP]
  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é externe sont résolues. Les composants de la bibliothèque sont liés pour satisfaire les références externes aux entités non définies dans le traduction en cours. Toutes ces sorties de traducteur sont rassemblées dans un image de programme qui contient les informations nécessaires à l'exécution dans son fichier environnement d'exécution. (c'est moi qui souligne)

[note de bas de page] Les implémentations doivent se comporter comme si ces phases distinctes avaient lieu, même si, en pratique, différentes phases pourraient être combinées.

Les erreurs spécifiées se produisent au cours de cette dernière étape de compilation, plus communément appelée liaison. En gros, cela signifie que vous avez compilé un grand nombre de fichiers d’implémentation dans des fichiers objets ou des bibliothèques et que vous souhaitez maintenant les faire fonctionner ensemble.

Disons que vous avez défini le symbole a dans a.cpp. Maintenant, b.cppa déclaré ce symbole et l'a utilisé. Avant la liaison, il suppose simplement que ce symbole a été défini quelque part, mais il ne se soucie pas encore de savoir où. La phase de liaison est chargée de trouver le symbole et de le lier correctement à b.cpp (ainsi à l'objet ou à la bibliothèque qui l'utilise).

Si vous utilisez Microsoft Visual Studio, vous verrez que les projets génèrent des fichiers .lib. 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 avec lesquelles vous créez un lien et les symboles exportés sont fournis aux bibliothèques qui utilisent ce .lib (le cas échéant).

Des mécanismes similaires existent pour d'autres compilateurs/plates-formes.

Les messages d'erreur courants sont error LNK2001, error LNK1120, error LNK2019 pour Microsoft Visual Studio et undefined reference tosymboleName 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 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:

761
Luchian Grigore

Membres de classe:

Un destructeur pur virtual nécessite une implémentation.

Pour déclarer un destructeur pur, vous devez tout de même 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 car les destructeurs de classe de base sont appelés lorsque l'objet est détruit implicitement; une définition est donc requise. 

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

Ceci est similaire aux méthodes non -virtual sans définition, avec le raisonnement ajouté 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() comme pur:

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

Membres de la classe non -virtual

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

struct A
{ 
    ~A();
};

Ce qui suit produirait 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 la 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 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() {}

static Les membres de données doivent être définis en dehors de la classe dans une unité de traduction single:

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 membre de données staticconst de type intégral ou énumération dans la définition de classe; Cependant, odr-use de ce membre nécessitera toujours une définition de la portée de l'espace de noms comme décrit ci-dessus. C++ 11 permet l'initialisation à l'intérieur de la classe pour tous les membres de données static const.

163
Luchian Grigore

É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 contenant les définitions des symboles définis dans cette unité de traduction. Pour utiliser ces symboles, vous devez créer un lien avec ces fichiers d'objet.

Sous gcc , vous spécifiez tous les fichiers d’objets devant être liés ensemble dans la ligne de commande ou compilez les fichiers d’implémentation.

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

La libraryName ici est simplement le nom nu de la bibliothèque, sans ajouts spécifiques à la plate-forme. Donc, par exemple Sur Linux, les fichiers de bibliothèque sont généralement appelés libfoo.so mais vous ne feriez qu’écrire -lfoo. Sous Windows, ce même fichier peut s'appeler 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 de -L‹directory›. Assurez-vous de ne pas écrire d'espace après -l ou -L.

Pour XCode : Ajoutez les chemins de recherche de l'en-tête de l'utilisateur -> ajoutez le chemin de recherche de la bibliothèque -> faites glisser la référence de bibliothèque actuelle dans le dossier du projet.

SousMSVS, les fichiers ajoutés à un projet ont automatiquement leurs fichiers objets liés et un fichier lib sera généré (en usage courant). Pour utiliser les symboles dans un projet séparé, vous devez inclure les fichiers lib dans les paramètres du projet. Ceci est fait dans la section lieur des propriétés du projet, dans Input -> Additional Dependencies. (le chemin d'accès au fichier lib devrait être ajouté dans Linker -> General -> Additional Library Directories) Lors de l'utilisation d'une bibliothèque tierce fournie avec un fichier lib, son échec est généralement le résultat de 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. DansMSVS, l'ajout du fichier au projet le compilera automatiquement (bien que les fichiers puissent être manuellement exclus de la construction).

Dans la programmation Windows, le signe indicateur que vous n'avez pas lié la 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 indiquez la bibliothèque à utiliser. Par exemple, MSDN place les informations dans une zone située au bas de chaque fonction dans une section appelée "Bibliothèque".

102
Luchian Grigore

Déclaré mais ne définit pas une variable ou une 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)

Parmi les autres types d’inadéquations possibles:

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

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.

94
Luchian Grigore

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 bibliothèque A dépend de la bibliothèque B, alors libADOIT/ 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, la commandeFAITimporte!

77
Svalorzen

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

Je vais essayer d'expliquer ce qu'est un "symbole externe de référence non défini/externe".

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

Faire des fichiers d'objet

$ 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 contenant 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 ne comptent pas

Nous voyons donc 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 pas vu ses symboles

Lier nos fichiers d'objet

$ 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 qu'un fichier objet, la liaison n'est pas encore terminée . Essayez de créer un lien.

$ 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

C’est arrivé 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. Obtenir le résultat 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 nom_var_local, 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 nom_var_local et lui avons attribué la valeur 456789 . Essayez de créer un fichier objet à partir de celui-ci.

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

ok, voir readelf 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 l'exécuter

$ ./prog 
123456789

ok, on pirate ça :)

En conséquence, une "erreur de symbole externe non définie/référence non définie" se produit lorsque l'éditeur de liens ne peut pas trouver de symboles globaux dans les fichiers d'objet.

68
Kastaneda

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é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 mutilés. Vous devez donc déclarer la fonction comme:

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é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 sous forme de code C); l'inclusion devra être comme suit;

extern "C" {
    #include "cheader.h"
}
64
Luchian Grigore

Si tout échoue, recompilez.

J'ai récemment pu éliminer une erreur externe non résolue dans Visual Studio 2012 en recompilant simplement 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. La bibliothèque A tente d'utiliser des symboles dans B.lib et la bibliothèque B utilise des symboles de A.lib. Ni existent pour commencer avec. Lorsque vous essayez de compiler A, l'étape de lien échouera car elle ne pourra 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. La recompilation de A fonctionnera maintenant car B.lib est maintenant trouvé.

61
sgryzko

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

MSVS requiert que vous spécifiiez les symboles à exporter et à importer à l'aide de __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é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. Lors de l’inclusion de la déclaration dans un module différent, il serait étendu à 

__declspec(dllimport) void foo();

et indique au compilateur que la définition se trouve dans l'une des bibliothèques auxquelles vous êtes lié (voir également 1)

Vous pouvez utiliser des classes d'importation/exportation similaires:

class DLLIMPEXP X
{
};
52
Luchian Grigore

C’est l’un des messages d’erreur les plus déroutants que tous les programmeurs de VC++ ont vu à maintes reprises. Soyons clairs d’abord.

A. Qu'est-ce qu'un symbole? .__ En 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, à l'exception des noms et des signes appartenant 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 la traduction en cours. unité. (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 élément d'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 tels que extern, __declspec (dllimport) et ainsi de suite.

C. Qu'est-ce que «résoudre»? Résoudre est un terme de temps de liaison. Dans link-time, l'éditeur de liens tente de trouver la définition externe de chaque symbole des fichiers objets qui ne peut pas trouver sa définition en interne. La portée de ce processus de recherche, y compris:

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

Ce processus de recherche s'appelle résoudre.

D. Enfin, pourquoi 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 à l'éditeur de liens qui n'a pas réussi à trouver 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, nous ajoutons donc

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 construisez ce code, vous obtenez une erreur LNK2019 indiquant que foo est un symbole non résolu. Dans ce cas, nous savons que foo () a sa définition dans a.cpp, mais est différente de celle que nous appelons (valeur de retour différente). C'est le cas que 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 ne soit pas ajoutée à la liste de dépendances supplémentaire (définie à partir de: Project | Properties | Configuration Properties | Linker | Input | Additional Dependency) du paramètre de votre projet. Maintenant, l'éditeur de liens signalera un LNK2019 puisque la définition n'existe pas dans la portée de la recherche actuelle.

50
Nima Soroush

Implémentations de modèles non visibles.

Les définitions des modèles non spécifiés doivent être visibles de 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à un fichier d'implémentation. Si vous devez séparer l'implémentation, la solution habituelle consiste à créer un fichier impl que vous incluez à la fin de l'en-tête et 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 dans un emplacement visible pour 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 ne doit pas nécessairement être visible, mais la spécialisation doit être préalablement déclarée.

Pour plus d'explications et une autre solution possible (instanciation explicite), voir cette question et cette réponse .

50
Luchian Grigore

référence indéfinie à WinMain@16 ou similaire 'inhabituel'main() référence de point d'entrée (en particulier pour visual-studio ).

Vous avez peut-être manqué de choisir le bon type de projet avec votre IDE actuel. Le IDE peut vouloir se lier, par exemple. Les applications Windows projettent une telle fonction de point d’entrée (comme spécifié dans la référence manquante ci-dessus), au lieu de la signature int main(int argc, char** argv); couramment utilisée.

Si votre IDE prend en charge projets de console simple, vous souhaiterez peut-être choisir ce type de projet au lieu d'un projet d'application Windows.


Voici case1 et case2 traités plus en détail à partir d'un problème monde réel.

37

De même, si vous utilisez des bibliothèques tierces, assurez-vous de disposer des fichiers binaires 32/64 bits appropriés.

34
Dula

Microsoft propose un #pragma pour référencer la bibliothèque correcte au moment du lien.

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

Outre le 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.

33
Niall

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 avec Visual Studio 2013. Le problème est que le fichier de package contenait uniquement des bibliothèques pour Visual Studio 2010 et 2012.

La solution correcte est d'espérer que le développeur publie un package mis à jour, puis une mise à niveau, mais cela a fonctionné pour moi en piratant un paramètre supplémentaire pour VS2013, en pointant vers les fichiers de la bibliothèque VS2012.

J'ai modifié le package (dans le dossier packages du répertoire de la solution) en recherchant packagename\build\native\packagename.targets et à l'intérieur de ce fichier, en copiant toutes les sections v110. J'ai changé le v110 en v120 dans les champs de condition uniquement en prenant soin de laisser tous les chemins de fichiers sous le nom v110. Cela a simplement permis à Visual Studio 2013 de créer un lien vers les bibliothèques de 2012 et, dans ce cas, cela a fonctionné.

31
Malvineous

Un bug dans le compilateur/IDE

J'ai récemment eu ce problème, et il s'est avéré que c'était un bogue dans Visual Studio Express 2013 . J'ai dû supprimer un fichier source du projet et l'ajouter de nouveau pour surmonter le bogue.

Étapes à suivre si vous pensez que cela pourrait être un bogue du compilateur/IDE:

  • Nettoyer le projet (certains IDE ont une option pour le faire, vous pouvez aussi Le faire manuellement en supprimant les fichiers objets)
  • Essayez de démarrer un nouveau projet, copiez tout le code source à partir de l’original.
26
developerbmw

Utilisez l'éditeur de liens pour aider à diagnostiquer l'erreur

La plupart des linkers modernes incluent une option détaillée qui s'imprime à des degrés divers;

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

Pour gcc et clang; vous ajouterez 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 du lien.

23
Niall

Le fichier .lib lié est associé à un fichier .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 à TestProject. Toutefois, ce fichier lib a été généré lors de la génération de la balise DLL du projet MyProject. De plus, je ne contenais pas le code source de toutes les méthodes de MyProject, mais uniquement un accès aux points d'entrée de la DLL. 

Pour résoudre le problème, j’ai construit MyProject en tant que LIB et ai lié TestProject à ce fichier .lib (j’ai copié le fichier .lib généré dans le dossier TestProject). Je peux alors 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. 

23
octoback

Puisque les gens semblent être dirigés vers cette question en ce qui concerne les erreurs de lieur, je vais ajouter ceci ici.

Une des raisons possibles des erreurs de l’éditeur de liens avec GCC 5.2.0 est qu’un nouvel ABI de la bibliothèque libstdc ++ est maintenant choisi par défaut.

Si vous obtenez des erreurs de l'éditeur de liens concernant des références non définies à des symboles impliquant des types dans l'espace de noms std :: __ cxx11 ou la balise [abi: cxx11], cela signifie probablement que vous essayez de lier des fichiers objet compilés avec des valeurs différentes pour _GLIBCXX_USE_CXX11_ABI macro. Cela se produit généralement lorsque vous vous connectez à une bibliothèque tierce compilée avec une version plus ancienne 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 soudain des erreurs dans l’éditeur de liens lorsque vous passez à un GCC après la version 5.1.0, cela serait une chose à vérifier.

18
Plankalkül

Un wrapper autour de GNU ld qui ne supporte pas les scripts de l'éditeur de liens

Certains fichiers .so sont en réalité GNU ld scripts de l'éditeur de liens , par exemple. libtbb.so fichier est un fichier texte ASCII avec le contenu suivant:

INPUT (libtbb.so.2)

Certaines versions plus complexes peuvent ne pas supporter cela. Par exemple, si vous incluez -v dans les options du compilateur, vous pouvez voir que les fichiers de commande de script mainwin gcc mwdip discards de l’éditeur de liens dans la liste de sortie détaillée des lib fichier de commande d'entrée de script de l'éditeur de liens avec une copie du fichier à la place (ou un lien symbolique), p.ex.

cp libtbb.so.2 libtbb.so

Vous pouvez également remplacer l’argument -l par le chemin complet du fichier .so, par exemple. au lieu de -ltbb do /home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2

17
JDiMatteo

Incohérences UNICODE définitions

Une version Windows UNICODE est générée avec TCHAR, étant définie en tant que wchar_t, etc. Si vous ne construisez pas avec UNICODE, définie en tant que construction avec TCHAR définie en tant que char, etc. Ces définitions UNICODE et _UNICODE affectent tous les types de chaîne "T" ; LPTSTR, LPCTSTR et leur wapiti.

Construire une bibliothèque avec UNICODE défini et tenter de la lier dans un projet où UNICODE n'est pas défini entraînera des erreurs de l'éditeur de liens car il y aura une discordance dans la définition de TCHAR; char vs wchar_t.

L'erreur comprend généralement une fonction avec une valeur avec un type dérivé char ou wchar_t, 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. C'est un signe indicateur que le code était initialement destiné à la fois à UNICODE et à Multi. -Byte Caractère (ou "étroit") construire.

Pour corriger cela, construisez 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 soit;

    #define UNICODE
    #define _UNICODE
    
  2. Ou dans les paramètres du projet;

    Propriétés du projet> Général> Valeurs 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 cohérent entre les versions "Release" et "Debug".

13
Niall

Quand 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) se désynchronisent. Laisse-moi expliquer. 

Comment fonctionnent les linkers? L'éditeur de liens associe une déclaration de fonction (déclarée dans l'en-tête) à 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 de toujours obtenir une erreur de l'éditeur de liens même si la déclaration et la définition semblent correspondre? Oui! Ils peuvent sembler identiques dans le code source, mais cela dépend vraiment de ce que voit le compilateur. En gros, vous pourriez vous retrouver dans 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 que même si les deux déclarations de fonction ont la même apparence dans le code source, elles sont vraiment différentes en fonction du compilateur.

Vous pourriez vous demander comment on se retrouve dans une situation pareille? Inclure les chemins bien sur! Si lors de la compilation de la bibliothèque partagée, le chemin d’inclusion mène à header1.h et que vous finissez par utiliser header2.h dans votre propre programme, il vous restera à 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.

Élaboration plus poussée avec un exemple

J'ai deux projets: graphics.lib et main.exe. Les deux projets dépendent de 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

Ensuite, vous allez inclure 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 c'est un échec. La raison en est que la bibliothèque commune utilise différentes versions du même include common_math.h (je l'ai expliqué 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 ).

Notez dans cet exemple, l'éditeur de liens vous dirait qu'il ne pouvait pas 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 problème est que l'éditeur de liens voit une signature différente car 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 arriver parce qu'ils proviennent de deux fichiers d'inclusion légèrement différents (peut-être que les fichiers d'inclusion 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 bizarre mutilé donné dans l'erreur de l'éditeur de liens. (par exemple, draw @ graphics @ XYZ).
  2. Importez 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 mutilé est différent.
  4. Faites attention à la raison pour laquelle les noms mutilés ont été différents. Vous pourrez 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 source liés entre eux pour produire une bibliothèque ou un exécutable.

EDIT 1: Réécrit la première section pour être plus facile à comprendre. Veuillez commenter ci-dessous pour me faire savoir si quelque chose doit être corrigé. Merci!

12
fafaro

Nettoyer et reconstruire

Un «nettoyage» de la construction peut supprimer le «bois mort» qui peut être laissé derrière des constructions précédentes, des constructions échouées, des constructions incomplètes et d'autres problèmes de construction liés au système de construction.

En général, IDE ou la construction inclura une forme de fonction "propre", mais celle-ci pourrait ne pas être configurée correctement (par exemple, dans un fichier makefile manuel) ou échouer (par exemple, les binaires intermédiaires ou résultants sont en lecture seule).

Une fois le "nettoyage" terminé, vérifiez qu'il a réussi et que tous les fichiers intermédiaires générés (par exemple, un fichier Make automatisé) ont été supprimés.

Ce peut être considéré comme un dernier recours, mais constitue souvent une bonne première étape} surtout si le code lié à l'erreur a été ajouté récemment (soit localement, soit à partir du référentiel source).

12
Niall

"Extern" manquant dans les déclarations/définitions de variable const (C++ uniquement)

Pour les personnes venant de C, il peut être surprenant que, dans C++, les variables globales const soient associées à des liaisons internes (ou statiques). Cela n’a pas été le cas en C, toutes les variables globales étant implicitement extern (c’est-à-dire lorsque le mot clé static 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;

Sinon, on peut déclarer la variable const dans fichier1.cpp avec extern explicite

7
Andreas H.

Même s’il s’agit d’une question assez ancienne avec de multiples réponses acceptées, je voudrais expliquer comment résoudre une erreur obscure "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 être compiler également en C++ 14 alors j'ai 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, fichier.cpp:

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

Notez le différentes bibliothèques utilisé dans main.cpp et file.h. Depuis main.cpp # inclus "fichier.h" après <système_fichier>, la version du système de fichiers utilisée était celle de C++ 17 _. J'avais 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 fichier.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs -> liens main.o et fichier.o 

De cette façon, toute fonction contenue dans fichier.o et utilisée dans main.o que requis path_t donnait des erreurs "référence non définie" car (main.o) référé à std::filesystem::path mais (fichier.o) à std::experimental::filesystem::path

Résolution

Pour résoudre ce problème, j’avais juste besoin de changer <experimental :: filestystem> dans file.h en <filestystem>.

3
Stypox

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 générées avec l'option -fvisibility=hidden, seuls 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 montrés par nm avec le 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++ était utilisé).

Semblable aux dll Windows, on marquerait les fonctions publiques avec une définition, par exemple DLL_PUBLIC défini comme:

#define DLL_PUBLIC __attribute__ ((visibility ("default")))

DLL_PUBLIC int my_public_function(){
  ...
}

Ce qui correspond approximativement à la version de Windows/MSVC:

#ifdef BUILDING_DLL
    #define DLL_PUBLIC __declspec(dllexport) 
#else
    #define DLL_PUBLIC __declspec(dllimport) 
#endif

Plus des informations sur la visibilité peuvent être trouvées sur le wiki de gcc.


Lorsqu'une unité de traduction est compilée avec -fvisibility=hidden, les symboles résultants ont encore une liaison externe (indiquée avec le type de symbole en majuscule par nm) et peuvent être utilisés pour une liaison externe sans problème si les fichiers objet font partie d'une bibliothèque statique. La liaison devient locale uniquement lorsque les fichiers d'objet sont liés dans une bibliothèque partagée.

Pour rechercher 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
2
ead

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 correspondent à 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 celle qui convient, soit installer la version appropriée 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’expédier les deux architectures dans le même fichier. Peut-être que le lien s'attend à ce que les deux versions soient présentes, mais une seule existe. Il peut également s'agir d'un problème avec le mauvais dossier lib/lib64 dans lequel la bibliothèque est sélectionnée.

0
Matthieu Brucher

L’erreur se produit lorsque l’éditeur de liens n’a trouvé aucune définition d’une définition dans le fichier de déclaration, c’est-à-dire à partir d’un fichier d’en-tête ou d’un fichier de déclaration. Cela se produit lorsque l'implémentation n'est pas trouvée. 

Après la compilation, l'éditeur de liens essaie de trouver les implémentations à partir d'une bibliothèque, d'une fonction virtuelle ou d'une définition de modèle. S'il n'en trouve pas alors, ces erreurs se produisent conformément à l'architecture de l'éditeur de liens.

0
Karthik Krish
  • Le code peut être compilé tel quel, avec g ++ 44 (Red Hat 4.4.7-8)

Ubuntu a un correctif pour Debian gcc-4.4/ g ++ - 4.4 : Pas bon avec ce code (et quelques autres codes/correctifs ns2 aussi.)

Ubuntu: mpolsr_umolsr-v1_ns235.patchhttps://drive.google.com/file/d/0B7S...ew?usp=sharing (créé en 2017 avec le code mpolsr, aucune modification.)

tar xvf ns-allinone-2.35_gcc5.tar.gz

https://drive.google.com/file/d/0B7S...ew?usp=sharing

cd ns-allinone-2.35/
patch -p0 < mpolsr_umolsr-v1_ns235.patch  // umolsr version v1.0 is used
./install
              // Stops with MPOLSR.cc
cd ns-2.35/
              // Edit the Makefile line 37 to CPP = g++34 , and run make
make
              // When 'make' stops with an mdart/* error, change to CPP = g++-4.4
make

gcc34 Ubuntu https://drive.google.com/file/d/0B7S255p3kFXNRTkzQnRSNXZ6UVU/view?usp=sharing

g ++ 34 Ubuntu https://drive.google.com/file/d/0B7S255p3kFXNV3JVbnWoWGNWdG8/view?usp=sharing

0
Knud Larsen

Les fonctions ou méthodes de classe sont définies dans les fichiers source avec le spécificateur inline.

Un exemple:-

main.cpp

#include "gum.h"
#include "foo.h"

int main()
{
    gum();
    foo f;
    f.bar();
    return 0;
}

foo.h (1)

#pragma once

struct foo {
    void bar() const;
};

gomme.h (1)

#pragma once

extern void gum();

foo.cpp (1)

#include "foo.h"
#include <iostream>

inline /* <- wrong! */ void foo::bar() const {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

gum.cpp (1)

#include "gum.h"
#include <iostream>

inline /* <- wrong! */ void gum()
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

Si vous spécifiez que gum (de la même manière, foo::bar) est inline au sens de sa définition, alors Le compilateur insère gum (s'il le souhaite), par: -

  • n'émettant aucune définition unique de gum, et donc
  • n'émettant aucun symbole par lequel l'éditeur de liens peut faire référence à la définition de gum et
  • en remplaçant tous les appels à gum par des copies incorporées du corps compilé de gum.

Par conséquent, si vous définissez gum inline dans un fichier source gum.cpp, il est compilé dans un fichier objet gum.o dans lequel tous les appels à gum sont en ligne Et aucun symbole n'est défini pour permettre à l'éditeur de liens de faire référence à gum. . Lorsque vousliez gum.o dans un programme avec un autre fichier d’objet, par ex. main.o qui fait référence à un symbole externe gum, l'éditeur de liens ne peut pas résoudre ces références. Donc, la liaison échoue:

Compiler:

g++ -c  main.cpp foo.cpp gum.cpp

Lien:

$ g++ -o prog main.o foo.o gum.o
main.o: In function `main':
main.cpp:(.text+0x18): undefined reference to `gum()'
main.cpp:(.text+0x24): undefined reference to `foo::bar() const'
collect2: error: ld returned 1 exit status

Vous ne pouvez définir gum comme inline que si le compilateur peut voir sa définition dans chaque fichier source dans lequel gum peut être appelé. Cela signifie que sa définition en ligne doit exister dans un fichier header que vous include dans chaque fichier sourcevous compilez dans lequel gum peut être appelé. Faites l'une des deux choses suivantes:

Ne pas inclure les définitions

Supprimez le spécificateur inline de la définition du fichier source:

foo.cpp (2)

#include "foo.h"
#include <iostream>

void foo::bar() const {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

gum.cpp (2)

#include "gum.h"
#include <iostream>

void gum()
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

Reconstruire avec ça:

$ g++ -c  main.cpp foo.cpp gum.cpp
imk@imk-Inspiron-7559:~/develop/so/scrap1$ g++ -o prog main.o foo.o gum.o
imk@imk-Inspiron-7559:~/develop/so/scrap1$ ./prog
void gum()
void foo::bar() const

Succès.

Ou en ligne correctement

Définitions en ligne dans les fichiers d'en-tête:

foo.h (2)

#pragma once
#include <iostream>

struct foo {
    void bar() const  { // In-class definition is implicitly inline
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};
// Alternatively...
#if 0
struct foo {
    void bar() const;
};
inline void foo::bar() const  {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}
#endif

gomme.h (2)

#pragma once
#include <iostream>

inline void gum() {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

Maintenant, nous n'avons plus besoin de foo.cpp ou gum.cpp:

$ g++ -c main.cpp
$ g++ -o prog main.o
$ ./prog
void gum()
void foo::bar() const
0
Mike Kinghan