Wat is een niet-gedefinieerde referentie/onopgeloste externe symboolfout en hoe los ik deze op?

Wat zijn ongedefinieerde referentie/onopgeloste externe symboolfouten? Wat zijn veelvoorkomende oorzaken en hoe deze te verhelpen/voorkomen?


Antwoord 1, autoriteit 100%

Het samenstellen van een C++-programma vindt plaats in verschillende stappen, zoals gespecificeerd door 2.2 (met dank aan Keith Thompson voor de referentie ):

De prioriteit tussen de syntaxisregels voor vertaling wordt gespecificeerd door de volgende fasen [zie voetnoot].

  1. Tekens van fysieke bronbestanden worden op een door de implementatie gedefinieerde manier toegewezen aan de basistekenset van de bron
    (introductie van tekens voor nieuwe regels voor indicatoren aan het einde van de regel) als
    noodzakelijk. [SNIP]
  2. Elke instantie van een backslash-teken (\) onmiddellijk gevolgd door een teken voor een nieuwe regel wordt verwijderd, waardoor fysieke bronregels worden gesplitst naar
    logische bronlijnen vormen. [SNIP]
  3. Het bronbestand wordt ontleed in preprocessing-tokens (2.5) en reeksen witruimtetekens (inclusief opmerkingen). [SNIP]
  4. Voorbewerkingsrichtlijnen worden uitgevoerd, macro-aanroepen worden uitgebreid en _Pragma unaire operatorexpressies worden uitgevoerd. [SNIP]
  5. Elk brontekensetlid in een letterlijke letter of een letterlijke tekenreeks, evenals elke ontsnappingsreeks en universele-tekennaam
    in een letterlijke letter of een niet-onbewerkte tekenreeks, wordt geconverteerd naar
    het corresponderende lid van de uitvoeringstekenset; [SNIP]
  6. Aangrenzende letterlijke tekenreeksen worden aaneengeschakeld.
  7. Witruimtetekens die tokens van elkaar scheiden, zijn niet langer belangrijk. Elke preprocessing token wordt omgezet in een token. (2.7). De
    resulterende tokens worden syntactisch en semantisch geanalyseerd en
    vertaald als een vertaaleenheid. [SNIP]
  8. Vertaalde vertaaleenheden en instantiatie-eenheden worden als volgt gecombineerd: [SNIP]
  9. Alle verwijzingen naar externe entiteiten zijn opgelost. Bibliotheekcomponenten zijn gekoppeld om te voldoen aan externe verwijzingen naar entiteiten die niet zijn gedefinieerd in de
    huidige vertaling. Al dergelijke uitvoer van vertalers wordt verzameld in een
    programma-afbeelding die informatie bevat die nodig is voor uitvoering in zijn
    uitvoeringsomgeving.
    (nadruk van mij)

[footnote] Implementaties moeten zich gedragen alsof deze afzonderlijke fasen plaatsvinden, hoewel in de praktijk verschillende fasen kunnen worden samengevoegd.

De gespecificeerde fouten treden op tijdens deze laatste fase van compilatie, meestal aangeduid als koppelen. Het betekent in feite dat je een aantal implementatiebestanden hebt gecompileerd in objectbestanden of bibliotheken en dat je ze nu wilt laten samenwerken.

Stel dat je symbool A hebt gedefinieerd in a.cpp. Nu, b.cpp verklaarde dat symbool en gebruikte het. Voor het koppelen gaat het er gewoon van uit dat dat symbool ergens is gedefinieerd, maar het maakt nog niet uit waar. De koppelingsfase is verantwoordelijk voor het vinden van het symbool en het correct koppelen ervan aan b.cpp (nou ja, eigenlijk aan het object of de bibliotheek die het gebruikt).

Als je Microsoft Visual Studio gebruikt, zul je zien dat projecten .lib-bestanden genereren. Deze bevatten een tabel met geëxporteerde symbolen en een tabel met geïmporteerde symbolen. De geïmporteerde symbolen worden omgezet in de bibliotheken waarnaar u linkt, en de geëxporteerde symbolen worden geleverd voor de bibliotheken die die .lib gebruiken (indien aanwezig).

Vergelijkbare mechanismen bestaan ​​voor andere compilers/platforms.

Veelvoorkomende foutmeldingen zijn error LNK2001, error LNK1120, error LNK2019 voor Microsoft Visual Studio en undefined reference to symbolName voor GCC.

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

genereert de volgende fouten met 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

en soortgelijke fouten met 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

Veelvoorkomende oorzaken zijn:


Antwoord 2, autoriteit 20%

Lesleden:

Een pure virtual destructor heeft een implementatie nodig.

Om een ​​destructor puur te verklaren, moet je hem nog steeds definiëren (in tegenstelling tot een normale functie):

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

Dit gebeurt omdat destructors van de basisklasse worden aangeroepen wanneer het object impliciet wordt vernietigd, dus een definitie is vereist.

virtual-methoden moeten worden geïmplementeerd of als puur worden gedefinieerd.

Dit is vergelijkbaar met niet-virtual methoden zonder definitie, met de toegevoegde redenering dat
de pure declaratie genereert een dummy vtable en je zou de linkerfout kunnen krijgen zonder de functie te gebruiken:

struct X
{
    virtual void foo();
};
struct Y : X
{
   void foo() {}
};
int main()
{
   Y y; //linker error although there was no call to X::foo
}

Om dit te laten werken, verklaart u X::foo() als puur:

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

Niet-virtual klasleden

Sommige leden moeten worden gedefinieerd, zelfs als ze niet expliciet worden gebruikt:

struct A
{ 
    ~A();
};

Het volgende zou de fout opleveren:

A a;      //destructor undefined

De implementatie kan inline zijn, in de klassedefinitie zelf:

struct A
{ 
    ~A() {}
};

of buiten:

A::~A() {}

Als de implementatie buiten de klassedefinitie valt, maar in een header, moeten de methoden worden gemarkeerd als inline om een ​​meervoudige definitie te voorkomen.

Alle gebruikte lidmethoden moeten worden gedefinieerd als ze worden gebruikt.

Een veelgemaakte fout is het vergeten om de naam te kwalificeren:

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

De definitie zou moeten zijn

void A::foo() {}

static gegevensleden moeten buiten de klas worden gedefinieerd in een enkele vertaaleenheid:

struct X
{
    static int x;
};
int main()
{
    int x = X::x;
}
//int X::x; //uncomment this line to define X::x

Er kan een initialisatiefunctie worden gegeven voor een static const-gegevenslid van het integraal- of opsommingstype binnen de klassedefinitie; odr-gebruik van dit lid vereist echter nog steeds een naamruimtebereikdefinitie zoals hierboven beschreven. C++11 staat initialisatie binnen de klasse toe voor alle static const dataleden.


Antwoord 3, autoriteit 14%

Kan niet linken naar geschikte bibliotheken/objectbestanden of implementatiebestanden compileren

Gewoonlijk genereert elke vertaaleenheid een objectbestand dat de definities bevat van de symbolen die in die vertaaleenheid zijn gedefinieerd.
Om die symbolen te gebruiken, moet je naar die objectbestanden linken.

Onder gcc zou u alle objectbestanden specificeren die aan elkaar gekoppeld moeten worden in de opdrachtregel, of de implementatiebestanden samen compileren.

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

De libraryName hier is slechts de naam van de bibliotheek, zonder platformspecifieke toevoegingen. Dus bijv. op Linux worden bibliotheekbestanden gewoonlijk libfoo.so genoemd, maar u schrijft alleen -lfoo. In Windows zou datzelfde bestand foo.lib kunnen heten, maar je zou hetzelfde argument gebruiken. Mogelijk moet u de map toevoegen waar deze bestanden te vinden zijn met behulp van -L‹directory›. Zorg ervoor dat u geen spatie schrijft na -L of -L.

Voor XCode: voeg de zoekpaden in de koptekst van de gebruiker toe -> voeg het bibliotheekzoekpad toe -> sleep de eigenlijke bibliotheekverwijzing naar de projectmap.

Onder MSVS worden bij bestanden die aan een project zijn toegevoegd automatisch de objectbestanden aan elkaar gekoppeld en wordt een lib-bestand gegenereerd (in algemeen gebruik). Om de symbolen in een apart project te gebruiken, zou je:
moet u de lib-bestanden opnemen in de projectinstellingen. Dit wordt gedaan in het gedeelte Linker van de projecteigenschappen, in Input -> Additional Dependencies. (het pad naar het bestand lib moet zijn
toegevoegd in Linker -> General -> Additional Library Directories) Bij gebruik van een bibliotheek van derden die is voorzien van een lib-bestand, resulteert het niet doen hiervan meestal in de fout.

Het kan ook voorkomen dat u vergeet het bestand aan de compilatie toe te voegen, in welk geval het objectbestand niet wordt gegenereerd. In gcc voeg je de bestanden toe aan de opdrachtregel. In MSVS zal het toevoegen van het bestand aan het project ervoor zorgen dat het automatisch wordt gecompileerd (hoewel bestanden handmatig kunnen worden uitgesloten van de build).

In Windows-programmering is het verklikkerteken dat u geen noodzakelijke bibliotheek hebt gekoppeld, dat de naam van het onopgeloste symbool begint met __imp_. Zoek de naam van de functie op in de documentatie en er zou moeten staan ​​welke bibliotheek je moet gebruiken. MSDN plaatst de informatie bijvoorbeeld in een vak onder aan elke functie in een sectie met de naam “Bibliotheek”.


Antwoord 4, autoriteit 12%

Aangegeven maar geen variabele of functie gedefinieerd.

Een typische variabele declaratie is

extern int x;

Omdat dit slechts een verklaring is, is een enkele definitie nodig. Een overeenkomstige definitie zou zijn:

int x;

Het volgende zou bijvoorbeeld een fout genereren:

extern int x;
int main()
{
    x = 0;
}
//int x; // uncomment this line for successful definition

Vergelijkbare opmerkingen zijn van toepassing op functies. Het declareren van een functie zonder deze te definiëren leidt tot de fout:

void foo(); // declaration only
int main()
{
   foo();
}
//void foo() {} //uncomment this line for successful definition

Zorg ervoor dat de functie die u implementeert exact overeenkomt met de functie die u hebt gedeclareerd. U kunt bijvoorbeeld niet-overeenkomende cv-kwalificaties hebben:

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)

Andere voorbeelden van mismatches zijn onder meer

  • Functie/variabele gedeclareerd in de ene naamruimte, gedefinieerd in een andere.
  • Functie/variabele gedeclareerd als klaslid, gedefinieerd als globaal (of vice versa).
  • Functieretourtype, parameternummer en -types en aanroepconventie komen niet allemaal precies overeen.

De foutmelding van de compiler geeft je vaak de volledige declaratie van de variabele of functie die is gedeclareerd maar nooit is gedefinieerd. Vergelijk het goed met de definitie die je hebt gegeven. Zorg ervoor dat elk detail overeenkomt.


Antwoord 5, autoriteit 10%

De volgorde waarin onderling afhankelijke gekoppelde bibliotheken worden opgegeven, is verkeerd.

De volgorde waarin bibliotheken zijn gekoppeld, is WEL van belang als de bibliotheken van elkaar afhankelijk zijn. In het algemeen geldt dat als bibliotheek A afhankelijk is van bibliotheek B, libA MOET verschijnen voor libB in de linkervlaggen.

Bijvoorbeeld:

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

Maak de bibliotheken:

$ 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

Compileren:

$ 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

Dus om het nog een keer te herhalen, de volgorde DOES is belangrijk!


Antwoord 6, autoriteit 8%

wat is een “niet gedefinieerde referentie/onopgelost extern symbool”

Ik zal proberen uit te leggen wat een “ongedefinieerde referentie/onopgelost extern symbool” is.

opmerking: ik gebruik g++ en Linux en alle voorbeelden zijn ervoor

We hebben bijvoorbeeld een 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;
}

en

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

Maak objectbestanden

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

Na de assembler-fase hebben we een objectbestand, dat eventuele symbolen bevat om te exporteren.
Kijk naar de symbolen

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

Ik heb een aantal regels afgewezen voor uitvoer, omdat ze er niet toe doen

We zien dus volgsymbolen om te exporteren.

[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable

src2.cpp exporteert niets en we hebben geen symbolen gezien

Link onze objectbestanden

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

en voer het uit

$ ./prog
123

Linker ziet geëxporteerde symbolen en koppelt deze. Nu proberen we regels in src2.cpp te verwijderen, zoals hier

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

en herbouw een objectbestand

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

OK (geen fouten), omdat we alleen een objectbestand bouwen, is het koppelen nog niet gedaan.
Probeer te linken

$ 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

Het is gebeurd omdat onze local_var_name statisch is, d.w.z. niet zichtbaar voor andere modules.
Nu dieper. De uitvoer van de vertaalfase ophalen

$ 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

Dus we hebben gezien dat er geen label is voor local_var_name, daarom heeft linker het niet gevonden. Maar we zijn hackers 🙂 en we kunnen het repareren. Open src1.s in je teksteditor en wijzig

.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4

naar

    .globl  local_var_name
    .data
    .align 4
    .type   local_var_name, @object
    .size   local_var_name, 4
local_var_name:
    .long   456789

d.w.z. je zou zoals hieronder moeten hebben

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

we hebben de zichtbaarheid van local_var_name gewijzigd en de waarde ingesteld op 456789.
Probeer er een objectbestand van te maken

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

ok, zie readelf output (symbolen)

$ readelf --symbols src1.o
8: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 local_var_name

local_var_name heeft nu Bind GLOBAL (was LOCAL)

link

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

en voer het uit

$ ./prog 
123456789

ok, we hacken het 🙂

Dus als resultaat – een “ongedefinieerde verwijzing/onopgeloste externe symboolfout” treedt op wanneer de linker geen globale symbolen in de objectbestanden kan vinden.


Antwoord 7, autoriteit 8%

Symbolen werden gedefinieerd in een C-programma en gebruikt in C++-code.

De functie (of variabele) void foo() is gedefinieerd in een C-programma en u probeert deze te gebruiken in een C++-programma:

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

De C++-linker verwacht dat namen worden verminkt, dus je moet de functie declareren als:

extern "C" void foo();
int main()
{
    foo();
}

Equivalent, in plaats van te worden gedefinieerd in een C-programma, werd de functie (of variabele) void foo() gedefinieerd in C++ maar met C-koppeling:

extern "C" void foo();

en u probeert het te gebruiken in een C++-programma met C++-koppeling.

Als een hele bibliotheek is opgenomen in een headerbestand (en is gecompileerd als C-code); de include moet als volgt zijn;

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

Antwoord 8, autoriteit 8%

Als al het andere faalt, compileer dan opnieuw.

Ik heb onlangs een onopgeloste externe fout in Visual Studio 2012 kunnen verwijderen door het betreffende bestand opnieuw te compileren. Toen ik opnieuw bouwde, was de fout weg.

Dit gebeurt meestal wanneer twee (of meer) bibliotheken een cyclische afhankelijkheid hebben. Bibliotheek A probeert symbolen te gebruiken in B.lib en bibliotheek B probeert symbolen uit A.lib te gebruiken. Geen van beide bestaat om mee te beginnen. Wanneer u probeert A te compileren, mislukt de koppelingsstap omdat deze B.lib niet kan vinden. A.lib wordt gegenereerd, maar geen dll. U compileert vervolgens B, wat zal slagen en B.lib genereert. Het opnieuw compileren van A werkt nu omdat B.lib nu is gevonden.


Antwoord 9, autoriteit 7%

Sjabloonimplementaties niet zichtbaar.

Niet-gespecialiseerde sjablonen moeten hun definities zichtbaar hebben voor alle vertaaleenheden die ze gebruiken. Dat betekent dat u de definitie van een sjabloon niet kunt scheiden
naar een uitvoeringsdossier. Als u de implementatie moet scheiden, is de gebruikelijke oplossing om een ​​impl-bestand te hebben dat u aan het einde van de header toevoegt
verklaart de sjabloon. Een veelvoorkomende situatie is:

template<class T>
struct X
{
    void foo();
};
int main()
{
    X<int> x;
    x.foo();
}
//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}

Om dit op te lossen, moet je de definitie van X::foo verplaatsen naar het headerbestand of naar een plaats die zichtbaar is voor de vertaaleenheid die het gebruikt.

Gespecialiseerde sjablonen kunnen worden geïmplementeerd in een implementatiebestand en de implementatie hoeft niet zichtbaar te zijn, maar de specialisatie moet vooraf worden aangegeven.

Voor verdere uitleg en een andere mogelijke oplossing (expliciete instantiatie) zie deze vraag en antwoord.


Antwoord 10, autoriteit 7%

Dit is een van de meest verwarrende foutmeldingen die elke VC++-programmeur keer op keer heeft gezien. Laten we eerst duidelijkheid scheppen.

A. Wat is symbool?
Kortom, een symbool is een naam. Het kan een variabelenaam, een functienaam, een klassenaam, een typedef-naam of iets anders zijn, behalve de namen en tekens die tot de C++-taal behoren. Het wordt door de gebruiker gedefinieerd of geïntroduceerd door een afhankelijkheidsbibliotheek (door een andere gebruiker gedefinieerd).

B. Wat is extern?
In VC++ wordt elk bronbestand (.cpp,.c,etc.) beschouwd als een vertaaleenheid, de compiler compileert één eenheid tegelijk en genereert één objectbestand (.obj) voor de huidige vertaaleenheid. (Merk op dat elk headerbestand dat dit bronbestand bevat, voorbewerkt zal worden en als onderdeel van deze vertaaleenheid zal worden beschouwd) Alles binnen een vertaaleenheid wordt als intern beschouwd, al het andere als extern. In C++ kunt u naar een extern symbool verwijzen door trefwoorden te gebruiken zoals extern, __declspec (dllimport) enzovoort.

C. Wat is resolve ?
Resolve is een term voor koppelingstijd. In koppeltijd probeert linker de externe definitie te vinden voor elk symbool in objectbestanden die de definitie intern niet kunnen vinden. De reikwijdte van dit zoekproces, inclusief:

  • Alle objectbestanden die tijdens het compileren zijn gegenereerd
  • Alle bibliotheken (.lib) die expliciet of impliciet zijn
    gespecificeerd als aanvullende afhankelijkheden van deze bouwtoepassing.

Dit zoekproces wordt oplossen genoemd.

D. Tot slot, waarom onopgelost extern symbool?
Als de linker de externe definitie niet kan vinden voor een symbool dat geen interne definitie heeft, rapporteert hij een onopgeloste externe symboolfout.

E. Mogelijke oorzaken van LNK2019: onopgeloste externe symboolfout.
We weten al dat deze fout te wijten is aan het feit dat de linker de definitie van externe symbolen niet kon vinden, de mogelijke oorzaken kunnen worden gesorteerd als:

  1. Definitie bestaat

Als we bijvoorbeeld een functie met de naam foo hebben gedefinieerd in a.cpp:

int foo()
{
    return 0;
}

In b.cpp willen we de functie foo aanroepen, dus voegen we toe

void foo();

om functie foo() te declareren en in een andere functietekst aan te roepen, zeg je bar():

void bar()
{
    foo();
}

Als je nu deze code maakt, krijg je een LNK2019-foutmelding waarin wordt geklaagd dat foo een onopgelost symbool is. In dit geval weten we dat foo() zijn definitie heeft in a.cpp, maar verschilt van degene die we aanroepen (andere retourwaarde). Dit is het geval dat er een definitie bestaat.

  1. Definitie bestaat niet

Als we enkele functies in een bibliotheek willen aanroepen, maar de importbibliotheek wordt niet toegevoegd aan de aanvullende afhankelijkheidslijst (ingesteld uit: Project | Properties | Configuration Properties | Linker | Input | Additional Dependency ) van uw projectinstelling. Nu zal de linker een LNK2019 rapporteren omdat de definitie niet bestaat in het huidige zoekbereik.


Antwoord 11, autoriteit 7%

Onjuist importeren/exporteren van methoden/klassen tussen modules/dll (specifiek voor de compiler).

MSVS vereist dat u specificeert welke symbolen u wilt exporteren en importeren met behulp van __declspec(dllexport) en __declspec(dllimport).

Deze dubbele functionaliteit wordt meestal verkregen door het gebruik van een macro:

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

De macro THIS_MODULE zou alleen worden gedefinieerd in de module die de functie exporteert. Op die manier is de verklaring:

DLLIMPEXP void foo();

uitbreidt naar

__declspec(dllexport) void foo();

en vertelt de compiler om de functie te exporteren, aangezien de huidige module zijn definitie bevat. Als de aangifte in een andere module wordt opgenomen, wordt deze uitgebreid naar

__declspec(dllimport) void foo();

en vertelt de compiler dat de definitie in een van de bibliotheken staat waar je naar gelinkt hebt (zie ook 1)).

U kunt op dezelfde manier klassen importeren/exporteren:

class DLLIMPEXP X
{
};

Antwoord 12, autoriteit 5%

undefined verwijzing naar WinMain@16 of vergelijkbare ‘ongebruikelijke’ main() referentiepuntverwijzing (vooral voor ).

Misschien heb je gemist om het juiste projecttype te kiezen met je huidige IDE. De IDE wil mogelijk b.v. Windows-toepassing projecteert naar een dergelijke ingangspuntfunctie (zoals gespecificeerd in de ontbrekende referentie hierboven), in plaats van de veelgebruikte int main(int argc, char** argv);-handtekening.

Als uw IDE Plain Console-projecten ondersteunt, wilt u misschien dit projecttype kiezen in plaats van een Windows-toepassingsproject.


Hier zijn case1 en case2 in meer detail behandeld vanuit een echte wereld probleem.


Antwoord 13, autoriteit 4%

Als u bibliotheken van derden gebruikt, zorg er dan voor dat u de juiste 32/64-bits binaire bestanden gebruikt


Antwoord 14, autoriteit 4%

Microsoft biedt een #pragma om op het moment van de koppeling naar de juiste bibliotheek te verwijzen;

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

Naast het bibliotheekpad inclusief de directory van de bibliotheek, moet dit de volledige naam van de bibliotheek zijn.


Antwoord 15, autoriteit 4%

Visual Studio NuGet-pakket moet worden bijgewerkt voor nieuwe toolsetversie

Ik had net dit probleem bij het koppelen van libpng met Visual Studio 2013. Het probleem is dat het pakketbestand alleen bibliotheken had voor Visual Studio 2010 en 2012.

De juiste oplossing is te hopen dat de ontwikkelaar een bijgewerkt pakket uitbrengt en vervolgens upgradet, maar het werkte voor mij door een extra instelling voor VS2013 te hacken, verwijzend naar de VS2012-bibliotheekbestanden.

Ik heb het pakket bewerkt (in de map packages in de directory van de oplossing) door packagename\build\native\packagename.targets te zoeken en in dat bestand alle v110 secties. Ik heb de v110 gewijzigd in v120 in alleen de voorwaardevelden en heel voorzichtig om de bestandsnaampaden allemaal als v110 te laten . Hierdoor kon Visual Studio 2013 eenvoudig linken naar de bibliotheken voor 2012 en in dit geval werkte het.


Antwoord 16, autoriteit 4%

Stel dat je een groot project hebt geschreven in c++ met duizend .cpp-bestanden en duizend .h-bestanden. En laten we zeggen dat het project ook afhankelijk is van tien statische bibliotheken. Laten we zeggen dat we op Windows werken en ons project bouwen in Visual Studio 20xx. Wanneer u op Ctrl + F7 Visual Studio drukt om de hele oplossing te compileren (stel dat we maar één project in de oplossing hebben)

Wat is de betekenis van compilatie?

  • Visual Studio zoekt in bestand .vcxproj en begint met het compileren van elk bestand met de extensie .cpp. De volgorde van compilatie is niet gedefinieerd. U moet er dus niet vanuit gaan dat het bestand main.cpp eerst wordt gecompileerd
  • Als .cpp-bestanden afhankelijk zijn van extra .h-bestanden om symbolen te vinden
    die al dan niet gedefinieerd zijn in het bestand .cpp
  • Als er één .cpp-bestand bestaat waarin de compiler geen symbool kon vinden, geeft een compilertijdfout het bericht Symbool x kan niet worden gevonden
  • Voor elk bestand met de extensie .cpp wordt een objectbestand .o gegenereerd en ook Visual Studio schrijft de uitvoer in een bestand met de naam ProjectName.Cpp.Clean.txt dat alle objectbestanden bevat die moeten worden verwerkt door de linker.

De tweede stap van het compileren wordt gedaan door Linker.Linker moet alle objectbestanden samenvoegen en uiteindelijk de uitvoer bouwen (dit kan een uitvoerbaar bestand of een bibliotheek zijn)

Stappen bij het koppelen van een project

  • Ontdek alle objectbestanden en vind de definitie die alleen in headers is gedeclareerd (bijv. De code van een methode van een klasse zoals vermeld in eerdere antwoorden, of de initialisatie van een statische variabele die lid is van een klasse )
  • Als een symbool niet kan worden gevonden in objectbestanden, wordt hij ook doorzocht in Aanvullende bibliotheken. Voor het toevoegen van een nieuwe bibliotheek aan een project Configuratie-eigenschappen -> VC++-mappen -> Bibliotheekmappen en hier heb je een extra map opgegeven voor het doorzoeken van bibliotheken en Configuratie-eigenschappen -> Linker -> Invoer voor het specificeren van de naam van de bibliotheek.
    -Als de Linker het symbool dat u in één .cpp schrijft niet kan vinden, roept hij een linkertijdfout op die kan klinken als
    error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

Observatie

  1. Zodra de Linker één symbool vindt, zoekt hij er niet naar in andere bibliotheken
  2. De volgorde van het koppelen van bibliotheken doet er toe.
  3. Als Linker een extern symbool vindt in een statische bibliotheek, neemt hij het symbool op in de uitvoer van het project. Als de bibliotheek echter wordt gedeeld (dynamisch), neemt hij de code (symbolen) niet op in de uitvoer, maar Runtime crashes kunnen optreden

Hoe dit soort fouten op te lossen

Compilertijdfout:

  • Zorg ervoor dat u uw c++-project syntactisch correct schrijft.

Linker-tijdfout

  • Definieer al je symbolen die je declareert in je header-bestanden
  • Gebruik #pragma once om de compiler toe te staan ​​geen enkele header op te nemen als deze al was opgenomen in de huidige .cpp die wordt gecompileerd
  • Zorg ervoor dat uw externe bibliotheek geen symbolen bevat die in conflict kunnen komen met andere symbolen die u in uw headerbestanden hebt gedefinieerd
  • Als je de sjabloon gebruikt om er zeker van te zijn dat je de definitie van elke sjabloonfunctie in het headerbestand opneemt, zodat de compiler de juiste code kan genereren voor elke instantie.

Antwoord 17, autoriteit 3%

Een bug in de compiler/IDE

Ik had onlangs dit probleem en het bleek het was een fout in Visual Studio Express 2013. Ik moest een bronbestand uit het project verwijderen en opnieuw toevoegen om de bug te verhelpen.

Stappen om te proberen als je denkt dat het een bug in compiler/IDE kan zijn:

  • Maak het project schoon (sommige IDE’s hebben een optie om dit te doen, u kunt ook
    doe het handmatig door de objectbestanden te verwijderen)
  • Probeer een nieuw project te starten,
    alle broncode kopiëren van de originele.

Antwoord 18, autoriteit 3%

Gebruik de linker om de fout te diagnosticeren

De meeste moderne linkers bevatten een uitgebreide optie die in verschillende mate wordt afgedrukt;

  • Aanroep van koppeling (opdrachtregel),
  • Gegevens over welke bibliotheken zijn opgenomen in de koppelingsfase,
  • De locatie van de bibliotheken,
  • Gebruikte zoekpaden.

Voor gcc en clang; normaal gesproken voegt u -v -Wl,--verbose of -v -Wl,-v toe aan de opdrachtregel. Meer details vindt u hier;

Voor MSVC wordt /VERBOSE (in het bijzonder /VERBOSE:LIB) toegevoegd aan de link-opdrachtregel.


Antwoord 19, autoriteit 3%

Gekoppeld .lib-bestand is gekoppeld aan een .dll

Ik had hetzelfde probleem. Stel dat ik projecten MyProject en TestProject heb. Ik had het lib-bestand voor MyProject effectief aan het TestProject gekoppeld. Dit lib-bestand is echter geproduceerd toen de DLL voor het MyProject werd gebouwd. Ook bevatte ik niet de broncode voor alle methoden in het MyProject, maar alleen toegang tot de toegangspunten van de DLL.

Om het probleem op te lossen, heb ik het MyProject als een LIB gebouwd en TestProject aan dit .lib-bestand gekoppeld (ik kopieer en plak het gegenereerde .lib-bestand in de map TestProject). Ik kan dan MyProject opnieuw bouwen als DLL. Het is aan het compileren omdat de lib waaraan TestProject is gekoppeld wel code bevat voor alle methoden in klassen in MyProject.


Antwoord 20, autoriteit 3%

Omdat mensen naar deze vraag lijken te worden verwezen als het gaat om linkerfouten, ga ik dit hier toevoegen.

Een mogelijke reden voor linker-fouten met GCC 5.2.0 is dat er nu standaard een nieuwe libstdc++-bibliotheek ABI is gekozen.

Als u linker-fouten krijgt over ongedefinieerde verwijzingen naar symbolen die betrekking hebben op typen in de std::__cxx11-naamruimte of de tag [abi:cxx11], dan geeft dit waarschijnlijk aan dat u probeert om objectbestanden aan elkaar te koppelen die met verschillende waarden zijn gecompileerd voor de _GLIBCXX_USE_CXX11_ABI-macro. Dit gebeurt meestal bij het linken naar een bibliotheek van derden die is gecompileerd met een oudere versie van GCC. Als de bibliotheek van derden niet opnieuw kan worden opgebouwd met de nieuwe ABI, moet u uw code opnieuw compileren met de oude ABI.

Dus als je plotseling linker-fouten krijgt bij het overschakelen naar een GCC na 5.1.0, is dit iets om uit te proberen.


Antwoord 21, autoriteit 2%

Uw koppeling verbruikt bibliotheken vóór de objectbestanden die ernaar verwijzen

  • U probeert uw programma te compileren en te koppelen met de GCC-toolchain.
  • Uw koppeling specificeert alle benodigde bibliotheken en bibliotheekzoekpaden
  • Als libfoo afhankelijk is van libbar, dan plaatst uw koppeling correct libfoo vóór libbar.
  • Uw koppeling mislukt met undefined reference to iets fouten.
  • Maar alle ongedefinieerde iets‘s worden gedeclareerd in de header-bestanden die je hebt
    #included en zijn in feite gedefinieerd in de bibliotheken die u koppelt.

Voorbeelden staan ​​in C. Ze kunnen evengoed C++ zijn

Een minimaal voorbeeld van een statische bibliotheek die je zelf hebt gebouwd

mijn_lib.c

#include "my_lib.h"
#include <stdio.h>
void hw(void)
{
    puts("Hello World");
}

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

U bouwt uw statische bibliotheek:

$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o

U stelt uw programma samen:

$ gcc -I. -c -o eg1.o eg1.c

Je probeert het te linken met libmy_lib.a en faalt:

$ 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

Hetzelfde resultaat als je in één stap compileert en linkt, zoals:

$ 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

Een minimaal voorbeeld van een gedeelde systeembibliotheek, de compressiebibliotheek libz

eg2.c

#include <zlib.h>
#include <stdio.h>
int main()
{
    printf("%s\n",zlibVersion());
    return 0;
}

Stel je programma samen:

$ gcc -c -o eg2.o eg2.c

Probeer je programma te linken met libz en faal:

$ 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

Hetzelfde als je in één keer compileert en linkt:

$ 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

En een variatie op voorbeeld 2 met pkg-config:

$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'

Wat doe je verkeerd?

In de reeks objectbestanden en bibliotheken die u wilt koppelen om uw
programma plaatst u de bibliotheken voor de objectbestanden die verwijzen naar
hen. U moet de bibliotheken na de objectbestanden plaatsen die verwijzen
voor hen.

Koppel voorbeeld 1 correct:

$ gcc -o eg1 eg1.o -L. -lmy_lib

Succes:

$ ./eg1 
Hello World

Koppel voorbeeld 2 correct:

$ gcc -o eg2 eg2.o -lz

Succes:

$ ./eg2 
1.2.8

Koppel de variatie van voorbeeld 2 pkg-config correct:

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

De uitleg

Lezen is vanaf hier optioneel.

Standaard wordt een koppelingsopdracht gegenereerd door GCC op uw distro,
verbruikt de bestanden in de koppeling van links naar rechts in
volgorde van de opdrachtregel. Wanneer het vindt dat een bestand verwijst naar iets
en bevat er geen definitie voor, om naar een definitie te zoeken
in bestanden verder naar rechts. Als het uiteindelijk een definitie vindt, zal de
verwijzing is opgelost. Als er aan het eind nog geen verwijzingen zijn opgelost,
de koppeling mislukt: de linker zoekt niet achteruit.

Eerst, voorbeeld 1, met statische bibliotheek my_lib.a

Een statische bibliotheek is een geïndexeerd archief van objectbestanden. Wanneer de linker
vindt -lmy_lib in de koppelingsvolgorde en komt erachter dat dit verwijst
naar de statische bibliotheek ./libmy_lib.a, het wil weten of uw programma
heeft een van de objectbestanden in libmy_lib.a nodig.

Er is alleen een objectbestand in libmy_lib.a, namelijk my_lib.o, en er is maar één ding gedefinieerd
in my_lib.o, namelijk de functie hw.

De linker zal beslissen dat je programma my_lib.o nodig heeft als en alleen als het dat al weet
je programma verwijst naar hw, in een of meer van de objectbestanden die het al heeft
toegevoegd aan het programma, en dat geen van de objectbestanden die het al heeft toegevoegd
bevat een definitie voor hw.

Als dat waar is, zal de linker een kopie van my_lib.o uit de bibliotheek halen en
voeg het toe aan je programma. Dan bevat je programma een definitie voor hw, dus
de verwijzingen naar hw zijn opgelost.

Als je het programma probeert te linken zoals:

$ gcc -o eg1 -L. -lmy_lib eg1.o

de linker heeft niet toegevoegd eg1.o aan het programma wanneer hij ziet
-lmy_lib. Omdat het op dat moment eg1.o niet heeft gezien.
Uw programma verwijst nog niet naar hw: it
maakt nog geen verwijzingen helemaal, omdat alle verwijzingen die het maakt
staan ​​in eg1.o.

Dus de linker voegt my_lib.o niet toe aan het programma en heeft geen verdere
gebruiken voor libmy_lib.a.

Vervolgens vindt het eg1.o en voegt het toe als programma. Een objectbestand in de
koppelingsvolgorde wordt altijd aan het programma toegevoegd. Nu maakt het programma
een verwijzing naar hw, en bevat geen definitie van hw; maar
er is niets meer in de koppelingsvolgorde dat de ontbrekende zou kunnen bieden
definitie. De verwijzing naar hw wordt onopgelost en de koppeling mislukt.

Ten tweede, voorbeeld 2, met gedeelde bibliotheek libz

Een gedeelde bibliotheek is geen archief van objectbestanden of iets dergelijks. Zijn
veel meer als een programma dat geen main-functie heeft en
onthult in plaats daarvan meerdere andere symbolen die het definieert, zodat andere
programma’s kunnen ze tijdens runtime gebruiken.

Veel Linux-distributies configureren tegenwoordig hun GCC-toolchain zodat de taalstuurprogramma’s (gcc,g++,gfortran enz.)
instrueer de systeemlinker (ld) om gedeelde bibliotheken te koppelen op een indien nodig basis.
Je hebt een van die distributies.

Dit betekent dat wanneer de linker -lz vindt in de koppelingsvolgorde, en erachter komt dat dit verwijst
naar de gedeelde bibliotheek (zeg) /usr/lib/x86_64-linux-gnu/libz.so, wil het weten of er verwijzingen zijn die het aan je programma heeft toegevoegd en die nog niet zijn gedefinieerd definities die worden geëxporteerd door libz

Als dat waar is, dan zal de linker geen stukjes uit libz kopiëren en
voeg ze toe aan je programma; in plaats daarvan zal het gewoon de code van je programma bewerken
zodat:-

  • Tijdens runtime laadt de systeemprogrammalader een kopie van libz in de
    hetzelfde proces als uw programma wanneer het een kopie van uw programma laadt, om het uit te voeren.

  • Tijdens runtime, wanneer uw programma verwijst naar iets dat is gedefinieerd in
    libz, die verwijzing gebruikt de definitie die is geëxporteerd door de kopie van libz in
    hetzelfde proces.

Uw programma wil verwijzen naar slechts één ding waarvan de definitie is geëxporteerd door libz,
namelijk de functie zlibVersion, waarnaar slechts één keer wordt verwezen in eg2.c.
Als de linker die verwijzing aan je programma toevoegt, en dan de definitie vindt
geëxporteerd door libz, de referentie is opgelost

Maar wanneer u het programma probeert te linken zoals:

gcc -o eg2 -lz eg2.o

de volgorde van gebeurtenissen is op dezelfde manier verkeerd als bij voorbeeld 1.
Op het moment dat de linker -lz vindt, zijn er geen verwijzingen naar iets
in het programma: ze staan ​​allemaal in eg2.o, wat nog niet is gezien. Dus de
linker besluit dat het geen nut heeft voor libz. Wanneer het eg2.o bereikt, voegt het toe aan het programma,
en vervolgens een ongedefinieerde verwijzing naar zlibVersion heeft, is de koppelingsvolgorde voltooid;
die verwijzing is niet opgelost en de koppeling mislukt.

Ten slotte heeft de variatie pkg-config van voorbeeld 2 een nu voor de hand liggende verklaring.
Na schaalvergroting:

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

wordt:

gcc -o eg2 -lz eg2.o

wat weer gewoon voorbeeld 2 is.

Ik kan het probleem reproduceren in voorbeeld 1, maar niet in voorbeeld 2

De koppeling:

gcc -o eg2 -lz eg2.o

werkt prima voor jou!

(Of: die koppeling werkte prima voor u, laten we zeggen Fedora 23, maar mislukt op Ubuntu 16.04)

Dat komt omdat de distro waarop de koppeling werkt een van de distro’s is die
configureert zijn GCC-toolchain niet om gedeelde bibliotheken indien nodig te linken.

Vroeger was het normaal dat Unix-achtige systemen statisch en gedeeld aan elkaar koppelden
bibliotheken volgens verschillende regels. Statische bibliotheken in een koppelingsvolgorde werden gekoppeld
op basis van indien nodig uitgelegd in voorbeeld 1, maar gedeelde bibliotheken werden onvoorwaardelijk gekoppeld.

Dit gedrag is voordelig bij linktime omdat de linker niet hoeft na te denken
of het programma een gedeelde bibliotheek nodig heeft: als het een gedeelde bibliotheek is,
koppelen. En de meeste bibliotheken in de meeste koppelingen zijn gedeelde bibliotheken. Maar er zijn ook nadelen:-

  • Het is onrendabel bij runtime, omdat het ertoe kan leiden dat gedeelde bibliotheken
    geladen samen met een programma, zelfs als ze niet nodig zijn.

  • De verschillende koppelingsregels voor statische en gedeelde bibliotheken kunnen verwarrend zijn
    voor ondeskundige programmeurs, die misschien niet weten of -lfoo in hun koppeling zit
    gaat oplossen naar /some/where/libfoo.a of naar /some/where/libfoo.so,
    en begrijpen mogelijk het verschil niet tussen gedeelde en statische bibliotheken
    hoe dan ook.

Deze afweging heeft geleid tot de schismatieke situatie van vandaag. Sommige distributies hebben
hebben hun GCC-koppelingsregels voor gedeelde bibliotheken gewijzigd, zodat de indien nodig
principe geldt voor alle bibliotheken. Sommige distro’s zijn bij de oude gebleven
manier.

Waarom krijg ik dit probleem nog steeds, zelfs als ik tegelijkertijd compileer en link?

Als ik het gewoon doe:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c

gcc moet toch eerst eg1.c compileren, en dan het resultaat koppelen
objectbestand met libmy_lib.a. Dus hoe kan het dat objectbestand niet kennen?
is nodig bij het koppelen?

Omdat compileren en linken met een enkele opdracht niets verandert aan de
volgorde van de koppelingsvolgorde.

Als je het bovenstaande commando uitvoert, komt gcc erachter dat je een compilatie wilt +
koppeling. Dus achter de schermen genereert het een compilatiecommando en wordt uitgevoerd
het, genereert vervolgens een koppelingscommando en voert het uit, alsof u de . had uitgevoerd
twee commando’s:

$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o

Dus de koppeling mislukt net zoals wanneer je deze twee commando’s doe uitvoert. De
het enige verschil dat je merkt aan de mislukking is dat gcc a . heeft gegenereerd
tijdelijk objectbestand in het geval van compile + link, omdat je het niet vertelt
om eg1.o te gebruiken. We zien:

/tmp/ccQk1tvs.o: In function `main'

in plaats van:

eg1.o: In function `main':

Zie ook

De volgorde waarin onderling afhankelijke gekoppelde bibliotheken worden opgegeven, is verkeerd

Het is maar één manier om onderling afhankelijke bibliotheken in de verkeerde volgorde te plaatsen
waarin je bestanden kunt krijgen die definities nodig hebben van dingen die eraan komen
later in de koppeling dan de bestanden die de definities geven. Bibliotheken voor de . plaatsen
objectbestanden die ernaar verwijzen is een andere manier om dezelfde fout te maken.


Antwoord 22, autoriteit 2%

Een wrapper rond GNU ld die geen linkerscripts ondersteunt

Sommige .so-bestanden zijn eigenlijk GNU ld linker-scripts, bijv libtbb.so-bestand is een ASCII-tekstbestand met deze inhoud:

INPUT (libtbb.so.2)

Sommige complexere builds ondersteunen dit mogelijk niet. Als u bijvoorbeeld -v opneemt in de compileropties, kunt u zien dat de mainwin gcc-wrapper mwdip verwijdert linkerscript-opdrachtbestanden in de uitgebreide uitvoerlijst van bibliotheken om in te linken. Een eenvoudige oplossing is om het linkerscript-invoeropdrachtbestand te vervangen door een kopie van het bestand (of een symbolische link), bijvoorbeeld

cp libtbb.so.2 libtbb.so

Of je zou het -l argument kunnen vervangen door het volledige pad van de .so, b.v. in plaats van -ltbb doe /home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2


Antwoord 23, autoriteit 2%

Sjablonen vriendschap sluiten…

Gezien het codefragment van een sjabloontype met een vriend-operator (of functie);

template <typename T>
class Foo {
    friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};

De operator<< wordt gedeclareerd als een niet-sjabloonfunctie. Voor elk type t dat wordt gebruikt met Foo, moet er een niet-sjabloon operator<< zijn. Als er bijvoorbeeld een type Foo<int> is gedeclareerd, dan moet er als volgt een operatorimplementatie zijn;

std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}

Omdat het niet is geïmplementeerd, kan de linker het niet vinden en resulteert in de fout.

Om dit te corrigeren, kunt u een sjabloonoperator declareren vóór het type Foo en vervolgens als vriend de juiste instantie declareren. De syntaxis is een beetje onhandig, maar ziet er als volgt uit;

// 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
}

De bovenstaande code beperkt de vriendschap van de operator tot de overeenkomstige instantie van Foo, d.w.z. de operator<< <int> instantiatie is beperkt tot toegang tot de privé-leden van de instantiatie van Foo<int>.

Alternatieven omvatten;

  • De vriendschap laten uitbreiden naar alle instanties van de sjablonen, als volgt;

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
    
  • Of de implementatie voor de operator<< kan inline worden gedaan binnen de klassendefinitie;

    template <typename T>
    class Foo {
        friend std::ostream& operator<<(std::ostream& os, const Foo& a)
        { /*...*/ }
        // ...
    };
    

Opmerking, wanneer de declaratie van de operator (of functie) alleen in de klasse voorkomt, is de naam niet beschikbaar voor “normale” opzoeking, alleen voor argumentafhankelijke opzoeking, van cppreference;

Een naam die voor het eerst is gedeclareerd in een vrienddeclaratie binnen klasse of klassensjabloon X wordt lid van de binnenste omsluitende naamruimte van X, maar is niet toegankelijk voor opzoeken (behalve argumentafhankelijke opzoeking waarbij X wordt beschouwd) tenzij een overeenkomende declaratie aan de naamruimte bereik is voorzien…

Er is meer informatie over sjabloonvrienden op cppreference en de Veelgestelde vragen over C++.

Codelijst met de bovenstaande technieken.


Als een kanttekening bij het falende codevoorbeeld; g++ waarschuwt hiervoor als volgt

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)


Antwoord 24, autoriteit 2%

Als uw include-paden anders zijn

Linkerfouten kunnen optreden wanneer een headerbestand en de bijbehorende gedeelde bibliotheek (.lib-bestand) niet synchroon lopen. Laat het me uitleggen.

Hoe werken linkers? De linker komt overeen met een functiedeclaratie (aangegeven in de kop) met zijn definitie (in de gedeelde bibliotheek) door hun handtekeningen te vergelijken. U kunt een linkerfout krijgen als de linker geen functiedefinitie vindt die perfect overeenkomt.

Is het mogelijk om nog steeds een linkerfout te krijgen, ook al lijken de declaratie en de definitie overeen te komen? Ja! Ze zien er misschien hetzelfde uit in de broncode, maar het hangt er echt van af wat de compiler ziet. In wezen zou je in een situatie als deze kunnen eindigen:

// header1.h
typedef int Number;
void foo(Number);
// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically

Merk op hoe, hoewel beide functiedeclaraties er identiek uitzien in de broncode, ze echt verschillend zijn volgens de compiler.

Je kunt je afvragen hoe je in zo’n situatie terecht komt? Natuurlijk paden opnemen! Als bij het compileren van de gedeelde bibliotheek het include-pad leidt naar header1.h en u uiteindelijk header2.h in uw eigen programma gebruikt, blijft u achter in uw header benieuwd wat er is gebeurd (bedoelde woordspeling).

Een voorbeeld van hoe dit in de echte wereld kan gebeuren, wordt hieronder uitgelegd.

Verdere uitwerking met een voorbeeld

Ik heb twee projecten: graphics.lib en main.exe. Beide projecten zijn afhankelijk van common_math.h. Stel dat de bibliotheek de volgende functie exporteert:

// graphics.lib    
#include "common_math.h" 
void draw(vec3 p) { ... } // vec3 comes from common_math.h

En dan ga je gang en neem je de bibliotheek op in je eigen project.

// main.exe
#include "other/common_math.h"
#include "graphics.h"
int main() {
    draw(...);
}

Boem! Je krijgt een linkerfout en je hebt geen idee waarom het niet werkt. De reden is dat de gemeenschappelijke bibliotheek verschillende versies van dezelfde gebruikt, waaronder common_math.h (ik heb het hier in het voorbeeld duidelijk gemaakt door een ander pad op te nemen, maar het is misschien niet altijd zo duidelijk. Misschien het include-pad is anders in de compiler-instellingen).

Merk op dat in dit voorbeeld de linker je zou vertellen dat het draw() niet kon vinden, terwijl je in werkelijkheid weet dat het duidelijk wordt geëxporteerd door de bibliotheek. Je zou uren achter je hoofd kunnen krabben en je afvragen wat er is misgegaan. Het punt is dat de linker een andere handtekening ziet omdat de parametertypen iets anders zijn. In het voorbeeld is vec3 een ander type in beide projecten wat betreft de compiler. Dit kan gebeuren omdat ze uit twee enigszins verschillende include-bestanden komen (misschien komen de include-bestanden uit twee verschillende versies van de bibliotheek).

Debuggen van de linker

DUMPBIN is je vriend als je Visual Studio gebruikt. Ik weet zeker dat andere compilers andere vergelijkbare tools hebben.

Het proces gaat als volgt:

  1. Let op de rare verminkte naam in de linkerfout. (bijv. draw@graphics@XYZ).
  2. Dump de geëxporteerde symbolen uit de bibliotheek naar een tekstbestand.
  3. Zoek naar het geëxporteerde symbool van belang en merk op dat de verminkte naam anders is.
  4. Let op waarom de verminkte namen anders zijn geworden. Je zou kunnen zien dat de parametertypes verschillend zijn, ook al zien ze er hetzelfde uit in de broncode.
  5. Reden waarom ze anders zijn. In het bovenstaande voorbeeld zijn ze verschillend vanwege verschillende include-bestanden.

[1] Met project bedoel ik een set bronbestanden die aan elkaar zijn gekoppeld om een ​​bibliotheek of een uitvoerbaar bestand te produceren.

BEWERK 1: Het eerste gedeelte herschreven om het gemakkelijker te begrijpen te maken. Reageer hieronder om me te laten weten als er iets anders moet worden opgelost. Bedankt!


Antwoord 25, autoriteit 2%

Inconsistente UNICODE-definities

Een Windows UNICODE build is gebouwd met TCHAR etc. gedefinieerd als wchar_t etc. Als er niet gebouwd wordt met UNICODE gedefinieerd als build met TCHAR gedefinieerd als char enz. Deze definities van UNICODE en _UNICODE zijn van invloed op alle t” tekenreeksen; LPTSTR, LPCTSTR en hun eland.

Het bouwen van één bibliotheek met UNICODE gedefinieerd en proberen deze te koppelen in een project waar UNICODE niet is gedefinieerd, zal resulteren in linker-fouten omdat er een mismatch in de definitie zal zijn van TCHAR; char versus wchar_t.

De fout bevat meestal een functie een waarde met een char of wchar_t afgeleid type, deze kunnen std::basic_string<> zijn enz. ook. Wanneer u door de betreffende functie in de code bladert, wordt er vaak verwezen naar TCHAR of std::basic_string<TCHAR> enz. Dit is een verklikkersignaal dat de code was oorspronkelijk bedoeld voor zowel een UNICODE- als een Multi-Byte Character (of “smalle”) build.

Om dit te corrigeren, bouwt u alle vereiste bibliotheken en projecten met een consistente definitie van UNICODE (en _UNICODE).

  1. Dit kan met een van beide;

    #define UNICODE
    #define _UNICODE
    
  2. Of in de projectinstellingen;

    Projecteigenschappen > Algemeen > Projectstandaarden > Tekenset

  3. Of op de opdrachtregel;

    /DUNICODE /D_UNICODE
    

Het alternatief is ook van toepassing, als UNICODE niet bedoeld is om te worden gebruikt, zorg er dan voor dat de definities niet zijn ingesteld en/of de instelling met meerdere tekens wordt gebruikt in de projecten en consequent wordt toegepast.

Vergeet ook niet om consistent te zijn tussen de builds “Release” en “Debug”.


Antwoord 26, autoriteit 2%

Reinigen en opnieuw opbouwen

Een “schoon” van de build kan het “dood hout” verwijderen dat mogelijk rondslingert van eerdere builds, mislukte builds, onvolledige builds en andere build-systeemgerelateerde build-problemen.

Over het algemeen zal de IDE of build een vorm van “schone” functie bevatten, maar deze is mogelijk niet correct geconfigureerd (bijvoorbeeld in een handmatige makefile) of kan mislukken (bijvoorbeeld de tussenliggende of resulterende binaire bestanden zijn alleen-lezen).

Zodra het “opschonen” is voltooid, controleert u of het “opschonen” is gelukt en dat alle gegenereerde tussenbestanden (bijv. een geautomatiseerde makefile) met succes zijn verwijderd.

Dit proces kan worden gezien als een laatste redmiddel, maar is vaak een goede eerste stap; vooral als de code met betrekking tot de fout recentelijk is toegevoegd (lokaal of vanuit de bronrepository).


Antwoord 27

Ontbrekende “extern” in const variabele declaraties/definities (alleen C++)

Voor mensen die van C komen, kan het een verrassing zijn dat globale constvariabelen in C++ een interne (of statische) koppeling hebben. In C was dit niet het geval, aangezien alle globale variabelen impliciet extern zijn (d.w.z. wanneer het sleutelwoord static ontbreekt).

Voorbeeld:

// 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 zou zijn om een ​​header-bestand te gebruiken en dit op te nemen in file2.cpp en file1.cpp

extern const int test;
extern int test2;

Als alternatief zou men de variabele const in file1.cpp kunnen declareren met expliciete extern


Antwoord 28

Hoewel dit een vrij oude vraag is met meerdere geaccepteerde antwoorden, wil ik u graag vertellen hoe u een obscure “ongedefinieerde verwijzing naar” -fout kunt oplossen.

Verschillende versies van bibliotheken

Ik gebruikte een alias om te verwijzen naar std::filesystem::path: bestandssysteem staat in de standaardbibliotheek sinds C++17 maar mijn programma moest ook compileren in C+ +14 dus besloot ik een variabele alias te gebruiken:

#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

Stel dat ik drie bestanden heb: main.cpp, file.h, file.cpp:

  • file.h #include’s <experimental::filesystem> en bevat de bovenstaande code
  • file.cpp, de implementatie van file.h, #include’s “file.h
  • main.cpp #include’s <bestandssysteem> en “file.h

Let op de verschillende bibliotheken die worden gebruikt in main.cpp en file.h. Sinds main.cpp #include’d “file.h” na <bestandssysteem>, was de versie van het bestandssysteem die daar werd gebruikt de versie van C++17 . Ik compileerde het programma met de volgende commando’s:

$ g++ -g -std=c++17 -c main.cpp -> compileert main.cpp naar main.o
$ g++ -g -std=c++17 -c file.cpp -> compileert file.cpp en file.h naar file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs -> linkt main.o en file.o

Op deze manier gaf elke functie in file.o en gebruikt in main.o die vereist path_t “undefined reference”-fouten omdat main.o verwees naar std::filesystem::path maar file.o naar std::experimental::filesystem::path.

Oplossing

Om dit op te lossen hoefde ik alleen maar <experimental::filesystem> in file.h naar <filesystem>.


Antwoord 29

Als je linkt naar gedeelde bibliotheken, zorg er dan voor dat de gebruikte symbolen niet verborgen zijn.

Het standaardgedrag van gcc is dat alle symbolen zichtbaar zijn. Als de vertaaleenheden echter zijn gebouwd met de optie -fvisibility=hidden, zijn alleen functies/symbolen gemarkeerd met __attribute__ ((visibility ("default"))) extern in de resulterend gedeeld object.

Je kunt controleren of de symbolen die je zoekt extern zijn door het volgende aan te roepen:

# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL 

de verborgen/lokale symbolen worden weergegeven door nm met een symbooltype in kleine letters, bijvoorbeeld t in plaats van `T voor code-sectie:

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

Je kunt ook nm gebruiken met de optie -C om de namen te ontrafelen (als C++ werd gebruikt).

Vergelijkbaar met Windows-dll’s, zou men openbare functies markeren met een definitie, bijvoorbeeld DLL_PUBLIC gedefinieerd als:

#define DLL_PUBLIC __attribute__ ((visibility ("default")))
DLL_PUBLIC int my_public_function(){
  ...
}

Wat ongeveer overeenkomt met de Windows’/MSVC-versie:

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

Meer informatie over zichtbaarheid is te vinden op de gcc-wiki.


Als een vertaaleenheid wordt gecompileerd met -fvisibility=hidden, hebben de resulterende symbolen nog steeds een externe koppeling (getoond met hoofdlettersymbool door nm) en kunnen ze worden gebruikt voor externe koppeling zonder probleem als de objectbestanden onderdeel worden van statische bibliotheken. De koppeling wordt alleen lokaal wanneer de objectbestanden zijn gekoppeld aan een gedeelde bibliotheek.

Om te zien welke symbolen in een objectbestand verborgen zijn, voer je het volgende uit:

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2

Antwoord 30

Functies of class-methoden worden gedefinieerd in bronbestanden met de inline specificatie.

Een voorbeeld:-

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

gum.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;
}

Als u opgeeft dat gum (vergelijkbaar met foo::bar) in de definitie inline is, dan
de compiler zal gum inline zetten (als hij daarvoor kiest), door:-

  • geen unieke definitie van gum uitzenden, en daarom
  • geen enkel symbool uitzendt waarmee de linker kan verwijzen naar de definitie van gum, en in plaats daarvan
  • alle aanroepen naar gum vervangen door inline kopieën van de gecompileerde hoofdtekst van gum.

Als gevolg hiervan, als u gum inline definieert in een bronbestand gum.cpp, is het
gecompileerd naar een objectbestand gum.o waarin alle aanroepen naar gum inline zijn
en er is geen symbool gedefinieerd waarmee de linker kan verwijzen naar gum. Wanneer je
link gum.o in een programma samen met een ander objectbestand, b.v. main.o
die verwijzingen maken naar een extern symbool gum, kan de linker niet oplossen
die referenties. Dus de koppeling mislukt:

Compileren:

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

Link:

$ 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

Je kunt gum alleen als inline definiëren als de compiler zijn definitie kan zien in elk bronbestand waarin gum mag worden aangeroepen. Dat betekent dat de inline-definitie moet bestaan ​​in een header-bestand dat u opneemt in elk bronbestand
je compileert waarin gum mag worden aangeroepen. Doe een van de volgende twee dingen:

Voeg de definities ook niet toe

Verwijder de inline specificatie uit de bronbestandsdefinitie:

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

Daarmee opnieuw opbouwen:

$ 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

Succes.

Of correct inline

Inline-definities in headerbestanden:

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

gum.h (2)

#pragma once
#include <iostream>
inline void gum() {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

Nu hebben we foo.cpp of gum.cpp niet meer nodig:

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

LEAVE A REPLY

Please enter your comment!
Please enter your name here

five × four =

Other episodes