O que é um erro de símbolo externo não definido / referência não definido e como corrigi-lo?

1493

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.

Luchian Grigore
fonte
@LuchianGrigore 'sinta-se à vontade para adicionar uma resposta' Preferi adicionar o link relevante (IMHO) à sua resposta principal, se você desejar.
πάντα ῥεῖ
19
Esta pergunta é muito geral para admitir uma resposta útil. Não acredito no número de mais de 1000 pessoas de reputação comentando isso, sem descartar a questão. Enquanto isso, vejo muitas perguntas sensatas e muito úteis para mim que estão fechadas.
Albert van der Horst
10
@AlbertvanderHorst "Esta pergunta é muito geral para admitir uma resposta útil" As respostas abaixo não são úteis?
LF
4
Eles podem ser úteis, assim como muitas respostas para perguntas sinalizadas como genéricas demais.
Albert van der Horst
1
Eu gostaria de ver um exemplo reproduzível mínimo como algo que pedimos para a maioria dos novos usuários, honestamente. Não quero dizer nada com isso, é apenas - não podemos esperar que as pessoas sigam as regras que não impomos a nós mesmos.
Danilo

Respostas:

850

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é] .

  1. 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]
  2. 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]
  3. 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]
  4. 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]
  5. 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]
  6. Os tokens literais de sequência adjacente são concatenados.
  7. 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]
  8. As unidades de tradução traduzida e as unidades de instanciação são combinadas da seguinte forma: [SNIP]
  9. 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 aem 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 .libarquivos. 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 LNK2019para 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:

Luchian Grigore
fonte
16
Pessoalmente, acho que as mensagens de erro do vinculador MS são tão legíveis quanto os erros do GCC. Eles também têm a vantagem de incluir nomes mutilados e não mutilados para o externo não resolvido. Ter o nome desconfigurado pode ser útil quando você precisa olhar diretamente para as bibliotecas ou arquivos de objetos para ver qual pode ser o problema (por exemplo, uma incompatibilidade de convenção de chamada). Além disso, não tenho certeza de qual versão do MSVC produziu os erros aqui, mas as versões mais recentes incluem o nome (desconfigurado e não desconfigurado) da função referente ao símbolo externo não resolvido.
Michael Burr
5
David Drysdale escreveu um ótimo artigo sobre como os linkers funcionam: Guia para Iniciantes de Linkers . Dado o tópico desta pergunta, achei que poderia ser útil.
Pressacco
@TankorSmash Use gcc? MinGW para ser mais preciso.
Doug65536 15/10
1
@luchian seria bom se você adicionar o correto, corrigindo os erros acima
Phalgun
178

Membros da classe:

Um virtualdestruidor 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 virtualmé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;
};

virtualMembros não pertencentes à classe

Alguns 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 inlinepara impedir uma definição múltipla.

Todos os métodos de membro usados ​​precisam ser definidos se usados.

Um erro comum é esquecer de qualificar o nome:

struct A
{
   void foo();
};

void foo() {}

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

A definição deve ser

void A::foo() {}

staticos 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 constmembro 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 constos membros de dados.

Luchian Grigore
fonte
Apenas pensei em enfatizar que é possível fazer as duas coisas, e o dtor não é realmente uma exceção. (não é óbvio de seu texto à primeira vista.)
Deduplicator
112

Falha ao vincular bibliotecas / arquivos de objeto apropriados ou compilar arquivos de implementação

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 libraryNameaqui é 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.somas 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 -lou -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 libarquivo é gerado (em uso comum). Para usar os símbolos em um projeto separado, você precisará incluir os libarquivos nas configurações do projeto. Isso é feito na seção Vinculador das propriedades do projeto, em Input -> Additional Dependencies. (o caminho para o libarquivo deve ser adicionado Linker -> General -> Additional Library Directories) Ao usar uma biblioteca de terceiros que é fornecida com um libarquivo, 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".

Luchian Grigore
fonte
1
Seria bom se você explicitamente explicasse o erro comum em gcc main.cvez 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).
MM
101

Declarado mas não definiu uma variável ou funçã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

  • Função / variável declarada em um espaço para nome, definida em outro.
  • Função / variável declarada como membro da classe, definida como global (ou vice-versa).
  • O tipo de retorno da função, o número e os tipos de parâmetros e a convenção de chamada não concordam exatamente.

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.

Luchian Grigore
fonte
No VS, os arquivos cpp correspondentes aos do cabeçalho #includesnão adicionados ao diretório de origem também se enquadram na categoria de definições ausentes.
30518 Laurie Stearn
86

A ordem na qual as bibliotecas vinculadas interdependentes são especificadas está incorreta.

A ordem na qual as bibliotecas estão vinculadas importa se as bibliotecas dependem uma da outra. Em geral, se a biblioteca Adepende da biblioteca B, libA DEVE aparecer antes libBnas 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!

Svalorzen
fonte
O fato curioso é que, no meu caso, eu tinha um arquivo de objeto que depende de uma biblioteca compartilhada. Eu tive que modificar o Makefile e colocar a biblioteca DEPOIS do objeto com o gcc 4.8.4 no Debian. No Centos 6.5 com o gcc 4.4, o Makefile funcionou sem problemas.
Marco Sulla
74

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.

Kastaneda
fonte
71

Os símbolos foram definidos em um programa C e usados ​​no código C ++.

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"
}
Luchian Grigore
fonte
5
Ou, inversamente, se você desenvolver uma biblioteca C, uma boa regra é proteger o (s) arquivo (s) de cabeçalho (s), envolvendo todas as declarações exportadas com #ifdef __cplusplus [\n] extern"C" { [\n] #endife #ifdef __cplusplus [\n] } [\n] #endif( [\n]sendo retorno de carro real, mas não posso escrever isso corretamente no comentário).
Bentoy13
Como no comentário acima, a seção 'Criando cabeçalhos de idiomas mistos' aqui ajudou: oracle.com/technetwork/articles/servers-storage-dev/…
zanbri
Realmente me ajudou! Este foi o meu caso quando procurei esta questão #
knightofhorizon
Isso também pode acontecer se você incluir o arquivo de cabeçalho C ++ comum por acidente, cercado por C : externoextern "C" { #include <myCppHeader.h> } .
ComFreek 25/03
68

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.

sgryzko
fonte
1
Correto - isso acontece quando as bibliotecas têm uma dependência cíclica.
Luchian Grigore
1
Ampliei sua resposta e vinculei a principal. Obrigado.
Luchian Grigore
57

Importar / exportar incorretamente métodos / classes entre módulos / dll (específico do compilador).

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_MODULEseria 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
{
};
Luchian Grigore
fonte
2
Para ser concluída, esta resposta deve mencionar os arquivos do GCC visibilitye do Windows .def, pois eles também influenciam o nome e a presença do símbolo.
rubenvb
@rubenvb Eu não uso .defarquivos há muito tempo. Sinta-se livre para adicionar uma resposta ou editar esta.
Luchian Grigore
57

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:

  • Todos os arquivos de objeto gerados em tempo de compilação
  • Todas as bibliotecas (.lib) especificadas explícita ou implicitamente como dependências adicionais desse aplicativo de construção.

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:

  1. Existe definição

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.

  1. A definição nã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 Dependencyseu projeto. Agora, o vinculador reportará um LNK2019, pois a definição não existe no escopo de pesquisa atual.

Nima Soroush
fonte
1
Obrigado pela explicação sistemática. Eu pude entender as outras respostas aqui depois de ler esta.
displayName
56

Implementações de modelo não visíveis.

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 implarquivo 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::foopara 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 .

Luchian Grigore
fonte
1
Mais informações sobre "os modelos devem ser definidos no cabeçalho" podem ser encontradas em stackoverflow.com/questions/495021
PlasmaHH
41

referência indefinida WinMain@16ou referência de ponto de entrada 'incomum' semelhantemain() (especialmente para)

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.

πάντα ῥεῖ
fonte
2
Não posso deixar de apontar essa pergunta e o fato de que isso é causado com mais freqüência por não ter nenhuma função principal do que não ter WinMain. Programas válidos em C ++ precisam de um main.
chris
36

Além disso, se você estiver usando bibliotecas de terceiros, verifique se possui os binários corretos de 32/64 bits

Dula
fonte
34

A Microsoft oferece um #pragmapara referenciar a biblioteca correta no momento do link;

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

Além do caminho da biblioteca, incluindo o diretório da biblioteca, este deve ser o nome completo da biblioteca.

Niall
fonte
34

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 packagespasta dentro do diretório da solução) localizando packagename\build\native\packagename.targetse dentro desse arquivo, copiando todas as v110seções. Mudei v110para para v120nos 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.

Malvineous
fonte
1
Isso parece excessivamente específico - talvez um novo tópico seja um lugar melhor para esta resposta.
Luchian Grigore
3
@LuchianGrigore: Eu queria postar aqui, pois a pergunta era exatamente esse problema, mas estava marcada como uma duplicata dessa pergunta, então não consegui responder lá. Então, eu postei minha resposta aqui.
Malvineous
Essa pergunta já tem uma resposta aceita. Está marcado como duplicado porque a causa geral está listada acima. O que aconteceria se tivéssemos uma resposta aqui para todos os problemas com uma biblioteca não incluída?
Luchian Grigore
6
@LuchianGrigore: esse problema não é específico de uma biblioteca, afeta todas as bibliotecas usando o sistema de gerenciamento de pacotes do Visual Studio. Por acaso encontrei a outra pergunta porque nós dois tivemos problemas com a libpng. Eu também tive o mesmo problema (com a mesma solução) para libxml2, libiconv e glew. Essa pergunta é sobre um problema com o sistema de gerenciamento de pacotes do Visual Studio, e minha resposta explica o motivo e fornece uma solução alternativa. Alguém acabou de ver "externo não resolvido" e assumiu que era um problema de vinculador padrão quando na verdade é um problema de gerenciamento de pacotes.
Malvineous
34

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?

  • Pesquisa do Visual Studio no arquivo .vcxproj e começa a compilar cada arquivo que possui a extensão .cpp. A ordem de compilação é indefinida. Portanto, você não deve presumir que o arquivo main.cpp seja compilado primeiro
  • Se os arquivos .cpp dependem de arquivos .h adicionais, a fim de encontrar símbolos que podem ou não ser definidos no arquivo .cpp
  • Se existir um arquivo .cpp no ​​qual o compilador não conseguiu encontrar um símbolo, um erro de tempo do compilador gera a mensagem Não foi possível encontrar símbolo x
  • Para cada arquivo com extensão .cpp é gerado um arquivo de objeto .o e também o Visual Studio grava a saída em um arquivo chamado ProjectName.Cpp.Clean.txt que contém todos os arquivos de objeto que devem ser processados ​​pelo vinculador.

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

  • Analise todos os arquivos de objetos e encontre a definição que foi declarada apenas nos cabeçalhos (por exemplo: o código de um método de uma classe, como mencionado nas respostas anteriores, ou evento a inicialização de uma variável estática que é membro dentro de uma classe)
  • Se um símbolo não podiam ser encontrados em arquivos de objeto ele também é procurado em Libraries.For adicionais adicionando uma nova biblioteca a um projeto de propriedades de configuração -> Diretórios VC ++ -> Biblioteca Diretórios e aqui você especificou pasta adicional para pesquisar bibliotecas e propriedades de configuração -> Vinculador -> Entrada para especificar o nome da biblioteca. -Se o vinculador não conseguir encontrar o símbolo que você escreve em um .cpp, ele gera um erro de tempo do vinculador que pode parecer error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

Observação

  1. Depois que o Linker encontra um símbolo, ele não o pesquisa em outras bibliotecas.
  2. A ordem de vincular bibliotecas é importante .
  3. Se Linker encontrar um símbolo externo em uma biblioteca estática, ele incluirá o símbolo na saída do projeto.No entanto, se a biblioteca for compartilhada (dinâmica), ele não incluirá o código (símbolos) na saída, mas as falhas em tempo de execução podem ocorrer

Como resolver esse tipo de erro

Erro de tempo do compilador:

  • Certifique-se de escrever o seu projeto c ++ sintático corretamente.

Erro de tempo do vinculador

  • Defina todo o seu símbolo que você declara nos arquivos de cabeçalho
  • Use #pragma oncepara permitir que o compilador não inclua um cabeçalho, se já estiver incluído no .cpp atual, compilado
  • Verifique se a sua biblioteca externa não contém símbolos que possam entrar em conflito com outros símbolos que você definiu nos arquivos de cabeçalho
  • Quando você usa o modelo para certificar-se de incluir a definição de cada função de modelo no arquivo de cabeçalho para permitir que o compilador gere o código apropriado para qualquer instanciação.
eFarzad
fonte
Sua resposta não é específica para o visual studio? A pergunta não especifica nenhuma ferramenta IDE / compilador, portanto, torna sua resposta inútil para a parte que não é do visual studio.
Victor Polevoy
Você está certo . Mas cada em cada processo IDE de compilação / Vinculação está sendo feito um pouco differently.But os arquivos são processados exatamente o mesmo (mesmo g ++ fazer a mesma coisa quando se analisar as bandeiras ..)
O problema não é realmente sobre o IDE, mas sobre uma resposta para vincular problemas. Os problemas de vinculação não estão relacionados ao IDE, mas ao processo de compilação e compilação.
Victor Polevoy 13/08/2015
Sim. Mas o processo de compilação / vinculação está sendo feito no g ++ / Visual Studio (compilador fornecido pela Microsoft para VS) / Eclipse / Net Beans da mesma maneira
29

Um erro no compilador / IDE

Recentemente, 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:

  • Limpe o projeto (alguns IDEs têm uma opção para fazer isso, você também pode fazê-lo manualmente, excluindo os arquivos de objeto)
  • Tente iniciar um novo projeto, copiando todo o código-fonte do original.
developerbmw
fonte
5
Acreditar que suas ferramentas estão quebradas provavelmente o afastará da verdadeira causa. É muito mais provável que você tenha cometido um erro do que um compilador causou o seu problema. Limpar sua solução ou recriar sua configuração de compilação pode corrigir erros de compilação, mas isso não significa que há um erro no compilador. O link "descobriu que era um bug" não é confirmado pela Microsoft e não é reproduzível.
JDiMatteo
4
@JDiMatteo Existem 21 respostas para esta pergunta e, portanto, uma quantidade significativa de respostas não será uma solução "provável". Se você descartar todas as respostas abaixo do limite de probabilidade, esta página se tornará inútil, pois a maioria dos casos comuns é facilmente detectada.
Developerbmw
27

Use o vinculador para ajudar a diagnosticar o erro

A maioria dos vinculadores modernos inclui uma opção detalhada que é impressa em vários graus;

  • Chamada de link (linha de comando),
  • Dados sobre quais bibliotecas estão incluídas no estágio de link,
  • A localização das bibliotecas,
  • Caminhos de pesquisa usados.

Para gcc e clang; você normalmente adicionaria -v -Wl,--verboseou -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.

Niall
fonte
26

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.

Kiriloff
fonte
25

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.

Plankalkül
fonte
20

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 -ltbbfazer/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2

JDiMatteo
fonte
20

Sua ligação consome bibliotecas antes dos arquivos de objeto que se referem a eles

  • Você está tentando compilar e vincular seu programa à cadeia de ferramentas GCC.
  • Sua ligação especifica todas as bibliotecas e caminhos de pesquisa de bibliotecas necessários
  • Se libfoodepende libbar, então sua ligação coloca corretamente libfooantes libbar.
  • Sua ligação falha com erros de undefined reference to algo .
  • Mas todos os indefinidos algo s são declarados nos arquivos de cabeçalho você tem #included e são de fato definido nas bibliotecas que você está ligando.

Os exemplos estão em C. Eles poderiam igualmente ser C ++

Um exemplo mínimo que envolve uma biblioteca estática que você construiu

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.ae 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

Um exemplo mínimo que envolve uma biblioteca de sistema compartilhada, a biblioteca de compactação 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 libze 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'

O que você está fazendo de errado?

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-configvariação do exemplo 2 corretamente:

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

A explicação

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_libna 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.ose, e somente se, ele já souber que o programa se refere a hwum 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.obiblioteca e a adicionará ao seu programa. Em seguida, seu programa contém uma definição para hw, portanto, suas referências hwsã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.oao programa e não tem mais uso para libmy_lib.a.

Em seguida, ele encontra eg1.oe 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 hwe 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 hwacaba 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 mainfunçã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++, gfortranetc.) 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 -lzna 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 libze 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 libzno 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 libzno 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 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-configvariaçã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.

Eu posso reproduzir o problema no exemplo 1, mas não no exemplo 2

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 -lfooo vínculo será resolvido com /some/where/libfoo.aou para /some/where/libfoo.soe 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.

Por que ainda sinto esse problema, mesmo que eu compile e vincule ao mesmo tempo?

Se eu apenas fizer:

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

certamente o gcc precisa compilar eg1.cprimeiro 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, gccdescobre 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':

Veja também

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.

Mike Kinghan
fonte
18

Modelos de amizade ...

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 Tusados 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 Footipo 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)

Niall
fonte
16

Quando seus caminhos de inclusão são diferentes

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.he 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.

Elaboração adicional com um exemplo

Eu tenho dois projetos: graphics.libe 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).

Depurando o vinculador

DUMPBIN é seu amigo, se você estiver usando o Visual Studio. Tenho certeza que outros compiladores têm outras ferramentas semelhantes.

O processo é assim:

  1. Observe o nome estranho e mutilado fornecido no erro do vinculador. (por exemplo, draw @ graphics @ XYZ).
  2. Despejar os símbolos exportados da biblioteca em um arquivo de texto.
  3. Procure o símbolo de interesse exportado e observe que o nome desconfigurado é diferente.
  4. Preste atenção ao motivo pelo qual os nomes mutilados acabaram diferentes. Você seria capaz de ver que os tipos de parâmetro são diferentes, mesmo que pareçam iguais no código-fonte.
  5. Razão pela qual eles são diferentes. No exemplo acima, eles são diferentes devido a diferentes arquivos de inclusão.

[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!

fafaro
fonte
15

UNICODEDefinições inconsistentes

Uma compilação UNICODE do Windows é criada com TCHARetc. sendo definida como wchar_tetc. Quando não UNICODEcompilada com TCHARdefinida como compilada com definida como charetc. Essas UNICODEe _UNICODEdefinições afetam todos os tipos de " T" strings ; LPTSTR, LPCTSTRE seus alces.

Construir uma biblioteca com UNICODEdefinido e tentar vinculá-lo em um projeto onde UNICODEnão está definido resultará em erros do vinculador, pois haverá uma incompatibilidade na definição de TCHAR; charvs. wchar_t.

O erro geralmente inclui uma função, um valor com um tipo charou wchar_tderivado, 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 TCHARou 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).

  1. Isso pode ser feito com qualquer um deles;

    #define UNICODE
    #define _UNICODE
  2. Ou nas configurações do projeto;

    Propriedades do projeto> Geral> Padrões do projeto> Conjunto de caracteres

  3. 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".

Niall
fonte
14

Limpar e reconstruir

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).

Niall
fonte
10

Falta "externo" nas constdeclarações / definições de variáveis ​​(apenas C ++)

Para pessoas provenientes de C, pode ser uma surpresa que em C ++ constvariá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 staticpalavra-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 constvariável em file1.cpp com explícitaextern

Andreas H.
fonte
8

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".

Versões diferentes das bibliotecas

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:

  • file.h # include < experimental :: filesystem > e contém o código acima
  • file.cpp , a implementação de file.h, # include " file.h "
  • main.cpp # include < sistema de arquivos > e " arquivo.h "

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::pathmas file.o para std::experimental::filesystem::path.

Resolução

Para corrigir isso, eu só precisava alterar <experimental :: filesystem> em file.h para <filesystem> .

Stypox
fonte
5

Ao vincular contra bibliotecas compartilhadas, verifique se os símbolos usados ​​não estão ocultos.

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 nmcom o tipo de símbolo em minúsculas, por exemplo, em tvez 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 nmcom a opção -Cpara 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_PUBLICdefinido 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=hiddenos 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
ead
fonte
Você deve usar nm -CDou nm -gCDpara visualizar símbolos externos. Veja também Visibilidade no wiki do GCC.
JWW
2

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 libvez de, lib64por 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/ lib64onde a biblioteca é selecionada.

Matthieu Brucher
fonte