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.
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.
Risposte:
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] .
- 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]
- 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]
- Il file di origine viene scomposto in token di preelaborazione (2.5) e sequenze di caratteri di spazi bianchi (inclusi i commenti). [Omissis]
- Vengono eseguite le direttive di preelaborazione, le invocazioni di macro vengono espanse e vengono eseguite espressioni operatore unarie di _Pragma. [Omissis]
- 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]
- I token letterali stringa adiacenti sono concatenati.
- 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]
- Le unità di traduzione tradotte e le unità di istanza sono combinate come segue: [SNIP]
- 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 a
in 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 .lib
file. 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 LNK2019
per 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:
#pragma
(Microsoft Visual Studio)UNICODE
Definizioni incoerentivirtual
distruttore 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 virtual
metodi 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;
};
virtual
Membri non di classeAlcuni 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 inline
da impedire una definizione multipla.
Tutti i metodi membro utilizzati devono essere definiti se utilizzati.
struct A
{
void foo();
};
void foo() {}
int main()
{
A a;
a.foo();
}
La definizione dovrebbe essere
void A::foo() {}
static
i 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
const
membro 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 const
i membri dei dati.
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 libraryName
qui è solo il nome a nudo della biblioteca, senza aggiunte piattaforma specifici. Quindi, ad esempio, i file della libreria Linux vengono generalmente chiamati libfoo.so
ma 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 -l
o -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 lib
file verrebbe generato (di uso comune). Per utilizzare i simboli in un progetto separato, è necessario includere i lib
file nelle impostazioni del progetto. Questo viene fatto nella sezione Linker delle proprietà del progetto, in Input -> Additional Dependencies
. (il percorso del lib
file 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".
gcc main.c
invece di gcc main.c other.c
(che spesso fanno i principianti prima che i loro progetti diventino così grandi da creare file .o).
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
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.
#includes
non aggiunti alla directory di origine rientrano nella categoria delle definizioni mancanti.
L'ordine in cui le librerie sono collegate È importante se le librerie dipendono l'una dall'altra. In generale, se la libreria A
dipende dalla libreria B
, libA
DEVE apparire prima libB
nei 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 !
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.
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"
}
#ifdef __cplusplus [\n] extern"C" { [\n] #endif
e #ifdef __cplusplus [\n] } [\n] #endif
( [\n]
essendo un vero ritorno a capo ma non posso scriverlo correttamente nei commenti).
extern "C" { #include <myCppHeader.h> }
.
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.
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_MODULE
verrebbe 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
{
};
visibility
e Windows .def
, poiché questi influenzano anche il nome e la presenza del simbolo.
.def
file da anni. Sentiti libero di aggiungere una risposta o modificarla.
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:
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:
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.
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.
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 impl
file 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 .
riferimento indefinito WinMain@16
o riferimento al punto di ingresso "insolito" similemain()
(in particolare pervisual-studio).
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 .
WinMain
. I programmi C ++ validi richiedono a main
.
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 packages
cartella all'interno della directory della soluzione) trovando packagename\build\native\packagename.targets
e all'interno di quel file, copiando tutte le v110
sezioni. Ho cambiato il v110
per 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.v120
v110
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?
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
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
Osservazione
Come risolvere questo tipo di errore
Errore tempo compilatore:
Errore tempo linker
#pragma once
per consentire al compilatore di non includere un'intestazione se era già incluso nell'attuale .cpp che viene compilatoRecentemente 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:
I linker più moderni includono un'opzione dettagliata che viene stampata a vari livelli;
Per gcc e clang; in genere si aggiunge -v -Wl,--verbose
o -v -Wl,-v
alla riga di comando. Maggiori dettagli possono essere trovati qui;
Per MSVC, /VERBOSE
(in particolare /VERBOSE:LIB
) viene aggiunto alla riga di comando del collegamento.
/VERBOSE
sull'opzione linker .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.
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.
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 -ltbb
farlo/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
libfoo
dipende da libbar
, allora il tuo collegamento mette correttamente libfoo
prima libbar
.undefined reference to
qualcosa di errori.#include
e sono di fatto definite nelle librerie che stai collegando.Esempi sono in C. Potrebbero anche essere C ++
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.a
e 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
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 libz
e 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'
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-config
correttamente la variante di esempio 2 :
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib)
$ ./eg2
1.2.8
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_lib
nella 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.o
se 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.o
libreria e la aggiungerà al tuo programma. Quindi, il tuo programma contiene una definizione per hw
, quindi i suoi riferimenti hw
sono 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.o
al programma e non serve più a libmy_lib.a
.
Successivamente, trova eg1.o
e lo aggiunge come programma. Un file oggetto nella sequenza di collegamenti viene sempre aggiunto al programma. Ora, il programma fa un riferimento hw
e non contiene una definizione di hw
; ma non è rimasto nulla nella sequenza di collegamenti che potrebbe fornire la definizione mancante. Il riferimento a hw
finisce 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 main
funzione 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++
, gfortran
ecc) 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 -lz
nella 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 libz
e 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 libz
nello 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 libz
nello 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-config
variazione 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.
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 -lfoo
nel loro collegamento si risolveranno /some/where/libfoo.a
o /some/where/libfoo.so
no 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.
Se lo faccio solo:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
sicuramente gcc deve eg1.c
prima 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, gcc
scopri 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':
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.
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 T
utilizzato 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 Foo
tipo 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)
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.h
e 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.
Ho due progetti: graphics.lib
e 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).
DUMPBIN è tuo amico, se stai usando Visual Studio. Sono sicuro che altri compilatori hanno altri strumenti simili.
Il processo procede in questo modo:
[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!
UNICODE
Definizioni incoerentiUna build di Windows UNICODE è costruita con TCHAR
ecc. Definita come wchar_t
ecc. Quando non si UNICODE
crea con TCHAR
definita come build con definita come char
ecc. Queste UNICODE
e _UNICODE
definizioni influiscono su tutti i " T
" tipi di stringa ; LPTSTR
, LPCTSTR
E le loro alci.
La creazione di una libreria con UNICODE
definita e il tentativo di collegarla in un progetto in cui UNICODE
non è definita comporterà errori del linker poiché si verificherà una mancata corrispondenza nella definizione di TCHAR
; char
vs wchar_t
.
L'errore di solito comprende una funzione di un valore con una char
o wchar_t
tipo derivato, questi potrebbero includere std::basic_string<>
ecc pure. Quando si sfoglia la funzione interessata nel codice, ci sarà spesso un riferimento a TCHAR
o 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
).
Questo può essere fatto con entrambi;
#define UNICODE
#define _UNICODE
O nelle impostazioni del progetto;
Proprietà del progetto> Generale> Impostazioni predefinite progetto> Set di caratteri
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".
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).
const
dichiarazioni / definizioni variabili (solo C ++)Per le persone provenienti da C potrebbe essere una sorpresa che in C ++ le const
variabili globali abbiano un collegamento interno (o statico). In C non è stato così, poiché tutte le variabili globali sono implicitamente extern
(cioè quando static
manca 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 const
esplicitamente la variabile in file1.cppextern
Anche se questa è una domanda piuttosto vecchia con più risposte accettate, vorrei condividere come risolvere un oscuro "riferimento indefinito a" errore.
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:
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::path
ma file.o a std::experimental::filesystem::path
.
Per risolvere questo problema, avevo solo bisogno di cambiare <file system :: sperimentale> in file.h in <file sistema> .
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 nm
tipo di simbolo minuscolo, ad esempio t
invece di `T per la sezione di codice:
nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL
Puoi anche usare nm
con l'opzione -C
per districare i nomi (se è stato usato C ++).
Simile a Windows-DLL, si contrassegnano le funzioni pubbliche con un define, ad esempio DLL_PUBLIC
definito 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=hidden
i 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
nm -CD
o nm -gCD
per visualizzare simboli esterni. Vedi anche Visibilità sul wiki di GCC.
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 lib
invece che lib64
per 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 lib64
cui viene prelevata la libreria.