Che cos'è un riferimento indefinito / errore di simbolo esterno non risolto e come posso risolverlo?


1493

Cosa sono gli errori di riferimento esterni non risolti / i simboli non risolti? Quali sono le cause più comuni e come risolverle / prevenirle?

Sentiti libero di modificare / aggiungere il tuo.


@LuchianGrigore 'sentiti libero di aggiungere una risposta' Ho preferito aggiungere il link pertinente (IMHO) alla tua risposta principale, se lo desideri.
πάντα ῥεῖ

19
Questa domanda è troppo generica per ammettere una risposta utile. Non posso credere al numero di oltre 1000 persone di reputazione che commentano questo, senza abbattere la domanda. Nel frattempo vedo molte domande sensate e molto utili per me che sono chiuse.
Albert van der Horst,

10
@AlbertvanderHorst "Questa domanda è troppo generica per ammettere una risposta utile" Le risposte sottostanti non sono utili?
LF

4
Possono essere utili, così come molte risposte a domande contrassegnate come troppo generiche.
Albert van der Horst,

1
Vorrei vedere l'esempio riproducibile minimo come qualcosa che chiediamo alla maggior parte dei nuovi utenti, onestamente. Non intendo nulla per questo, è solo che non possiamo aspettarci che le persone seguano le regole che non vengono applicate a noi stessi.
Danilo,

Risposte:


850

La compilazione di un programma C ++ avviene in diversi passaggi, come specificato da 2.2 (crediti a Keith Thompson per il riferimento) :

La precedenza tra le regole di sintassi della traduzione è specificata dalle seguenti fasi [vedi nota a piè di pagina] .

  1. I caratteri del file sorgente fisico vengono mappati, in modo definito dall'implementazione, al set di caratteri di base sorgente (introducendo caratteri di nuova riga per gli indicatori di fine riga) se necessario. [Omissis]
  2. Ogni istanza di un carattere barra rovesciata (\) immediatamente seguita da un carattere di nuova riga viene eliminata, unendo le linee di origine fisiche per formare linee di origine logiche. [Omissis]
  3. Il file di origine viene scomposto in token di preelaborazione (2.5) e sequenze di caratteri di spazi bianchi (inclusi i commenti). [Omissis]
  4. Vengono eseguite le direttive di preelaborazione, le invocazioni di macro vengono espanse e vengono eseguite espressioni operatore unarie di _Pragma. [Omissis]
  5. Ciascun membro del set di caratteri di origine in un letterale di carattere o in un letterale di stringa, nonché ogni sequenza di escape e nome di carattere universale in un letterale di carattere o un letterale di stringa non grezzo, viene convertito nel membro corrispondente del set di caratteri di esecuzione; [Omissis]
  6. I token letterali stringa adiacenti sono concatenati.
  7. I caratteri di spazio bianco che separano i token non sono più significativi. Ogni token di preelaborazione viene convertito in token. (2.7). I token risultanti vengono sintatticamente e semanticamente analizzati e tradotti come unità di traduzione. [Omissis]
  8. Le unità di traduzione tradotte e le unità di istanza sono combinate come segue: [SNIP]
  9. Tutti i riferimenti alle entità esterne sono stati risolti. I componenti della libreria sono collegati per soddisfare riferimenti esterni a entità non definite nella traduzione corrente. Tutti questi output del traduttore vengono raccolti in un'immagine del programma che contiene informazioni necessarie per l'esecuzione nel suo ambiente di esecuzione. (enfatizzare il mio)

[nota in calce] Le implementazioni devono comportarsi come se si verificassero queste fasi separate, sebbene in pratica diverse fasi possano essere raggruppate.

Gli errori specificati si verificano durante quest'ultima fase della compilazione, più comunemente indicata come collegamento. Fondamentalmente significa che hai compilato un mucchio di file di implementazione in file oggetto o librerie e ora vuoi farli lavorare insieme.

Di 'che hai definito il simbolo ain a.cpp. Ora, ha b.cpp dichiarato quel simbolo e lo ha usato. Prima del collegamento, presuppone semplicemente che quel simbolo sia stato definito da qualche parte , ma non gli importa ancora dove. La fase di collegamento è responsabile per trovare il simbolo e collegarlo correttamente b.cpp(bene, effettivamente all'oggetto o alla libreria che lo utilizza).

Se stai utilizzando Microsoft Visual Studio, vedrai che i progetti generano .libfile. Questi contengono una tabella di simboli esportati e una tabella di simboli importati. I simboli importati vengono risolti rispetto alle librerie a cui ti colleghi e i simboli esportati vengono forniti per le librerie che lo utilizzano.lib (se presenti).

Meccanismi simili esistono per altri compilatori / piattaforme.

I messaggi di errore più comuni sono error LNK2001, error LNK1120, error LNK2019per Microsoft Visual Studio e undefined reference to symbolName per GCC .

Il codice:

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

genererà i seguenti errori con 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

e errori simili con 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

Le cause più comuni includono:


16
Personalmente, penso che i messaggi di errore del linker MS siano leggibili tanto quanto gli errori GCC. Hanno anche il vantaggio di includere sia i nomi distorti che quelli non confusi per l'esterno irrisolto. Avere il nome alterato può essere utile quando è necessario esaminare direttamente le librerie o i file oggetto per vedere quale potrebbe essere il problema (ad esempio, una mancata corrispondenza della convenzione di chiamata). Inoltre, non sono sicuro di quale versione di MSVC abbia prodotto gli errori qui, ma le versioni più recenti includono il nome (sia alterato che non distorto) della funzione che si riferisce al simbolo esterno non risolto.
Michael Burr,

5
David Drysdale ha scritto un ottimo articolo su come funzionano i linker: Guida per principianti ai linker . Dato l'argomento di questa domanda, ho pensato che potesse rivelarsi utile.
Pressacco,

@TankorSmash Usa gcc? MinGW per essere più precisi.
doug65536,

1
@luchian sarebbe bello se aggiungi il corretto, correggendo gli errori sopra
Phalgun

178

Membri della classe:

Un puro virtualdistruttore ha bisogno di un'implementazione.

Dichiarare un distruttore puro richiede comunque di definirlo (a differenza di una normale funzione):

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

Ciò accade perché i distruttori della classe base vengono chiamati quando l'oggetto viene distrutto implicitamente, quindi è necessaria una definizione.

virtual i metodi devono essere implementati o definiti come puri.

Questo è simile ai non virtualmetodi senza definizione, con l'aggiunta del ragionamento secondo cui la dichiarazione pura genera una tabella fittizia e si potrebbe ottenere l'errore del linker senza usare la funzione:

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

Affinché funzioni, dichiara X::foo()come puro:

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

virtualMembri non di classe

Alcuni membri devono essere definiti anche se non utilizzati esplicitamente:

struct A
{ 
    ~A();
};

Quanto segue genererebbe l'errore:

A a;      //destructor undefined

L'implementazione può essere inline, nella stessa definizione di classe:

struct A
{ 
    ~A() {}
};

o fuori:

A::~A() {}

Se l'implementazione non rientra nella definizione della classe, ma in un'intestazione, i metodi devono essere contrassegnati in modo inlineda impedire una definizione multipla.

Tutti i metodi membro utilizzati devono essere definiti se utilizzati.

Un errore comune è dimenticare di qualificare il nome:

struct A
{
   void foo();
};

void foo() {}

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

La definizione dovrebbe essere

void A::foo() {}

statici membri dei dati devono essere definiti al di fuori della classe in un'unica unità di traduzione :

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

Un inizializzatore può essere fornito per un static constmembro di dati di tipo integrale o di enumerazione all'interno della definizione della classe; tuttavia, l'uso dispari di questo membro richiederà comunque una definizione dell'ambito dello spazio dei nomi come descritto sopra. C ++ 11 consente l'inizializzazione all'interno della classe per tutti static consti membri dei dati.


Ho solo pensato che potresti voler sottolineare che è possibile fare entrambe le cose, e il dtor non è in realtà un'eccezione. (Non è ovvio dalle tue parole a prima vista.)
Deduplicatore,

112

Impossibile eseguire il collegamento con le librerie / i file oggetto appropriati o compilare i file di implementazione

Comunemente, ogni unità di traduzione genererà un file oggetto che contiene le definizioni dei simboli definiti in tale unità di traduzione. Per usare quei simboli, devi collegarti a quei file oggetto.

Sotto gcc dovresti specificare tutti i file oggetto che devono essere collegati insieme nella riga di comando, o compilare insieme i file di implementazione.

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

Il libraryNamequi è solo il nome a nudo della biblioteca, senza aggiunte piattaforma specifici. Quindi, ad esempio, i file della libreria Linux vengono generalmente chiamati libfoo.soma si scrive solo -lfoo. Su Windows potrebbe essere chiamato lo stesso file foo.lib, ma useresti lo stesso argomento. Potrebbe essere necessario aggiungere la directory in cui è possibile trovare quei file utilizzando -L‹directory›. Assicurati di non scrivere uno spazio dopo -lo -L.

Per XCode : aggiungere i percorsi di ricerca dell'intestazione utente -> aggiungere il percorso di ricerca della libreria -> trascinare e rilasciare il riferimento effettivo della libreria nella cartella del progetto.

In MSVS , i file aggiunti a un progetto hanno automaticamente i loro file oggetto collegati tra loro e un libfile verrebbe generato (di uso comune). Per utilizzare i simboli in un progetto separato, è necessario includere i libfile nelle impostazioni del progetto. Questo viene fatto nella sezione Linker delle proprietà del progetto, in Input -> Additional Dependencies. (il percorso del libfile deve essere aggiunto in Linker -> General -> Additional Library Directories) Quando si utilizza una libreria di terze parti fornita con alib file, in genere l'errore non viene generato.

Può anche succedere che si dimentichi di aggiungere il file alla compilation, nel qual caso il file oggetto non verrà generato. In gcc dovresti aggiungere i file alla riga di comando. In MSVS l' aggiunta del file al progetto lo farà compilare automaticamente (anche se i file possono, manualmente, essere esclusi individualmente dalla build).

Nella programmazione di Windows, il segnale rivelatore che non è stato collegato a una libreria necessaria è che il nome del simbolo non risolto inizia con __imp_. Cerca il nome della funzione nella documentazione e dovrebbe indicare quale libreria devi usare. Ad esempio, MSDN inserisce le informazioni in una casella nella parte inferiore di ciascuna funzione in una sezione denominata "Libreria".


1
Sarebbe bello se tu potessi coprire esplicitamente l'errore comune gcc main.cinvece di gcc main.c other.c (che spesso fanno i principianti prima che i loro progetti diventino così grandi da creare file .o).
MM

101

Dichiarato ma non definito una variabile o una funzione.

Una tipica dichiarazione di variabile è

extern int x;

Poiché questa è solo una dichiarazione, è necessaria un'unica definizione . Una definizione corrispondente sarebbe:

int x;

Ad esempio, quanto segue genererebbe un errore:

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

Osservazioni simili si applicano alle funzioni. Dichiarare una funzione senza definirla porta all'errore:

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

Fare attenzione che la funzione implementata corrisponda esattamente a quella dichiarata. Ad esempio, potresti avere qualificatori CV non corrispondenti:

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)

Altri esempi di discrepanze includono

  • Funzione / variabile dichiarata in uno spazio dei nomi, definita in un altro.
  • Funzione / variabile dichiarata come membro della classe, definita come globale (o viceversa).
  • Il tipo di ritorno della funzione, il numero e il tipo di parametro e la convenzione di chiamata non concordano esattamente.

Il messaggio di errore del compilatore fornisce spesso la dichiarazione completa della variabile o della funzione dichiarata ma mai definita. Confrontalo da vicino con la definizione che hai fornito. Assicurati che ogni dettaglio corrisponda.


In VS, anche i file cpp corrispondenti a quelli nell'intestazione #includesnon aggiunti alla directory di origine rientrano nella categoria delle definizioni mancanti.
Laurie Stearn,

86

L'ordine in cui sono specificate le librerie collegate interdipendenti è errato.

L'ordine in cui le librerie sono collegate È importante se le librerie dipendono l'una dall'altra. In generale, se la libreria Adipende dalla libreria B, libA DEVE apparire prima libBnei flag del linker.

Per esempio:

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

Crea le librerie:

$ 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

Compilare:

$ 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

Quindi, per ripetere ancora, l'ordine È IMPORTANTE !


Il fatto curioso è che nel mio caso avevo un file oggetto che dipende da una libreria condivisa. Ho dovuto modificare il Makefile e mettere la libreria DOPO l'oggetto con gcc 4.8.4 su Debian. Su Centos 6.5 con gcc 4.4 il Makefile ha funzionato senza problemi.
Marco Sulla

74

che cos'è un "riferimento indefinito / simbolo esterno non risolto"

Proverò a spiegare cos'è un "riferimento indefinito / simbolo esterno non risolto".

nota: io uso g ++ e Linux e tutti gli esempi sono per questo

Ad esempio abbiamo del codice

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

e

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

Crea file oggetto

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

Dopo la fase di assemblaggio abbiamo un file oggetto, che contiene tutti i simboli da esportare. Guarda i simboli

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

Ho rifiutato alcune righe dall'output, perché non contano

Quindi, vediamo seguire i simboli per esportare.

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

src2.cpp non esporta nulla e non abbiamo visto i suoi simboli

Collega i nostri file oggetto

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

ed eseguirlo

$ ./prog
123

Linker vede i simboli esportati e li collega. Ora proviamo a rimuovere il commento dalle righe in src2.cpp come qui

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

e ricostruire un file oggetto

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

OK (nessun errore), poiché costruiamo solo file oggetto, il collegamento non è stato ancora eseguito. Prova a collegare

$ 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

È successo perché local_var_name è statico, ovvero non è visibile per altri moduli. Adesso più profondamente. Ottieni l'output della fase di traduzione

$ 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

Quindi, abbiamo visto che non esiste un'etichetta per local_var_name, ecco perché il linker non l'ha trovata. Ma siamo hacker :) e possiamo risolverlo. Apri src1.s nel tuo editor di testo e modifica

.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4

per

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

cioè dovresti avere come sotto

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

abbiamo modificato la visibilità di local_var_name e impostato il suo valore su 456789. Prova a creare un file oggetto da esso

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

ok, vedi l'output (simboli)

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

ora local_var_name ha Associa GLOBAL (era LOCAL)

collegamento

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

ed eseguirlo

$ ./prog 
123456789

ok, lo hackiamo :)

Di conseguenza, un "errore di riferimento esterno non risolto / simbolo esterno non risolto" si verifica quando il linker non riesce a trovare simboli globali nei file oggetto.


71

I simboli sono stati definiti in un programma C e utilizzati nel codice C ++.

La funzione (o variabile) è void foo()stata definita in un programma C e si tenta di utilizzarla in un programma C ++:

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

Il linker C ++ si aspetta che i nomi vengano alterati, quindi devi dichiarare la funzione come:

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

Equivalentemente, invece di essere definita in un programma C, la funzione (o variabile) è void foo()stata definita in C ++ ma con collegamento C:

extern "C" void foo();

e si tenta di utilizzarlo in un programma C ++ con collegamento C ++.

Se un'intera libreria è inclusa in un file di intestazione (ed è stata compilata come codice C); l'inclusione dovrà essere la seguente;

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

5
O al contrario, se sviluppi una libreria C, una buona regola è quella di proteggere i file di intestazione circondando tutte le dichiarazioni esportate con #ifdef __cplusplus [\n] extern"C" { [\n] #endife #ifdef __cplusplus [\n] } [\n] #endif( [\n]essendo un vero ritorno a capo ma non posso scriverlo correttamente nei commenti).
Bentoy13,

Come nel commento sopra, la sezione "Creazione di intestazioni in lingua mista" qui ha aiutato: oracle.com/technetwork/articles/servers-storage-dev/…
zanbri

Mi ha davvero aiutato! Questo è stato il mio caso quando ho cercato questa domanda
knightofhorizon,

Questo può succedere anche se si include il file di intestazione ordinaria C ++ per caso circondato da extern C : extern "C" { #include <myCppHeader.h> }.
ComFreek

68

Se tutto il resto fallisce, ricompilare.

Recentemente sono stato in grado di eliminare un errore esterno non risolto in Visual Studio 2012 semplicemente ricompilando il file in questione. Quando ho ricostruito, l'errore è andato via.

Questo di solito accade quando due (o più) librerie hanno una dipendenza ciclica. La libreria A tenta di utilizzare i simboli in B.lib e la libreria B tenta di utilizzare i simboli di A.lib. Nessuno dei due esiste per cominciare. Quando si tenta di compilare A, il passaggio del collegamento fallirà perché non riesce a trovare B.lib. Verrà generato A.lib, ma nessuna dll. Quindi compilare B, che avrà esito positivo e generare B.lib. La ricompilazione di A ora funzionerà perché ora è stato trovato B.lib.


1
Corretto: questo accade quando le librerie hanno una dipendenza ciclica.
Luchian Grigore,

1
Ho ampliato la tua risposta e mi sono collegato a quello principale. Grazie.
Luchian Grigore,

57

Importazione / esportazione errata di metodi / classi tra moduli / dll (specifici del compilatore).

MSVS richiede di specificare quali simboli esportare e importare usando __declspec(dllexport)e __declspec(dllimport).

Questa doppia funzionalità si ottiene in genere tramite l'uso di una macro:

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

La macro THIS_MODULEverrebbe definita solo nel modulo che esporta la funzione. In questo modo, la dichiarazione:

DLLIMPEXP void foo();

si espande a

__declspec(dllexport) void foo();

e dice al compilatore di esportare la funzione, poiché il modulo corrente contiene la sua definizione. Quando si include la dichiarazione in un modulo diverso, si espanderà a

__declspec(dllimport) void foo();

e dice al compilatore che la definizione si trova in una delle librerie a cui sei collegato (vedi anche 1) ).

Puoi similare le classi di importazione / esportazione:

class DLLIMPEXP X
{
};

2
Per essere completa, questa risposta dovrebbe menzionare i file di GCC visibilitye Windows .def, poiché questi influenzano anche il nome e la presenza del simbolo.
rubenvb,

@rubenvb Non uso .deffile da anni. Sentiti libero di aggiungere una risposta o modificarla.
Luchian Grigore,

57

Questo è uno dei messaggi di errore più confusi che ogni programmatore VC ++ ha visto più volte. Facciamo prima le cose chiarezza.

A. Che cos'è il simbolo? In breve, un simbolo è un nome. Può essere un nome di variabile, un nome di funzione, un nome di classe, un nome di battitura a macchina o qualsiasi cosa tranne quei nomi e segni che appartengono al linguaggio C ++. È definito dall'utente o introdotto da una libreria di dipendenze (un'altra definita dall'utente).

B. Che cos'è esterno? In VC ++, ogni file sorgente (.cpp, .c, ecc.) È considerato come un'unità di traduzione, il compilatore compila un'unità alla volta e genera un file oggetto (.obj) per l'unità di traduzione corrente. (Si noti che tutti i file di intestazione inclusi in questo file sorgente verranno preelaborati e saranno considerati come parte di questa unità di traduzione) Tutto all'interno di un'unità di traduzione è considerato interno, tutto il resto è considerato esterno. In C ++, puoi fare riferimento a un simbolo esterno usando parole chiave come extern,__declspec (dllimport) e così via.

C. Che cos'è la "risoluzione"? Risolvi è un termine che collega il tempo. Nel tempo di collegamento, il linker tenta di trovare la definizione esterna per ogni simbolo nei file oggetto che non riesce a trovare internamente la sua definizione. L'ambito di questo processo di ricerca include:

  • Tutti i file oggetto generati in fase di compilazione
  • Tutte le librerie (.lib) specificate in modo esplicito o implicito come dipendenze aggiuntive di questa applicazione di costruzione.

Questo processo di ricerca si chiama risoluzione.

D. Infine, perché simbolo esterno irrisolto? Se il linker non riesce a trovare la definizione esterna per un simbolo che non ha una definizione internamente, segnala un errore Simbolo esterno non risolto.

E. Possibili cause di LNK2019 : errore del simbolo esterno non risolto. Sappiamo già che questo errore è dovuto al fatto che il linker non è riuscito a trovare la definizione di simboli esterni, le possibili cause possono essere ordinate come:

  1. La definizione esiste

Ad esempio, se abbiamo una funzione chiamata foo definita in a.cpp:

int foo()
{
    return 0;
}

In b.cpp vogliamo chiamare la funzione foo, quindi aggiungiamo

void foo();

per dichiarare la funzione foo () e chiamarla in un altro corpo di funzione, dire bar():

void bar()
{
    foo();
}

Ora, quando si crea questo codice, viene visualizzato un errore LNK2019 che si lamenta che foo è un simbolo non risolto. In questo caso, sappiamo che foo () ha la sua definizione in a.cpp, ma diverso da quello che stiamo chiamando (diverso valore di ritorno). Questo è il caso in cui esiste una definizione.

  1. La definizione non esiste

Se vogliamo chiamare alcune funzioni in una libreria, ma la libreria di importazione non viene aggiunta all'elenco delle dipendenze aggiuntivo (impostato da Project | Properties | Configuration Properties | Linker | Input | Additional Dependency:) dell'impostazione del progetto. Ora il linker segnalerà un LNK2019 poiché la definizione non esiste nell'ambito di ricerca corrente.


1
Grazie per la spiegazione sistematica. Ho potuto capire le altre risposte qui dopo aver letto questo.
displayName

56

Implementazioni dei modelli non visibili.

I modelli non specializzati devono avere le loro definizioni visibili a tutte le unità di traduzione che li utilizzano. Ciò significa che non è possibile separare la definizione di un modello in un file di implementazione. Se è necessario separare l'implementazione, la soluzione alternativa abituale è disporre di un implfile che si include alla fine dell'intestazione che dichiara il modello. Una situazione comune è:

template<class T>
struct X
{
    void foo();
};

int main()
{
    X<int> x;
    x.foo();
}

//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}

Per risolvere questo problema, è necessario spostare la definizione di X::foo nel file di intestazione o in un punto visibile nell'unità di traduzione che lo utilizza.

I modelli specializzati possono essere implementati in un file di implementazione e l'implementazione non deve essere visibile, ma la specializzazione deve essere precedentemente dichiarata.

Per ulteriori spiegazioni e un'altra possibile soluzione (istanza esplicita) vedere questa domanda e risposta .


1
Ulteriori informazioni per "i modelli devono essere definiti nell'intestazione" può essere trovato in stackoverflow.com/questions/495021
PlasmaHH

41

riferimento indefinito WinMain@16o riferimento al punto di ingresso "insolito" similemain() (in particolare per).

Potresti aver perso la scelta del giusto tipo di progetto con il tuo IDE effettivo. L'IDE potrebbe voler associare, ad esempio, i progetti di applicazioni Windows a tale funzione del punto di ingresso (come specificato nel riferimento mancante sopra), invece della int main(int argc, char** argv);firma comunemente usata .

Se il tuo IDE supporta i progetti Plain Console potresti voler scegliere questo tipo di progetto, anziché un progetto di applicazione Windows.


Ecco case1 e case2 gestiti in modo più dettagliato da un problema del mondo reale .


2
Non posso fare a meno di sottolineare questa domanda e il fatto che ciò è più spesso causato dal fatto di non avere alcuna funzione principale rispetto al non avere WinMain. I programmi C ++ validi richiedono a main.
chris

36

Inoltre, se stai utilizzando librerie di terze parti assicurati di disporre dei binari a 32/64 bit corretti


34

Microsoft offre un #pragmariferimento alla libreria corretta al momento del collegamento;

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

Oltre al percorso della libreria inclusa la directory della libreria, questo dovrebbe essere il nome completo della libreria.


34

Il pacchetto NuGet di Visual Studio deve essere aggiornato per la nuova versione del set di strumenti

Ho appena avuto questo problema nel tentativo di collegare libpng a Visual Studio 2013. Il problema è che il file del pacchetto aveva solo librerie per Visual Studio 2010 e 2012.

La soluzione corretta è sperare che lo sviluppatore rilasci un pacchetto aggiornato e quindi esegua l'aggiornamento, ma ha funzionato per me hackerando un'impostazione aggiuntiva per VS2013, indicando i file della libreria VS2012.

Ho modificato il pacchetto (nella packagescartella all'interno della directory della soluzione) trovando packagename\build\native\packagename.targetse all'interno di quel file, copiando tutte le v110sezioni. Ho cambiato il v110per nei campi di condizione solo facendo molta attenzione a lasciare i sentieri filename tutti come . Ciò ha semplicemente consentito a Visual Studio 2013 di collegarsi alle librerie per il 2012 e, in questo caso, ha funzionato.v120v110


1
Questo sembra eccessivamente specifico - forse un nuovo thread sarebbe un posto migliore per questa risposta.
Luchian Grigore,

3
@LuchianGrigore: volevo pubblicare qui poiché quella domanda era esattamente questo problema, ma era contrassegnata come duplicata di questa domanda, quindi non ho potuto rispondere lì. Quindi ho pubblicato la mia risposta qui.
Malvineous,

Questa domanda ha già una risposta accettata. È contrassegnato come duplicato perché la causa generale è elencata sopra. Cosa accadrebbe se avessimo una risposta qui per ogni problema con una libreria che non è inclusa?
Luchian Grigore,

6
@LuchianGrigore: questo problema non è specifico di una libreria, ma interessa tutte le librerie che utilizzano il sistema di gestione dei pacchetti di Visual Studio. Mi è capitato di trovare l'altra domanda perché entrambi abbiamo avuto problemi con libpng. Ho anche avuto lo stesso problema (con la stessa soluzione) per libxml2, libiconv e glew. Questa domanda riguarda un problema con il sistema di gestione dei pacchetti di Visual Studio e la mia risposta spiega il motivo e fornisce una soluzione alternativa. Qualcuno ha appena visto "esterno non risolto" e ha ipotizzato che si trattasse di un problema di linker standard quando in realtà si tratta di un problema di gestione dei pacchetti.
Malvineous,

34

Supponiamo di avere un grande progetto scritto in c ++ che ha un migliaio di file .cpp e un migliaio di file .h. Supponiamo che il progetto dipenda anche da dieci librerie statiche. Diciamo che siamo su Windows e costruiamo il nostro progetto in Visual Studio 20xx. Quando premi Ctrl + F7 Visual Studio per iniziare a compilare l'intera soluzione (supponi di avere un solo progetto nella soluzione)

Qual è il significato della compilazione?

  • Ricerca di Visual Studio nel file .vcxproj e inizia a compilare ogni file con estensione .cpp. L'ordine di compilazione non è definito, quindi non si deve presumere che il file main.cpp sia compilato per primo
  • Se i file .cpp dipendono da file .h aggiuntivi al fine di trovare simboli che possono o non possono essere definiti nel file .cpp
  • Se esiste un file .cpp in cui il compilatore non è riuscito a trovare un simbolo, un errore di tempo del compilatore genera il messaggio Simbolo x non è stato trovato
  • Per ogni file con estensione .cpp viene generato un file oggetto .o e anche Visual Studio scrive l'output in un file denominato ProjectName.Cpp.Clean.txt che contiene tutti i file oggetto che devono essere elaborati dal linker.

Il secondo passo della compilazione viene eseguito da Linker. Linker dovrebbe unire tutto il file oggetto e costruire infine l'output (che può essere un eseguibile o una libreria)

Passaggi nel collegamento di un progetto

  • Analizza tutti i file oggetto e trova la definizione che è stata dichiarata solo nelle intestazioni (es: il codice di un metodo di una classe come menzionato nelle risposte precedenti, o evento l'inizializzazione di una variabile statica che è membro all'interno di una classe)
  • Se non è possibile trovare un simbolo nei file oggetto, viene cercato anche in Librerie aggiuntive. Per aggiungere una nuova libreria a un progetto Proprietà di configurazione -> Directory VC ++ -> Directory librerie e qui hai specificato una cartella aggiuntiva per la ricerca di librerie e proprietà di configurazione -> Linker -> Input per specificare il nome della libreria. -Se il Linker non riesce a trovare il simbolo che scrivi in ​​un .cpp, genera un errore di tempo del linker che potrebbe sembrare error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

Osservazione

  1. Una volta che il Linker trova un simbolo, non lo cerca in altre librerie
  2. L'ordine di collegamento delle librerie è importante .
  3. Se Linker trova un simbolo esterno in una libreria statica, include il simbolo nell'output del progetto.Tuttavia, se la libreria è condivisa (dinamica), non include il codice (simboli) nell'output, ma arresti anomali di runtime potrebbero si verificano

Come risolvere questo tipo di errore

Errore tempo compilatore:

  • Assicurati di scrivere il tuo progetto c ++ sintatticamente corretto.

Errore tempo linker

  • Definisci tutti i simboli che dichiari nei file di intestazione
  • Uso #pragma once per consentire al compilatore di non includere un'intestazione se era già incluso nell'attuale .cpp che viene compilato
  • Assicurati che la tua libreria esterna non contenga simboli che potrebbero entrare in conflitto con altri simboli definiti nei file di intestazione
  • Quando si utilizza il modello per assicurarsi di includere la definizione di ciascuna funzione del modello nel file di intestazione per consentire al compilatore di generare il codice appropriato per eventuali istanze.

La tua risposta non è specifica per Visual Studio? La domanda non specifica alcun tool IDE / compilatore, quindi rende la tua risposta inutile per la parte non-visual-studio.
Victor Polevoy,

Hai ragione . Ma ogni processo IDE di compilazione / collegamento viene eseguito in modo leggermente diverso, ma i file vengono elaborati esattamente allo stesso modo (anche g ++ fa la stessa cosa quando analizza i flag ..)

Il problema in realtà non riguarda l'IDE ma una risposta per i problemi di collegamento. I problemi di collegamento non sono correlati all'IDE ma al compilatore e al processo di compilazione.
Victor Polevoy,

Sì, ma il processo di compilazione / collegamento viene eseguito in g ++ / Visual Studio (compilatore fornito da Microsoft per VS) / Eclipse / Net Beans allo stesso modo

29

Un bug nel compilatore / IDE

Recentemente ho avuto questo problema e si è scoperto che era un bug in Visual Studio Express 2013 . Ho dovuto rimuovere un file sorgente dal progetto e aggiungerlo di nuovo per superare il bug.

Passaggi da provare se ritieni che potrebbe essere un bug nel compilatore / IDE:

  • Pulisci il progetto (alcuni IDE hanno un'opzione per farlo, puoi anche farlo manualmente cancellando i file oggetto)
  • Prova a iniziare un nuovo progetto, copiando tutto il codice sorgente da quello originale.

5
Credere che i tuoi strumenti siano rotti probabilmente ti allontanerà dalla vera causa. È molto più probabile che tu abbia commesso un errore che un compilatore abbia causato il tuo problema. La pulizia della soluzione o la ricostruzione della configurazione della build possono correggere errori di compilazione, ma ciò non significa che nel compilatore sia presente un bug. Il link "scoperto che era un bug" non è confermato da Microsoft e non è riproducibile.
JDiMatteo,

4
@JDiMatteo Ci sono 21 risposte a questa domanda e quindi una quantità significativa di risposte non sarà una soluzione "probabile". Se si eliminano tutte le risposte al di sotto della soglia di probabilità, questa pagina diventa effettivamente inutile poiché la maggior parte dei casi comuni viene individuata facilmente comunque.
Developerbmw,

27

Utilizzare il linker per aiutare a diagnosticare l'errore

I linker più moderni includono un'opzione dettagliata che viene stampata a vari livelli;

  • Richiamo del collegamento (riga di comando),
  • Dati su quali librerie sono incluse nella fase di collegamento,
  • La posizione delle biblioteche,
  • Percorsi di ricerca utilizzati.

Per gcc e clang; in genere si aggiunge -v -Wl,--verboseo -v -Wl,-valla riga di comando. Maggiori dettagli possono essere trovati qui;

Per MSVC, /VERBOSE(in particolare /VERBOSE:LIB) viene aggiunto alla riga di comando del collegamento.


26

Il file .lib collegato è associato a un dll

Ho avuto lo stesso problema. Supponiamo di avere progetti MyProject e TestProject. Avevo effettivamente collegato il file lib per MyProject a TestProject. Tuttavia, questo file lib è stato prodotto quando è stata creata la DLL per MyProject. Inoltre, non contenevo il codice sorgente per tutti i metodi in MyProject, ma solo l'accesso ai punti di ingresso della DLL.

Per risolvere il problema, ho creato MyProject come LIB e ho collegato TestProject a questo file .lib (copio incollando il file .lib generato nella cartella TestProject). Posso quindi creare nuovamente MyProject come DLL. Si sta compilando poiché la lib a cui è collegato TestProject contiene codice per tutti i metodi nelle classi in MyProject.


25

Dal momento che le persone sembrano essere indirizzate a questa domanda quando si tratta di errori del linker, aggiungerò qui.

Una possibile ragione per errori del linker con GCC 5.2.0 è che una nuova libreria ABI libstdc ++ è ora selezionata per impostazione predefinita.

Se ricevi errori del linker su riferimenti indefiniti a simboli che coinvolgono tipi nello spazio dei nomi std :: __ cxx11 o nel tag [abi: cxx11], probabilmente indica che stai provando a collegare insieme i file oggetto compilati con valori diversi per _GLIBCXX_USE_CXX11_ABI macro. Questo accade di solito quando si collega a una libreria di terze parti compilata con una versione precedente di GCC. Se non è possibile ricostruire la libreria di terze parti con la nuova ABI, sarà necessario ricompilare il codice con la vecchia ABI.

Quindi, se si verificano improvvisamente errori del linker quando si passa a un GCC dopo la 5.1.0, sarebbe una cosa da verificare.


20

Un wrapper per GNU ld che non supporta gli script di linker

Alcuni file .so sono in realtà script GNU ld linker , ad esempio il file libtbb.so è un file di testo ASCII con questo contenuto:

INPUT (libtbb.so.2)

Alcune build più complesse potrebbero non supportare questo. Ad esempio, se includi -v nelle opzioni del compilatore, puoi vedere che il mwdip wrapper mainwin gcc scarta i file dei comandi dello script del linker nell'elenco dettagliato delle librerie di output in cui collegarti. Un semplice accorgimento è sostituire il comando di input dello script del linker file con una copia del file invece (o un collegamento simbolico), ad es

cp libtbb.so.2 libtbb.so

Oppure potresti sostituire l'argomento -l con il percorso completo di .so, ad esempio invece di -ltbbfarlo/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2


20

Il collegamento utilizza le librerie prima dei file oggetto che si riferiscono ad esse

  • Stai tentando di compilare e collegare il tuo programma con la toolchain GCC.
  • Il collegamento specifica tutte le librerie e i percorsi di ricerca delle librerie necessari
  • Se libfoodipende da libbar, allora il tuo collegamento mette correttamente libfooprima libbar.
  • Il tuo legame fallisce con undefined reference to qualcosa di errori.
  • Ma tutte le cose indefinite sono dichiarate nei file di intestazione che hai #includee sono di fatto definite nelle librerie che stai collegando.

Esempi sono in C. Potrebbero anche essere C ++

Un esempio minimo che coinvolge una libreria statica creata da te

my_lib.c

#include "my_lib.h"
#include <stdio.h>

void hw(void)
{
    puts("Hello World");
}

my_lib.h

#ifndef MY_LIB_H
#define MT_LIB_H

extern void hw(void);

#endif

eg1.c

#include <my_lib.h>

int main()
{
    hw();
    return 0;
}

Costruisci la tua libreria statica:

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

Compili il tuo programma:

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

Si tenta di collegarlo libmy_lib.ae fallire:

$ 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

Lo stesso risultato se compili e colleghi in un solo passaggio, come:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

Un esempio minimo che coinvolge una libreria di sistema condivisa, la libreria di compressione libz

eg2.c

#include <zlib.h>
#include <stdio.h>

int main()
{
    printf("%s\n",zlibVersion());
    return 0;
}

Compila il tuo programma:

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

Prova a collegare il tuo programma libze fallisci:

$ 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

Lo stesso se si compila e si collega in una volta sola:

$ 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

E una variazione sull'esempio 2 che coinvolge pkg-config:

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

Cosa stai sbagliando?

Nella sequenza di file oggetto e librerie che si desidera collegare per creare il programma, si posizionano le librerie prima dei file oggetto che si riferiscono ad essi. È necessario posizionare le librerie dopo i file oggetto che si riferiscono ad esse.

Collegare correttamente l'esempio 1:

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

Successo:

$ ./eg1 
Hello World

Collegare correttamente l'esempio 2:

$ gcc -o eg2 eg2.o -lz

Successo:

$ ./eg2 
1.2.8

Collegare pkg-configcorrettamente la variante di esempio 2 :

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

La spiegazione

La lettura è facoltativa da qui in poi .

Per impostazione predefinita, un comando di collegamento generato da GCC, sulla tua distribuzione, utilizza i file nel collegamento da sinistra a destra nella sequenza della riga di comando. Quando rileva che un file fa riferimento a qualcosa e non contiene una definizione per esso, cercherà una definizione nei file più a destra. Se alla fine trova una definizione, il riferimento viene risolto. Se qualche riferimento rimane irrisolto alla fine, il collegamento fallisce: il linker non esegue la ricerca all'indietro.

Innanzitutto, esempio 1 , con libreria staticamy_lib.a

Una libreria statica è un archivio indicizzato di file oggetto. Quando il linker trova -lmy_libnella sequenza di linkage e scopre che si riferisce alla libreria statica ./libmy_lib.a, vuole sapere se il tuo programma ha bisogno di uno qualsiasi dei file oggetto libmy_lib.a.

C'è solo un file oggetto libmy_lib.a, vale a dire my_lib.o, e c'è solo una cosa definita in my_lib.o, vale a dire la funzione hw.

Il linker deciderà che il tuo programma ha bisogno my_lib.ose e solo se sa già che il tuo programma fa riferimento hw, in uno o più dei file oggetto che ha già aggiunto al programma e che nessuno dei file oggetto che ha già aggiunto contiene un definizione per hw.

Se questo è vero, il linker estrarrà una copia della my_lib.olibreria e la aggiungerà al tuo programma. Quindi, il tuo programma contiene una definizione per hw, quindi i suoi riferimenti hwsono stati risolti .

Quando provi a collegare il programma come:

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

il linker non ha aggiunto eg1.o al programma quando vede -lmy_lib. Perché a quel punto, non ha visto eg1.o. Il vostro programma non consente ancora tutti i riferimenti a hw: essa non consente ancora i riferimenti a tutti , perché tutti i riferimenti Rende sono ineg1.o .

Quindi il linker non si aggiunge my_lib.oal programma e non serve più a libmy_lib.a.

Successivamente, trova eg1.oe lo aggiunge come programma. Un file oggetto nella sequenza di collegamenti viene sempre aggiunto al programma. Ora, il programma fa un riferimento hwe non contiene una definizione di hw; ma non è rimasto nulla nella sequenza di collegamenti che potrebbe fornire la definizione mancante. Il riferimento a hwfinisce irrisolto e il collegamento non riesce.

Secondo, esempio 2 , con libreria condivisalibz

Una libreria condivisa non è un archivio di file oggetto o simili. È molto più simile a un programma che non ha una mainfunzione e invece espone più altri simboli che definisce, in modo che altri programmi possano usarli in fase di esecuzione.

Molti Linux distro oggi configurare il loro GCC toolchain in modo che i suoi driver di lingua ( gcc, g++, gfortranecc) istruire il linker di sistema ( ld) per collegare le librerie condivise su una alle necessità di base. Hai una di quelle distro.

Ciò significa che quando il linker trova -lznella sequenza di linkage e scopre che si riferisce alla libreria condivisa (diciamo) /usr/lib/x86_64-linux-gnu/libz.so, vuole sapere se eventuali riferimenti aggiunti al programma che non sono stati ancora definiti hanno definizioni che sono esportato dalibz

Se questo è vero, il linker non copierà alcun pezzo libze non lo aggiungerà al tuo programma; invece, aggiusterà semplicemente il codice del tuo programma in modo che: -

  • In fase di esecuzione, il caricatore del programma di sistema caricherà una copia libznello stesso processo del programma ogni volta che carica una copia del programma, per eseguirlo.

  • In fase di esecuzione, ogni volta che il programma fa riferimento a qualcosa in cui è definito libz, tale riferimento utilizza la definizione esportata dalla copia di libznello stesso processo.

Il tuo programma vuole fare riferimento a una sola cosa che ha una definizione esportata libz, ovvero la funzione zlibVersion, a cui si fa riferimento una sola volta, in eg2.c. Se il linker aggiunge quel riferimento al programma e quindi trova la definizione esportata da libz, il riferimento viene risolto

Ma quando provi a collegare il programma come:

gcc -o eg2 -lz eg2.o

l'ordine degli eventi è sbagliato nel proprio allo stesso modo come con l'esempio 1. Nel momento in cui i reperti linker -lz, non ci sono nessun riferimenti a qualsiasi cosa nel programma: sono tutti in eg2.o, che non è ancora stato visto. Quindi il linker decide che non serve a niente libz. Quando raggiunge eg2.o, lo aggiunge al programma e quindi ha un riferimento indefinito a zlibVersion, la sequenza di collegamento è terminata; tale riferimento è irrisolto e il collegamento non riesce.

Infine, la pkg-configvariazione dell'esempio 2 ha una spiegazione ora ovvia. Dopo l'espansione della shell:

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

diventa:

gcc -o eg2 -lz eg2.o

che è di nuovo solo l'esempio 2.

Posso riprodurre il problema nell'esempio 1, ma non nell'esempio 2

Il collegamento:

gcc -o eg2 -lz eg2.o

funziona bene per te!

(Oppure: quel collegamento ha funzionato bene su, diciamo, Fedora 23, ma fallisce su Ubuntu 16.04)

Questo perché la distro su cui funziona il collegamento è una di quelle che non configura la sua toolchain GCC per collegare le librerie condivise secondo necessità .

In passato, era normale per i sistemi unix-like collegare librerie statiche e condivise secondo regole diverse. Le librerie statiche in una sequenza di collegamenti sono state collegate in base alle necessità come spiegato nell'esempio 1, ma le librerie condivise sono state collegate incondizionatamente.

Questo comportamento è economico durante il collegamento perché il linker non deve ponderare se una libreria condivisa è necessaria al programma: se è una libreria condivisa, collegala. E la maggior parte delle librerie nella maggior parte dei collegamenti sono librerie condivise. Ma ci sono anche degli svantaggi: -

  • È antieconomico in fase di esecuzione , perché può causare il caricamento di librerie condivise insieme a un programma anche se non ne hanno bisogno.

  • Le diverse regole di collegamento per le librerie statiche e condivise possono essere fonte di confusione per i programmatori inesperti, che potrebbero non sapere se -lfoonel loro collegamento si risolveranno /some/where/libfoo.ao /some/where/libfoo.sono e potrebbero non capire comunque la differenza tra librerie condivise e statiche.

Questo compromesso ha portato alla situazione scismatica oggi. Alcune distribuzioni hanno modificato le regole di collegamento GCC per le librerie condivise in modo che il principio secondo necessità si applichi a tutte le librerie. Alcune distro si sono bloccate alla vecchia maniera.

Perché riscontro ancora questo problema anche se compilo e collegamento contemporaneamente?

Se lo faccio solo:

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

sicuramente gcc deve eg1.cprima compilare e poi collegare il file oggetto risultante con libmy_lib.a. Quindi, come può non sapere che il file oggetto è necessario quando sta facendo il collegamento?

Perché la compilazione e il collegamento con un singolo comando non cambia l'ordine della sequenza di collegamenti.

Quando esegui il comando sopra, gccscopri che vuoi compilation + linkage. Così dietro le quinte, genera un comando di compilazione, e lo esegue, quindi genera un comando di collegamento, e lo esegue, come se si avesse eseguito i due comandi:

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

Quindi il collegamento non riesce proprio come si fa se non esegue i due comandi. L'unica differenza che noti nell'errore è che gcc ha generato un file oggetto temporaneo nel caso compile + link, perché non gli stai dicendo di usarlo eg1.o. Vediamo:

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

invece di:

eg1.o: In function `main':

Guarda anche

L'ordine in cui sono specificate le librerie collegate interdipendenti è errato

Mettere le librerie interdipendenti nell'ordine sbagliato è solo un modo in cui è possibile ottenere file che richiedono definizioni di cose che verranno dopo nel collegamento rispetto ai file che forniscono le definizioni. Mettere le librerie davanti ai file oggetto che si riferiscono ad esse è un altro modo di fare lo stesso errore.


18

Modelli di amicizia ...

Dato lo snippet di codice di un tipo di modello con un operatore (o funzione) amico;

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

Il operator<<che viene dichiarato come una funzione non template. Per ogni tipo Tutilizzato con Foo, è necessario che non sia presente un modello operator<<. Ad esempio, se esiste un tipo Foo<int>dichiarato, allora deve esserci un'implementazione dell'operatore come segue;

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

Poiché non è implementato, il linker non riesce a trovarlo e genera l'errore.

Per correggere ciò, è possibile dichiarare un operatore modello prima del Footipo e quindi dichiarare come amico l'istanza appropriata. La sintassi è un po 'imbarazzante, ma è simile alla seguente;

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

Il codice precedente limita l'amicizia dell'operatore alla corrispondente istanza di Foo, ovvero l' operator<< <int>istanza è limitata per accedere ai membri privati ​​dell'istanza diFoo<int> .

Le alternative includono;

  • Permettere all'amicizia di estendersi a tutte le istanze dei modelli, come segue;

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
  • Oppure, l'implementazione per il operator<<può essere fatta in linea all'interno della definizione della classe;

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

Nota , quando la dichiarazione dell'operatore (o funzione) appare solo nella classe, il nome non è disponibile per la ricerca "normale", solo per la ricerca dipendente dall'argomento, da cppreference ;

Un nome dichiarato per la prima volta in una dichiarazione di amicizia all'interno della classe o del modello di classe X diventa un membro dello spazio dei nomi che racchiude più interno di X, ma non è accessibile per la ricerca (tranne la ricerca dipendente dall'argomento che considera X) a meno che una dichiarazione corrispondente nell'ambito dello spazio dei nomi sia fornito...

Ulteriori letture sugli amici dei template sono disponibili su cppreference e sulle Domande frequenti su C ++ .

Elenco di codice che mostra le tecniche sopra .


Come nota a margine dell'esempio di codice non riuscito; g ++ lo avverte come segue

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)


16

Quando i percorsi di inclusione sono diversi

Gli errori del linker possono verificarsi quando un file di intestazione e la libreria condivisa associata (file .lib) non sono sincronizzati. Lasciatemi spiegare.

Come funzionano i linker? Il linker abbina una dichiarazione di funzione (dichiarata nell'intestazione) con la sua definizione (nella libreria condivisa) confrontando le loro firme. È possibile ottenere un errore del linker se il linker non trova una definizione di funzione che corrisponde perfettamente.

È ancora possibile ottenere un errore del linker anche se la dichiarazione e la definizione sembrano corrispondere? Sì! Potrebbero apparire uguali nel codice sorgente, ma dipende davvero da cosa vede il compilatore. In sostanza potresti finire con una situazione come questa:

// header1.h
typedef int Number;
void foo(Number);

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

Nota come anche se entrambe le dichiarazioni di funzioni sembrano identiche nel codice sorgente, ma sono davvero diverse in base al compilatore.

Potresti chiedere come si finisce in una situazione del genere? Includi i percorsi ovviamente! Se durante la compilazione della libreria condivisa, il percorso include porta a header1.he si finisce per usareheader2.h nel tuo programma, rimarrai a grattare l'intestazione chiedendoti cosa è successo (gioco di parole).

Un esempio di come ciò può accadere nel mondo reale è spiegato di seguito.

Ulteriore elaborazione con un esempio

Ho due progetti: graphics.libe main.exe. Entrambi i progetti dipendono da common_math.h. Supponiamo che la libreria esporti la seguente funzione:

// graphics.lib    
#include "common_math.h" 

void draw(vec3 p) { ... } // vec3 comes from common_math.h

E poi vai avanti e includi la biblioteca nel tuo progetto.

// main.exe
#include "other/common_math.h"
#include "graphics.h"

int main() {
    draw(...);
}

Boom! Viene visualizzato un errore del linker e non si ha idea del perché non riesca. Il motivo è che la libreria comune utilizza versioni diverse della stessa inclusionecommon_math.h (l'ho reso evidente qui nell'esempio includendo un percorso diverso, ma potrebbe non essere sempre così ovvio. Forse il percorso include è diverso nelle impostazioni del compilatore) .

Nota in questo esempio, il linker ti direbbe che non è stato possibile trovare draw(), quando in realtà sai che ovviamente viene esportato dalla libreria. Potresti passare ore a grattarti la testa chiedendoti cosa è andato storto. Il fatto è che il linker vede una firma diversa perché i tipi di parametro sono leggermente diversi. Nell'esempio, vec3è un tipo diverso in entrambi i progetti per quanto riguarda il compilatore. Questo potrebbe accadere perché provengono da due file include leggermente diversi (forse i file include provengono da due diverse versioni della libreria).

Debug del linker

DUMPBIN è tuo amico, se stai usando Visual Studio. Sono sicuro che altri compilatori hanno altri strumenti simili.

Il processo procede in questo modo:

  1. Nota lo strano nome alterato fornito nell'errore del linker. (es. draw @ graphics @ XYZ).
  2. Scarica i simboli esportati dalla libreria in un file di testo.
  3. Cerca il simbolo di interesse esportato e nota che il nome alterato è diverso.
  4. Presta attenzione al perché i nomi alterati sono finiti in modo diverso. Potresti vedere che i tipi di parametro sono diversi, anche se sembrano uguali nel codice sorgente.
  5. Motivo per cui sono diversi. Nell'esempio sopra riportato, sono diversi a causa dei diversi file include.

[1] Per progetto intendo un insieme di file di origine che sono collegati tra loro per produrre una libreria o un eseguibile.

EDIT 1: riscrivere la prima sezione per essere più facile da capire. Commenta di seguito per farmi sapere se è necessario correggere qualcos'altro. Grazie!


15

UNICODEDefinizioni incoerenti

Una build di Windows UNICODE è costruita con TCHARecc. Definita come wchar_tecc. Quando non si UNICODEcrea con TCHARdefinita come build con definita come charecc. Queste UNICODEe _UNICODEdefinizioni influiscono su tutti i " T" tipi di stringa ; LPTSTR, LPCTSTRE le loro alci.

La creazione di una libreria con UNICODEdefinita e il tentativo di collegarla in un progetto in cui UNICODEnon è definita comporterà errori del linker poiché si verificherà una mancata corrispondenza nella definizione di TCHAR; charvs wchar_t.

L'errore di solito comprende una funzione di un valore con una charo wchar_ttipo derivato, questi potrebbero includere std::basic_string<>ecc pure. Quando si sfoglia la funzione interessata nel codice, ci sarà spesso un riferimento a TCHARo std::basic_string<TCHAR>ecc. Questo è un segno rivelatore che il codice era originariamente destinato sia a un UNICODE che a un carattere a più byte (o "stretto") .

Per correggere ciò, creare tutte le librerie e i progetti richiesti con una definizione coerente di UNICODE(e _UNICODE).

  1. Questo può essere fatto con entrambi;

    #define UNICODE
    #define _UNICODE
  2. O nelle impostazioni del progetto;

    Proprietà del progetto> Generale> Impostazioni predefinite progetto> Set di caratteri

  3. O dalla riga di comando;

    /DUNICODE /D_UNICODE

L'alternativa è applicabile anche, se UNICODE non è destinato a essere utilizzato, assicurarsi che le definizioni non siano impostate e / o che l'impostazione multi-carattere sia utilizzata nei progetti e applicata in modo coerente.

Non dimenticare di essere coerente anche tra le build "Release" e "Debug".


14

Pulito e ricostruito

Un "clean" della build può rimuovere il "legno morto" che può essere lasciato in giro da build precedenti, build fallite, build incomplete e altri problemi di build relativi al sistema di build.

In generale, l'IDE o la build includeranno una qualche forma di funzione "clean", ma questa potrebbe non essere configurata correttamente (ad es. In un makefile manuale) o potrebbe fallire (ad es. I binari intermedi o risultanti sono di sola lettura).

Una volta completato il "clean", verificare che il "clean" sia riuscito e che tutto il file intermedio generato (ad es. Un makefile automatizzato) sia stato rimosso con successo.

Questo processo può essere visto come un ricorso finale, ma è spesso un buon primo passo ; specialmente se il codice relativo all'errore è stato aggiunto di recente (o localmente o dal repository di origine).


10

"Extern" mancante nelle constdichiarazioni / definizioni variabili (solo C ++)

Per le persone provenienti da C potrebbe essere una sorpresa che in C ++ le constvariabili globali abbiano un collegamento interno (o statico). In C non è stato così, poiché tutte le variabili globali sono implicitamente extern(cioè quando staticmanca la parola chiave).

Esempio:

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

corretto sarebbe usare un file header e includerlo in file2.cpp e file1.cpp

extern const int test;
extern int test2;

In alternativa si potrebbe dichiarare constesplicitamente la variabile in file1.cppextern


8

Anche se questa è una domanda piuttosto vecchia con più risposte accettate, vorrei condividere come risolvere un oscuro "riferimento indefinito a" errore.

Diverse versioni di librerie

Stavo usando un alias per fare riferimento a std::filesystem::path: filesystem è nella libreria standard dal C ++ 17 ma il mio programma doveva essere compilato anche in C ++ 14 quindi ho deciso di usare un alias variabile:

#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

Diciamo che ho tre file: main.cpp, file.h, file.cpp:

  • file.h # include il < file system :: sperimentale > e contiene il codice sopra
  • file.cpp , l'implementazione di file.h, # include " file.h "
  • main.cpp # include < filesystem > e " file.h "

Nota le diverse librerie utilizzate in main.cpp e file.h. Poiché main.cpp # include " file.h " dopo < filesystem >, la versione del filesystem utilizzata era quella C ++ 17 . Ho usato per compilare il programma con i seguenti comandi:

$ g++ -g -std=c++17 -c main.cpp-> compila main.cpp in main.o
$ g++ -g -std=c++17 -c file.cpp-> compila file.cpp e file.h in file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs-> collega main.o e file.o

In questo modo qualsiasi funzione contenuta in file.o e utilizzato in main.o che richiestopath_t dato errori "di riferimento definito" perché main.o cui std::filesystem::pathma file.o a std::experimental::filesystem::path.

Risoluzione

Per risolvere questo problema, avevo solo bisogno di cambiare <file system :: sperimentale> in file.h in <file sistema> .


5

Quando si collega a librerie condivise, assicurarsi che i simboli utilizzati non siano nascosti.

Il comportamento predefinito di gcc è che tutti i simboli sono visibili. Tuttavia, quando le unità di traduzione sono costruite con l'opzione -fvisibility=hidden, solo le funzioni / i simboli contrassegnati con __attribute__ ((visibility ("default")))sono esterni nell'oggetto condiviso risultante.

Puoi verificare se i simboli che stai cercando sono esterni invocando:

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

i simboli nascosti / locali sono indicati da un nmtipo di simbolo minuscolo, ad esempio tinvece di `T per la sezione di codice:

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

Puoi anche usare nmcon l'opzione -Cper districare i nomi (se è stato usato C ++).

Simile a Windows-DLL, si contrassegnano le funzioni pubbliche con un define, ad esempio DLL_PUBLICdefinito come:

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

DLL_PUBLIC int my_public_function(){
  ...
}

Che corrisponde approssimativamente alla versione Windows / MSVC:

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

Maggiori informazioni sulla visibilità sono disponibili sul wiki di gcc.


Quando un'unità di traduzione viene compilata con -fvisibility=hiddeni simboli risultanti hanno ancora un collegamento esterno (mostrato con il simbolo di maiuscolo da nm) e può essere usato per un collegamento esterno senza problemi se i file oggetto diventano parte di librerie statiche. Il collegamento diventa locale solo quando i file oggetto sono collegati in una libreria condivisa.

Per trovare quali simboli in un file oggetto sono nascosti, eseguire:

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

È necessario utilizzare nm -CDo nm -gCDper visualizzare simboli esterni. Vedi anche Visibilità sul wiki di GCC.
jww

2

Diverse architetture

È possibile che venga visualizzato un messaggio come:

library machine type 'x64' conflicts with target machine type 'X86'

In tal caso, significa che i simboli disponibili sono per un'architettura diversa da quella per la quale si sta compilando.

Su Visual Studio, ciò è dovuto alla "Piattaforma" errata ed è necessario selezionare quella corretta o installare la versione corretta della libreria.

Su Linux, potrebbe essere dovuto alla cartella della libreria errata (usando libinvece che lib64per esempio).

Su MacOS, è possibile spedire entrambe le architetture nello stesso file. È possibile che il collegamento si aspetti che entrambe le versioni siano presenti, ma solo una lo è. Può anche essere un problema con la cartella lib/ errata in lib64cui viene prelevata la libreria.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.