O que são erros de símbolos externos indefinidos / de referência não definidos? Quais são as causas comuns e como corrigi-las / evitá-las?
Sinta-se livre para editar / adicionar seus próprios.
O que são erros de símbolos externos indefinidos / de referência não definidos? Quais são as causas comuns e como corrigi-las / evitá-las?
Sinta-se livre para editar / adicionar seus próprios.
Respostas:
A compilação de um programa C ++ ocorre em várias etapas, conforme especificado em 2.2 (créditos para referência a Keith Thompson) :
A precedência entre as regras de tradução da sintaxe é especificada pelas seguintes fases [ver nota de rodapé] .
- Os caracteres do arquivo de origem físico são mapeados, de maneira definida pela implementação, para o conjunto de caracteres de origem básico (introdução de caracteres de nova linha para indicadores de fim de linha), se necessário. [RECORTE]
- Cada instância de um caractere de barra invertida (\) imediatamente seguida por um caractere de nova linha é excluída, emendando as linhas de origem físicas para formar linhas de origem lógicas. [RECORTE]
- O arquivo de origem é decomposto em tokens de pré-processamento (2.5) e sequências de caracteres de espaço em branco (incluindo comentários). [RECORTE]
- As diretivas de pré-processamento são executadas, as chamadas de macro são expandidas e as expressões de operador _Pragma unary são executadas. [RECORTE]
- Cada membro do conjunto de caracteres de origem em um literal de caractere ou literal de sequência, bem como cada sequência de escape e nome universal de caractere em um literal de caractere ou literal de sequência não bruta, é convertido no membro correspondente do conjunto de caracteres de execução; [RECORTE]
- Os tokens literais de sequência adjacente são concatenados.
- Caracteres de espaço em branco que separam os tokens não são mais significativos. Cada token de pré-processamento é convertido em um token. (2,7) Os tokens resultantes são analisados sintática e semanticamente e traduzidos como uma unidade de tradução. [RECORTE]
- As unidades de tradução traduzida e as unidades de instanciação são combinadas da seguinte forma: [SNIP]
- Todas as referências a entidades externas são resolvidas. Os componentes da biblioteca estão vinculados para satisfazer referências externas a entidades não definidas na tradução atual. Toda essa saída do tradutor é coletada em uma imagem de programa que contém informações necessárias para execução em seu ambiente de execução. (ênfase minha)
[nota de rodapé] As implementações devem se comportar como se essas fases separadas ocorressem, embora na prática diferentes fases possam ser dobradas.
Os erros especificados ocorrem durante esse último estágio da compilação, mais comumente chamado de vinculação. Basicamente, significa que você compilou vários arquivos de implementação em arquivos ou bibliotecas de objetos e agora deseja que eles funcionem juntos.
Digamos que você definiu o símbolo a
em a.cpp
. Agora, b.cpp
declarou esse símbolo e o usou. Antes de vincular, simplesmente assume que esse símbolo foi definido em algum lugar , mas ainda não se importa onde. A fase de vinculação é responsável por encontrar o símbolo e vinculá-lo corretamente a b.cpp
(bem, na verdade, ao objeto ou biblioteca que o utiliza).
Se você estiver usando o Microsoft Visual Studio, verá que os projetos geram .lib
arquivos. Eles contêm uma tabela de símbolos exportados e uma tabela de símbolos importados. Os símbolos importados são resolvidos nas bibliotecas às quais você vincula e os símbolos exportados são fornecidos para as bibliotecas que usam isso .lib
(se houver).
Existem mecanismos semelhantes para outros compiladores / plataformas.
Mensagens de erro comuns são error LNK2001
, error LNK1120
, error LNK2019
para Microsoft Visual Studio e undefined reference to
SymbolName para GCC .
O código:
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;
}
irá gerar os seguintes erros com o 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 erros semelhantes com o 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
As causas comuns incluem:
#pragma
(Microsoft Visual Studio)UNICODE
Definições inconsistentesvirtual
destruidor puro precisa de uma implementação.Declarar um destruidor puro ainda requer que você o defina (ao contrário de uma função regular):
struct X
{
virtual ~X() = 0;
};
struct Y : X
{
~Y() {}
};
int main()
{
Y y;
}
//X::~X(){} //uncomment this line for successful definition
Isso acontece porque os destruidores da classe base são chamados quando o objeto é destruído implicitamente; portanto, é necessária uma definição.
virtual
métodos devem ser implementados ou definidos como puros.Isso é semelhante aos não virtual
métodos sem definição, com o raciocínio adicional de que a declaração pura gera uma vtable fictícia e você pode obter o erro do vinculador sem usar a função:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
int main()
{
Y y; //linker error although there was no call to X::foo
}
Para que isso funcione, declare X::foo()
como puro:
struct X
{
virtual void foo() = 0;
};
virtual
Membros não pertencentes à classeAlguns membros precisam ser definidos mesmo se não forem usados explicitamente:
struct A
{
~A();
};
O seguinte produziria o erro:
A a; //destructor undefined
A implementação pode estar embutida, na própria definição de classe:
struct A
{
~A() {}
};
ou fora:
A::~A() {}
Se a implementação estiver fora da definição de classe, mas em um cabeçalho, os métodos deverão ser marcados inline
para impedir uma definição múltipla.
Todos os métodos de membro usados precisam ser definidos se usados.
struct A
{
void foo();
};
void foo() {}
int main()
{
A a;
a.foo();
}
A definição deve ser
void A::foo() {}
static
os membros dos dados devem ser definidos fora da classe em uma única unidade de tradução :struct X
{
static int x;
};
int main()
{
int x = X::x;
}
//int X::x; //uncomment this line to define X::x
Um inicializador pode ser fornecido para um static
const
membro de dados do tipo integral ou de enumeração na definição de classe; no entanto, o uso de odr desse membro ainda exigirá uma definição de escopo de espaço para nome como descrito acima. O C ++ 11 permite a inicialização dentro da classe para todos static const
os membros de dados.
Geralmente, cada unidade de tradução gera um arquivo de objeto que contém as definições dos símbolos definidos nessa unidade de tradução. Para usar esses símbolos, você deve vincular esses arquivos de objeto.
No gcc, você especificaria todos os arquivos de objeto a serem vinculados na linha de comando ou compilaria os arquivos de implementação.
g++ -o test objectFile1.o objectFile2.o -lLibraryName
A libraryName
aqui é apenas o nome nua da biblioteca, sem acréscimos específicos da plataforma. Então, por exemplo, nos arquivos de biblioteca do Linux geralmente são chamados, libfoo.so
mas você só escreve -lfoo
. No Windows, esse mesmo arquivo pode ser chamado foo.lib
, mas você usaria o mesmo argumento. Pode ser necessário adicionar o diretório em que esses arquivos podem ser encontrados usando -L‹directory›
. Certifique-se de não escrever um espaço após -l
ou -L
.
Para XCode : Adicione os Caminhos de Pesquisa de Cabeçalho do Usuário -> adicione o Caminho de Pesquisa da Biblioteca -> arraste e solte a referência real da biblioteca na pasta do projeto.
No MSVS , os arquivos adicionados a um projeto automaticamente têm seus arquivos de objetos vinculados e um lib
arquivo é gerado (em uso comum). Para usar os símbolos em um projeto separado, você precisará incluir os lib
arquivos nas configurações do projeto. Isso é feito na seção Vinculador das propriedades do projeto, em Input -> Additional Dependencies
. (o caminho para o lib
arquivo deve ser adicionado Linker -> General -> Additional Library Directories
) Ao usar uma biblioteca de terceiros que é fornecida com um lib
arquivo, a falha em fazer isso geralmente resulta no erro.
Também pode acontecer que você esqueça de adicionar o arquivo à compilação; nesse caso, o arquivo de objeto não será gerado. No gcc, você adicionaria os arquivos à linha de comando. No MSVS, adicionar o arquivo ao projeto fará com que ele seja compilado automaticamente (embora os arquivos possam ser excluídos manualmente da compilação).
Na programação do Windows, o sinal indicativo de que você não vinculou uma biblioteca necessária é que o nome do símbolo não resolvido começa com __imp_
. Procure o nome da função na documentação e deve indicar qual biblioteca você precisa usar. Por exemplo, o MSDN coloca as informações em uma caixa na parte inferior de cada função em uma seção chamada "Biblioteca".
gcc main.c
vez de gcc main.c other.c
(o que os iniciantes costumam fazer antes que seus projetos se tornem tão grandes que constroem arquivos .o).
Uma declaração de variável típica é
extern int x;
Como esta é apenas uma declaração, é necessária uma única definição . Uma definição correspondente seria:
int x;
Por exemplo, o seguinte geraria um erro:
extern int x;
int main()
{
x = 0;
}
//int x; // uncomment this line for successful definition
Comentários semelhantes se aplicam às funções. Declarar uma função sem defini-la leva ao erro:
void foo(); // declaration only
int main()
{
foo();
}
//void foo() {} //uncomment this line for successful definition
Tenha cuidado para que a função que você implementa corresponda exatamente à que você declarou. Por exemplo, você pode ter cv-qualifiers incompatíveis:
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)
Outros exemplos de incompatibilidades incluem
A mensagem de erro do compilador geralmente fornecerá a declaração completa da variável ou função que foi declarada, mas nunca definida. Compare-o com a definição que você forneceu. Verifique se todos os detalhes correspondem.
#includes
não adicionados ao diretório de origem também se enquadram na categoria de definições ausentes.
A ordem na qual as bibliotecas estão vinculadas importa se as bibliotecas dependem uma da outra. Em geral, se a biblioteca A
depende da biblioteca B
, libA
DEVE aparecer antes libB
nas bandeiras do vinculador.
Por exemplo:
// 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;
};
Crie as bibliotecas:
$ 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
Compilar:
$ 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
Então, para repetir mais uma vez, a ordem FAZ importa!
o que é "referência indefinida / símbolo externo não resolvido"
Vou tentar explicar o que é uma "referência indefinida / símbolo externo não resolvido".
nota: eu uso g ++ e Linux e todos os exemplos são para isso
Por exemplo, temos algum código
// 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);
}
Crie arquivos de objeto
$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o
Após a fase de montagem, temos um arquivo de objeto, que contém quaisquer símbolos para exportar. Olhe para os símbolos
$ 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]
Rejeitei algumas linhas da saída, porque elas não importam
Então, vemos os seguintes símbolos para exportar.
[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable
src2.cpp não exporta nada e não vimos seus símbolos
Vincular nossos arquivos de objeto
$ g++ src1.o src2.o -o prog
e execute
$ ./prog
123
O vinculador vê símbolos exportados e os vincula. Agora tentamos descomentar linhas no src2.cpp como aqui
// 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 reconstruir um arquivo de objeto
$ g++ -c src2.cpp -o src2.o
OK (sem erros), porque apenas construímos um arquivo de objeto, a vinculação ainda não está concluída. Tente vincular
$ 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
Isso aconteceu porque nosso local_var_name é estático, ou seja, não é visível para outros módulos. Agora mais profundamente. Obter a saída da fase de tradução
$ 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
Portanto, vimos que não há rótulo para local_var_name, é por isso que o vinculador não o encontrou. Mas somos hackers :) e podemos corrigi-lo. Abra src1.s no seu editor de texto e altere
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
para
.globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
ou seja, você deve ter como abaixo
.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:
; ...
alteramos a visibilidade de local_var_name e configuramos seu valor para 456789. Tente criar um arquivo de objeto a partir dele
$ g++ -c src1.s -o src2.o
ok, veja a saída readelf (símbolos)
$ readelf --symbols src1.o
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 local_var_name
agora local_var_name tem Vincular GLOBAL (era LOCAL)
ligação
$ g++ src1.o src2.o -o prog
e execute
$ ./prog
123456789
ok, nós cortamos isso :)
Portanto, como resultado - um "erro de referência externa indefinida / símbolo não resolvido" ocorre quando o vinculador não consegue encontrar símbolos globais nos arquivos de objeto.
A função (ou variável) void foo()
foi definida em um programa C e você tenta usá-lo em um programa C ++:
void foo();
int main()
{
foo();
}
O vinculador C ++ espera que os nomes sejam desconfigurados, portanto, você deve declarar a função como:
extern "C" void foo();
int main()
{
foo();
}
Equivalentemente, em vez de ser definida em um programa C, a função (ou variável) void foo()
foi definida em C ++, mas com ligação C:
extern "C" void foo();
e você tenta usá-lo em um programa C ++ com ligação C ++.
Se uma biblioteca inteira estiver incluída em um arquivo de cabeçalho (e foi compilado como código C); o include precisará ser o seguinte;
extern "C" {
#include "cheader.h"
}
#ifdef __cplusplus [\n] extern"C" { [\n] #endif
e #ifdef __cplusplus [\n] } [\n] #endif
( [\n]
sendo retorno de carro real, mas não posso escrever isso corretamente no comentário).
extern "C" { #include <myCppHeader.h> }
.
Se tudo mais falhar, recompile.
Recentemente, consegui me livrar de um erro externo não resolvido no Visual Studio 2012 apenas recompilando o arquivo incorreto. Quando reconstruí, o erro desapareceu.
Isso geralmente acontece quando duas (ou mais) bibliotecas têm uma dependência cíclica. A biblioteca A tenta usar símbolos em B.lib e a biblioteca B tenta usar símbolos de A.lib. Nem existe para começar. Quando você tenta compilar A, a etapa do link falhará porque não consegue encontrar B.lib. A.lib será gerado, mas nenhuma DLL. Você compila B, que terá êxito e gerará B.lib. A recompilação de A agora funcionará porque o B.lib foi encontrado.
O MSVS exige que você especifique quais símbolos exportar e importar usando __declspec(dllexport)
e __declspec(dllimport)
.
Essa funcionalidade dupla geralmente é obtida através do uso de uma macro:
#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif
A macro THIS_MODULE
seria definida apenas no módulo que exporta a função. Dessa forma, a declaração:
DLLIMPEXP void foo();
expande para
__declspec(dllexport) void foo();
e diz ao compilador para exportar a função, pois o módulo atual contém sua definição. Ao incluir a declaração em um módulo diferente, ela seria expandida para
__declspec(dllimport) void foo();
e informa ao compilador que a definição está em uma das bibliotecas às quais você vinculou (consulte também 1) ).
Você pode simular classes de importação / exportação:
class DLLIMPEXP X
{
};
visibility
e do Windows .def
, pois eles também influenciam o nome e a presença do símbolo.
.def
arquivos há muito tempo. Sinta-se livre para adicionar uma resposta ou editar esta.
Essa é uma das mensagens de erro mais confusas que todos os programadores do VC ++ viram repetidamente. Vamos esclarecer as coisas primeiro.
A. O que é símbolo? Em resumo, um símbolo é um nome. Pode ser um nome de variável, um nome de função, um nome de classe, um nome de tipo de digitação ou qualquer coisa, exceto os nomes e sinais que pertencem à linguagem C ++. É definido pelo usuário ou introduzido por uma biblioteca de dependências (outro definido pelo usuário).
B. O que é externo?
No VC ++, todo arquivo de origem (.cpp, .c, etc.) É considerado uma unidade de tradução, o compilador compila uma unidade de cada vez e gera um arquivo de objeto (.obj) para a unidade de tradução atual. (Observe que todo arquivo de cabeçalho que esse arquivo de origem incluiu será pré-processado e será considerado como parte dessa unidade de tradução) Tudo dentro de uma unidade de tradução é considerado interno, todo o resto é considerado externo. Em C ++, você pode fazer referência a um símbolo externo usando palavras-chave como extern
, __declspec (dllimport)
e assim por diante.
C. O que é "resolver"? Resolução é um termo de tempo de vinculação. No tempo de vinculação, o vinculador tenta encontrar a definição externa para cada símbolo nos arquivos de objeto que não conseguem encontrar sua definição internamente. O escopo deste processo de pesquisa, incluindo:
Esse processo de busca é chamado de resolução.
D. Finalmente, por que o símbolo externo não resolvido? Se o vinculador não puder encontrar a definição externa para um símbolo que não tem definição internamente, ele reportará um erro de Símbolo Externo Não Resolvido.
E. Possíveis causas do LNK2019 : Erro de símbolo externo não resolvido. Já sabemos que esse erro ocorre porque o vinculador não encontrou a definição de símbolos externos; as possíveis causas podem ser classificadas como:
Por exemplo, se tivermos uma função chamada foo definida em a.cpp:
int foo()
{
return 0;
}
Em b.cpp queremos chamar a função foo, então adicionamos
void foo();
para declarar a função foo () e chamá-la em outro corpo da função, diga bar()
:
void bar()
{
foo();
}
Agora, ao criar esse código, você receberá um erro do LNK2019 reclamando que foo é um símbolo não resolvido. Nesse caso, sabemos que foo () tem sua definição em a.cpp, mas diferente da que estamos chamando (valor de retorno diferente). Este é o caso em que a definição existe.
Se queremos chamar algumas funções em uma biblioteca, mas a biblioteca de importação não é adicionada à lista de dependências adicionais (definida em:) da configuração do Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
seu projeto. Agora, o vinculador reportará um LNK2019, pois a definição não existe no escopo de pesquisa atual.
Modelos não especializados devem ter suas definições visíveis para todas as unidades de tradução que os usam. Isso significa que você não pode separar a definição de um modelo em um arquivo de implementação. Se você deve separar a implementação, a solução alternativa usual é ter um impl
arquivo que você inclua no final do cabeçalho que declara o modelo. Uma situação comum é:
template<class T>
struct X
{
void foo();
};
int main()
{
X<int> x;
x.foo();
}
//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}
Para corrigir isso, você deve mover a definição de X::foo
para o arquivo de cabeçalho ou algum lugar visível para a unidade de tradução que o utiliza.
Modelos especializados podem ser implementados em um arquivo de implementação e a implementação não precisa estar visível, mas a especialização deve ser declarada anteriormente.
Para mais explicações e outra solução possível (instanciação explícita), consulte esta pergunta e resposta .
referência indefinida WinMain@16
ou referência de ponto de entrada 'incomum' semelhantemain()
(especialmente paraestúdio visual)
Você pode ter deixado de escolher o tipo certo de projeto com seu IDE real. O IDE pode querer vincular, por exemplo, projetos de aplicativos Windows a essa função de ponto de entrada (conforme especificado na referência ausente acima), em vez da int main(int argc, char** argv);
assinatura comumente usada .
Se o seu IDE suportar Projetos de console simples, convém escolher esse tipo de projeto, em vez de um projeto de aplicativo do Windows.
Aqui estão case1 e caso2 tratadas com mais detalhes a partir de um mundo real problema.
WinMain
. Programas válidos em C ++ precisam de um main
.
O pacote NuGet do Visual Studio precisa ser atualizado para a nova versão do conjunto de ferramentas
Eu apenas tive esse problema ao tentar vincular a libpng ao Visual Studio 2013. O problema é que o arquivo do pacote tinha apenas bibliotecas para o Visual Studio 2010 e 2012.
A solução correta é esperar que o desenvolvedor libere um pacote atualizado e faça a atualização, mas funcionou para mim invadindo uma configuração extra do VS2013, apontando os arquivos da biblioteca do VS2012.
Editei o pacote (na packages
pasta dentro do diretório da solução) localizando packagename\build\native\packagename.targets
e dentro desse arquivo, copiando todas as v110
seções. Mudei v110
para para v120
nos campos de condição, tendo muito cuidado para deixar os caminhos de nome de arquivo todos comov110
. Isso simplesmente permitiu que o Visual Studio 2013 se vinculasse às bibliotecas de 2012 e, nesse caso, funcionou.
Suponha que você tenha um grande projeto escrito em c ++ que possua mil arquivos .cpp e mil arquivos .h. Digamos que o projeto também dependa de dez bibliotecas estáticas. Digamos que estamos no Windows e criamos nosso projeto no Visual Studio 20xx. Quando você pressiona Ctrl + F7 Visual Studio para começar a compilar toda a solução (suponha que tenhamos apenas um projeto na solução)
Qual o significado da compilação?
A segunda etapa da compilação é feita pelo Linker.Linker deve mesclar todo o arquivo de objeto e criar finalmente a saída (que pode ser um executável ou uma biblioteca)
Etapas para vincular um projeto
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
Observação
Como resolver esse tipo de erro
Erro de tempo do compilador:
Erro de tempo do vinculador
#pragma once
para permitir que o compilador não inclua um cabeçalho, se já estiver incluído no .cpp atual, compiladoRecentemente, tive esse problema e, no Visual Studio Express 2013, havia um erro. . Eu tive que remover um arquivo de origem do projeto e adicioná-lo novamente para superar o bug.
Etapas para tentar se você acredita que poderia ser um bug no compilador / IDE:
A maioria dos vinculadores modernos inclui uma opção detalhada que é impressa em vários graus;
Para gcc e clang; você normalmente adicionaria -v -Wl,--verbose
ou -v -Wl,-v
à linha de comando. Mais detalhes podem ser encontrados aqui;
Para MSVC, /VERBOSE
(em particular /VERBOSE:LIB
) é adicionado à linha de comando do link.
/VERBOSE
opção vinculador .O arquivo .lib vinculado está associado a uma.dll
Eu tive o mesmo problema. Digamos que eu tenha projetos MyProject e TestProject. Eu efetivamente vinculei o arquivo lib do MyProject ao TestProject. No entanto, esse arquivo lib foi produzido quando a DLL do MyProject foi criada. Além disso, eu não contive o código-fonte para todos os métodos no MyProject, mas apenas acesso aos pontos de entrada da DLL.
Para resolver o problema, criei o MyProject como um LIB e vinculei o TestProject a esse arquivo .lib (copie e cole o arquivo .lib gerado na pasta TestProject). Posso criar novamente o MyProject como uma DLL. Ele está compilando, pois a lib à qual o TestProject está vinculado contém código para todos os métodos nas classes no MyProject.
Como as pessoas parecem ser direcionadas para essa pergunta quando se trata de erros do vinculador, adicionarei isso aqui.
Um possível motivo para erros do vinculador no GCC 5.2.0 é que uma nova ABI da biblioteca libstdc ++ agora é escolhida por padrão.
Se você receber erros do vinculador sobre referências indefinidas a símbolos que envolvem tipos no espaço de nome std :: __ cxx11 ou na tag [abi: cxx11], isso provavelmente indica que você está tentando vincular arquivos de objetos que foram compilados com valores diferentes para o _GLIBCXX_USE_CXX11_ABI macro. Isso geralmente acontece ao vincular a uma biblioteca de terceiros que foi compilada com uma versão mais antiga do GCC. Se a biblioteca de terceiros não puder ser reconstruída com a nova ABI, será necessário recompilar seu código com a antiga ABI.
Portanto, se você repentinamente receber erros de vinculador ao mudar para um GCC após a 5.1.0, isso é algo a se verificar.
Um wrapper em torno do GNU ld que não suporta scripts de vinculador
Alguns arquivos .so são na verdade scripts de vinculador GNU ld , por exemplo, o arquivo libtbb.so é um arquivo de texto ASCII com este conteúdo:
INPUT (libtbb.so.2)
Algumas compilações mais complexas podem não suportar isso. Por exemplo, se você incluir -v nas opções do compilador, poderá ver que o mainwin gcc wrapper mwdip descarta os arquivos de comando do script do vinculador na lista de saída detalhada das bibliotecas a serem vinculadas. Uma solução simples é substituir o comando de entrada do script do vinculador arquivo com uma cópia do arquivo (ou um link simbólico), por exemplo
cp libtbb.so.2 libtbb.so
Ou você pode substituir o argumento -l pelo caminho completo do .so, por exemplo, em vez de -ltbb
fazer/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
libfoo
depende libbar
, então sua ligação coloca corretamente libfoo
antes libbar
.undefined reference to
algo .#include
d e são de fato definido nas bibliotecas que você está ligando.Os exemplos estão em C. Eles poderiam igualmente ser 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;
}
Você constrói sua biblioteca estática:
$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o
Você compila seu programa:
$ gcc -I. -c -o eg1.o eg1.c
Você tenta vinculá-lo libmy_lib.a
e falhar:
$ 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
O mesmo resultado se você compilar e vincular em uma etapa, como:
$ 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;
}
Compile seu programa:
$ gcc -c -o eg2.o eg2.c
Tente vincular seu programa libz
e falhar:
$ 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
Mesmo se você compilar e vincular de uma só vez:
$ 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 uma variação no exemplo 2 envolvendo pkg-config
:
$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
Na sequência de arquivos e bibliotecas de objetos que você deseja vincular para criar seu programa, você está colocando as bibliotecas antes dos arquivos de objetos que se referem a eles. Você precisa colocar as bibliotecas após os arquivos de objeto que se referem a eles.
Vincule o exemplo 1 corretamente:
$ gcc -o eg1 eg1.o -L. -lmy_lib
Sucesso:
$ ./eg1
Hello World
Vincule o exemplo 2 corretamente:
$ gcc -o eg2 eg2.o -lz
Sucesso:
$ ./eg2
1.2.8
Vincule a pkg-config
variação do exemplo 2 corretamente:
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib)
$ ./eg2
1.2.8
A leitura é opcional a partir daqui .
Por padrão, um comando de ligação gerado pelo GCC, na sua distribuição, consome os arquivos na ligação da esquerda para a direita na sequência da linha de comando. Quando descobre que um arquivo se refere a algo e não contém uma definição, procurará por uma definição em arquivos mais à direita. Se, eventualmente, encontrar uma definição, a referência será resolvida. Se alguma referência permanecer sem solução no final, a ligação falhará: o vinculador não pesquisa para trás.
Primeiro, exemplo 1 , com biblioteca estáticamy_lib.a
Uma biblioteca estática é um arquivo indexado de arquivos de objetos. Quando o vinculador encontra -lmy_lib
na sequência de ligação e descobre que isso se refere à biblioteca estática ./libmy_lib.a
, ele quer saber se o seu programa precisa de algum dos arquivos de objeto libmy_lib.a
.
Existe apenas um arquivo de objeto libmy_lib.a
, a saber my_lib.o
, e apenas uma coisa é definida my_lib.o
, a função hw
.
O vinculador decidirá que o seu programa precisa my_lib.o
se, e somente se, ele já souber que o programa se refere a hw
um ou mais dos arquivos de objetos que ele já adicionou ao programa e que nenhum dos arquivos de objetos que já adicionou contém um definição para hw
.
Se isso for verdade, o vinculador extrairá uma cópia da my_lib.o
biblioteca e a adicionará ao seu programa. Em seguida, seu programa contém uma definição para hw
, portanto, suas referências hw
são resolvidas .
Quando você tenta vincular o programa, como:
$ gcc -o eg1 -L. -lmy_lib eg1.o
o vinculador não foi adicionado eg1.o
ao programa quando ele vê
-lmy_lib
. Porque nesse ponto, ele não viu eg1.o
. Seu programa ainda não faz qualquer referência a hw
: ainda não faz qualquer referência em tudo , porque todas as referências Faz estão emeg1.o
.
Portanto, o vinculador não adiciona my_lib.o
ao programa e não tem mais uso para libmy_lib.a
.
Em seguida, ele encontra eg1.o
e o adiciona ao programa. Um arquivo de objeto na sequência de ligação é sempre adicionado ao programa. Agora, o programa faz uma referência hw
e não contém uma definição de hw
; mas não resta nada na sequência de ligação que possa fornecer a definição ausente. A referência a hw
acaba não resolvida e a ligação falha.
Segundo, exemplo 2 , com biblioteca compartilhadalibz
Uma biblioteca compartilhada não é um arquivo de arquivos de objetos ou algo parecido. É muito mais parecido com um programa que não tem uma main
função e, em vez disso, expõe vários outros símbolos que define, para que outros programas possam usá-los em tempo de execução.
Hoje muitos Linux distros configurar sua GCC toolchain para que seus pilotos linguagem ( gcc
, g++
, gfortran
etc.) instruir o vinculador do sistema ( ld
) para bibliotecas compartilhadas de link em um conforme a necessidade base. Você tem uma dessas distros.
Isso significa que, quando o vinculador encontra -lz
na sequência de vinculação e descobre que isso se refere à biblioteca compartilhada (por exemplo) /usr/lib/x86_64-linux-gnu/libz.so
, ele quer saber se alguma referência adicionada ao seu programa que ainda não foi definida possui definições que sejam exportado porlibz
Se isso for verdade, o vinculador não copiará nenhum pedaço libz
e o adicionará ao seu programa; em vez disso, ele apenas medicará o código do seu programa para que: -
Em tempo de execução, o carregador de programa do sistema carrega uma cópia libz
no mesmo processo que o seu programa sempre que ele carrega uma cópia do seu programa, para executá-lo.
No tempo de execução, sempre que seu programa se refere a algo definido em
libz
, essa referência usa a definição exportada pela cópia libz
no mesmo processo.
Seu programa deseja se referir a apenas uma coisa que possui uma definição exportada libz
, a saber, a função zlibVersion
, a qual é referida apenas uma vez, em eg2.c
. Se o vinculador adicionar essa referência ao seu programa e encontrar a definição exportada por libz
, a referência será resolvida
Mas quando você tenta vincular o programa como:
gcc -o eg2 -lz eg2.o
a ordem dos eventos está incorreta da mesma maneira que no exemplo 1. No momento em que o vinculador encontra -lz
, não há referências a nada no programa: eles estão todos inseridos eg2.o
, o que ainda não foi visto. Portanto, o vinculador decide que não tem utilidade libz
. Quando chega eg2.o
, o adiciona ao programa e, em seguida, tem uma referência indefinida zlibVersion
, a sequência de ligação está concluída; essa referência não foi resolvida e a ligação falha.
Por fim, a pkg-config
variação do exemplo 2 tem uma explicação agora óbvia. Após a expansão do shell:
gcc -o eg2 $(pkg-config --libs zlib) eg2.o
torna-se:
gcc -o eg2 -lz eg2.o
que é apenas o exemplo 2 novamente.
A ligação:
gcc -o eg2 -lz eg2.o
funciona muito bem para você!
(Ou: esse link funcionou bem para você, digamos, no Fedora 23, mas falha no Ubuntu 16.04)
Isso ocorre porque a distribuição na qual a ligação funciona é uma das que não configuram sua cadeia de ferramentas GCC para vincular bibliotecas compartilhadas conforme necessário .
Naquela época, era normal que sistemas do tipo Unix vinculassem bibliotecas estáticas e compartilhadas por regras diferentes. As bibliotecas estáticas em uma sequência de ligação foram vinculadas conforme a necessidade explicada no exemplo 1, mas as bibliotecas compartilhadas foram vinculadas incondicionalmente.
Esse comportamento é econômico no momento do link, porque o vinculador não precisa ponderar se uma biblioteca compartilhada é necessária pelo programa: se for uma biblioteca compartilhada, vincule-a. E a maioria das bibliotecas na maioria das ligações são bibliotecas compartilhadas. Mas também existem desvantagens: -
É antieconômico em tempo de execução , porque pode fazer com que as bibliotecas compartilhadas sejam carregadas junto com um programa, mesmo que não seja necessário.
As diferentes regras de vinculação para bibliotecas estáticas e compartilhadas podem ser confusas para programadores inexperientes, que podem não saber se -lfoo
o vínculo será resolvido com /some/where/libfoo.a
ou para /some/where/libfoo.so
e não entender a diferença entre bibliotecas compartilhadas e estáticas.
Esse trade-off levou à situação cismática hoje. Algumas distribuições alteraram suas regras de vinculação do GCC para bibliotecas compartilhadas para que o princípio conforme necessário se aplique a todas as bibliotecas. Algumas distros mantiveram o estilo antigo.
Se eu apenas fizer:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
certamente o gcc precisa compilar eg1.c
primeiro e depois vincular o arquivo de objeto resultante libmy_lib.a
. Então, como ele pode não saber que o arquivo de objeto é necessário ao fazer o link?
Porque compilar e vincular com um único comando não altera a ordem da sequência de vinculação.
Quando você executa o comando acima, gcc
descobre que deseja compilação + ligação. Por trás dos bastidores, ele gera um comando de compilação e o executa, gera um comando de ligação e o executa, como se você tivesse executado os dois comandos:
$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o
Assim, a ligação falhar tal como acontece se você não executar esses dois comandos. A única diferença que você nota na falha é que o gcc gerou um arquivo de objeto temporário no caso de compilação + link, porque você não está dizendo para usar eg1.o
. Nós vemos:
/tmp/ccQk1tvs.o: In function `main'
ao invés de:
eg1.o: In function `main':
A ordem na qual as bibliotecas vinculadas interdependentes são especificadas está incorreta
Colocar as bibliotecas interdependentes na ordem errada é apenas uma maneira pela qual você pode obter arquivos que precisam de definições das coisas que virão posteriormente no link do que os arquivos que fornecem as definições. Colocar as bibliotecas antes dos arquivos de objeto que se referem a eles é outra maneira de cometer o mesmo erro.
Dado o trecho de código de um tipo de modelo com um operador amigo (ou função);
template <typename T>
class Foo {
friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};
O operator<<
está sendo declarado como uma função não modelo. Para todos os tipos T
usados Foo
, é necessário que não haja modelos operator<<
. Por exemplo, se houver um tipo Foo<int>
declarado, deve haver uma implementação do operador da seguinte maneira;
std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}
Como não está implementado, o vinculador falha ao encontrá-lo e resulta no erro.
Para corrigir isso, você pode declarar um operador de modelo antes do Foo
tipo e, em seguida, declarar como amigo, a instanciação apropriada. A sintaxe é um pouco estranha, mas tem a seguinte aparência;
// 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
}
O código acima limita a amizade do operador à instanciação correspondente de Foo
, ou seja, a operator<< <int>
instanciação é limitada para acessar os membros privados da instanciação de Foo<int>
.
Alternativas incluem;
Permitindo que a amizade se estenda a todas as instanciações dos modelos, como segue;
template <typename T>
class Foo {
template <typename T1>
friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
// ...
};
Ou, a implementação para o operator<<
pode ser feita em linha dentro da definição de classe;
template <typename T>
class Foo {
friend std::ostream& operator<<(std::ostream& os, const Foo& a)
{ /*...*/ }
// ...
};
Observe que , quando a declaração do operador (ou função) aparece apenas na classe, o nome não está disponível para a pesquisa "normal", apenas para a pesquisa dependente de argumento da cppreference ;
Um nome declarado pela primeira vez em uma declaração de amigo na classe ou no modelo de classe X se torna um membro do espaço para nome mais interno de X, mas não está acessível para pesquisa (exceto a pesquisa dependente de argumento que considera X), a menos que uma declaração correspondente no escopo do espaço para nome seja forneceu...
Há mais leituras sobre amigos modelo na cppreference e no C ++ FAQ .
Listagem de código mostrando as técnicas acima .
Como uma observação lateral ao exemplo de código com falha; O g ++ alerta sobre isso da seguinte maneira
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)
Erros do vinculador podem ocorrer quando um arquivo de cabeçalho e sua biblioteca compartilhada associada (arquivo .lib) ficam fora de sincronia. Deixe-me explicar.
Como os vinculadores funcionam? O vinculador combina uma declaração de função (declarada no cabeçalho) com sua definição (na biblioteca compartilhada) comparando suas assinaturas. Você pode obter um erro de vinculador se o vinculador não encontrar uma definição de função que corresponda perfeitamente.
Ainda é possível obter um erro de vinculador, mesmo que a declaração e a definição pareçam corresponder? Sim! Eles podem ter a mesma aparência no código fonte, mas isso realmente depende do que o compilador vê. Essencialmente, você pode acabar com uma situação como esta:
// header1.h
typedef int Number;
void foo(Number);
// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically
Observe como, embora as duas declarações de função pareçam idênticas no código-fonte, mas são realmente diferentes de acordo com o compilador.
Você pode perguntar como alguém acaba em uma situação como essa? Inclua caminhos, é claro! Se, ao compilar a biblioteca compartilhada, o caminho de inclusão levar header1.h
e você acabar usandoheader2.h
em seu próprio programa, você ficará coçando o cabeçalho, imaginando o que aconteceu (trocadilho intencional).
Um exemplo de como isso pode acontecer no mundo real é explicado abaixo.
Eu tenho dois projetos: graphics.lib
e main.exe
. Ambos os projetos dependem common_math.h
. Suponha que a biblioteca exporte a seguinte função:
// graphics.lib
#include "common_math.h"
void draw(vec3 p) { ... } // vec3 comes from common_math.h
E então você vai em frente e inclui a biblioteca em seu próprio projeto.
// main.exe
#include "other/common_math.h"
#include "graphics.h"
int main() {
draw(...);
}
Estrondo! Você recebe um erro no vinculador e não tem idéia do porquê está falhando. O motivo é que a biblioteca comum usa versões diferentes da mesma inclusãocommon_math.h
(eu tornei óbvio aqui no exemplo, incluindo um caminho diferente, mas nem sempre é tão óbvio. Talvez o caminho da inclusão seja diferente nas configurações do compilador) .
Observe neste exemplo, o vinculador diria que não foi possível encontrar draw()
, quando na realidade você sabe que obviamente está sendo exportado pela biblioteca. Você pode passar horas coçando a cabeça imaginando o que deu errado. O problema é que o vinculador vê uma assinatura diferente porque os tipos de parâmetro são ligeiramente diferentes. No exemplo, vec3
é um tipo diferente nos dois projetos no que diz respeito ao compilador. Isso pode acontecer porque eles vêm de dois arquivos de inclusão ligeiramente diferentes (talvez os arquivos de inclusão venham de duas versões diferentes da biblioteca).
DUMPBIN é seu amigo, se você estiver usando o Visual Studio. Tenho certeza que outros compiladores têm outras ferramentas semelhantes.
O processo é assim:
[1] Por projeto, quero dizer um conjunto de arquivos de origem que são vinculados para produzir uma biblioteca ou um executável.
EDIT 1: Reescreva a primeira seção para ser mais fácil de entender. Comente abaixo para me informar se algo mais precisa ser corrigido. Obrigado!
UNICODE
Definições inconsistentesUma compilação UNICODE do Windows é criada com TCHAR
etc. sendo definida como wchar_t
etc. Quando não UNICODE
compilada com TCHAR
definida como compilada com definida como char
etc. Essas UNICODE
e _UNICODE
definições afetam todos os tipos de " T
" strings ; LPTSTR
, LPCTSTR
E seus alces.
Construir uma biblioteca com UNICODE
definido e tentar vinculá-lo em um projeto onde UNICODE
não está definido resultará em erros do vinculador, pois haverá uma incompatibilidade na definição de TCHAR
; char
vs. wchar_t
.
O erro geralmente inclui uma função, um valor com um tipo char
ou wchar_t
derivado, que pode incluir std::basic_string<>
etc. também. Ao navegar pela função afetada no código, muitas vezes haverá uma referência a TCHAR
ou std::basic_string<TCHAR>
etc. Esse é um sinal revelador de que o código foi originalmente destinado a uma compilação UNICODE e Caractere de Byte Múltiplo (ou "restrito") .
Para corrigir isso, construa todas as bibliotecas e projetos necessários com uma definição consistente de UNICODE
(e _UNICODE
).
Isso pode ser feito com qualquer um deles;
#define UNICODE
#define _UNICODE
Ou nas configurações do projeto;
Propriedades do projeto> Geral> Padrões do projeto> Conjunto de caracteres
Ou na linha de comando;
/DUNICODE /D_UNICODE
A alternativa também é aplicável, se o UNICODE não se destina a ser usado, verifique se as definições não estão definidas e / ou a configuração de vários caracteres é usada nos projetos e aplicada de forma consistente.
Não se esqueça de ser consistente entre as versões "Release" e "Debug".
Uma "limpeza" da compilação pode remover a "madeira morta" que pode ser deixada nas compilações anteriores, compilações com falha, compilações incompletas e outros problemas de compilação relacionados ao sistema de compilação.
Em geral, o IDE ou build inclui alguma forma de função "limpa", mas isso pode não estar configurado corretamente (por exemplo, em um arquivo de composição manual) ou pode falhar (por exemplo, os binários intermediários ou resultantes são somente leitura).
Depois que a "limpeza" for concluída, verifique se a "limpeza" foi bem-sucedida e se todo o arquivo intermediário gerado (por exemplo, um makefile automatizado) foi removido com êxito.
Esse processo pode ser visto como um recurso final, mas geralmente é um bom primeiro passo ; especialmente se o código relacionado ao erro foi adicionado recentemente (localmente ou no repositório de origem).
const
declarações / definições de variáveis (apenas C ++)Para pessoas provenientes de C, pode ser uma surpresa que em C ++ const
variáveis globais tenham ligação interna (ou estática). Em C, esse não foi o caso, pois todas as variáveis globais estão implicitamente extern
(ou seja, quando a static
palavra-chave está ausente).
Exemplo:
// 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
}
correto seria usar um arquivo de cabeçalho e incluí-lo em file2.cpp e file1.cpp
extern const int test;
extern int test2;
Como alternativa, pode-se declarar a const
variável em file1.cpp com explícitaextern
Embora essa seja uma pergunta bastante antiga com várias respostas aceitas, gostaria de compartilhar como resolver um erro obscuro de "referência indefinida a".
Eu estava usando um alias para me referir a std::filesystem::path
: sistema de arquivos está na biblioteca padrão desde C ++ 17, mas meu programa também precisava compilar em C ++ 14, então decidi usar um alias variável:
#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
Digamos que eu tenho três arquivos: main.cpp, file.h, file.cpp:
Observe as diferentes bibliotecas usadas em main.cpp e file.h. Desde main.cpp # include'd " file.h " depois < filesystem >, a versão do sistema de arquivos usado houve o C ++ 17 um . Eu costumava compilar o programa com os seguintes comandos:
$ g++ -g -std=c++17 -c main.cpp
-> compila main.cpp para main.o
$ g++ -g -std=c++17 -c file.cpp
-> compila file.cpp e file.h para file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs
-> links main.o e file.o
Desta forma, qualquer função contida em file.o e utilizado em main.o que necessáriopath_t
deu erros "referência indefinida" porque main.o referido std::filesystem::path
mas file.o para std::experimental::filesystem::path
.
Para corrigir isso, eu só precisava alterar <experimental :: filesystem> em file.h para <filesystem> .
O comportamento padrão do gcc é que todos os símbolos estão visíveis. No entanto, quando as unidades de conversão são construídas com a opção -fvisibility=hidden
, apenas as funções / símbolos marcados com __attribute__ ((visibility ("default")))
são externos no objeto compartilhado resultante.
Você pode verificar se os símbolos que você está procurando são externos, invocando:
# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL
os símbolos ocultos / locais são mostrados nm
com o tipo de símbolo em minúsculas, por exemplo, em t
vez de `T para a seção de código:
nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL
Você também pode usar nm
com a opção -C
para desmembrar os nomes (se C ++ foi usado).
Semelhante às dlls do Windows, seria possível marcar funções públicas com um define, por exemplo, DLL_PUBLIC
definido como:
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
DLL_PUBLIC int my_public_function(){
...
}
Que corresponde aproximadamente à versão Windows / MSVC:
#ifdef BUILDING_DLL
#define DLL_PUBLIC __declspec(dllexport)
#else
#define DLL_PUBLIC __declspec(dllimport)
#endif
Mais informações sobre visibilidade podem ser encontradas no wiki do gcc.
Quando uma unidade de conversão é compilada com -fvisibility=hidden
os símbolos resultantes, ainda possui ligação externa (mostrada com o tipo de letra maiúscula por nm
) e pode ser usada para ligação externa sem problemas se os arquivos de objeto se tornarem parte de bibliotecas estáticas. A ligação se torna local somente quando os arquivos de objeto são vinculados a uma biblioteca compartilhada.
Para descobrir quais símbolos em um arquivo de objeto estão ocultos, execute:
>>> objdump -t XXXX.o | grep hidden
0000000000000000 g F .text 000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g F .text 000000000000000b .hidden HIDDEN_SYMBOL2
nm -CD
ou nm -gCD
para visualizar símbolos externos. Veja também Visibilidade no wiki do GCC.
Arquiteturas diferentes
Você pode ver uma mensagem como:
library machine type 'x64' conflicts with target machine type 'X86'
Nesse caso, significa que os símbolos disponíveis são para uma arquitetura diferente daquela para a qual você está compilando.
No Visual Studio, isso ocorre devido à "Plataforma" errada e você precisa selecionar a correta ou instalar a versão correta da biblioteca.
No Linux, isso pode ocorrer devido à pasta da biblioteca errada (usando em lib
vez de, lib64
por exemplo).
No MacOS, existe a opção de enviar as duas arquiteturas no mesmo arquivo. Pode ser que o link espere que ambas as versões estejam lá, mas apenas uma está. Também pode ser um problema com a pasta errada lib
/ lib64
onde a biblioteca é selecionada.