¿Qué es un error de símbolo externo de referencia indefinido / no resuelto y cómo lo soluciono?

1493

¿Qué son los errores de símbolo externo de referencia indefinida / sin resolver? ¿Cuáles son las causas comunes y cómo solucionarlas / prevenirlas?

Siéntase libre de editar / agregar el suyo.

Luchian Grigore
fuente
@LuchianGrigore 'siéntase libre de agregar una respuesta' Preferí agregar el enlace relevante (en mi humilde opinión) su respuesta principal, si desea permitirlo.
πάντα ῥεῖ
19
Esta pregunta es demasiado general para admitir una respuesta útil. No puedo creer la cantidad de más de 1000 personas de reputación que comentan esto, sin derribar la pregunta. Mientras tanto, veo muchas preguntas que son sensatas y muy útiles para mí que están cerradas.
Albert van der Horst
10
@AlbertvanderHorst "Esta pregunta es demasiado general para admitir una respuesta útil" ¿Las respuestas a continuación no son útiles?
LF
44
Pueden ser útiles, al igual que muchas respuestas a preguntas marcadas como demasiado generales.
Albert van der Horst
1
Sinceramente, me gustaría ver un ejemplo reproducible mínimo como algo que pedimos a la mayoría de los nuevos usuarios. No quiero decir nada con eso, es solo que no podemos esperar que las personas sigan las reglas que no nos informamos.
Danilo

Respuestas:

850

La compilación de un programa C ++ se lleva a cabo en varios pasos, como se especifica en 2.2 (créditos a Keith Thompson para la referencia) :

La precedencia entre las reglas de sintaxis de la traducción se especifica mediante las siguientes fases [ver nota al pie] .

  1. Los caracteres del archivo de origen físico se asignan, de una manera definida por la implementación, al conjunto de caracteres de origen básico (introduciendo caracteres de nueva línea para indicadores de fin de línea) si es necesario. [RECORTE]
  2. Cada instancia de un carácter de barra diagonal inversa (\) seguida inmediatamente por un carácter de nueva línea se elimina, uniendo líneas de origen físicas para formar líneas de origen lógicas. [RECORTE]
  3. El archivo fuente se descompone en tokens de preprocesamiento (2.5) y secuencias de caracteres de espacio en blanco (incluidos los comentarios). [RECORTE]
  4. Se ejecutan las directivas de preprocesamiento, se amplían las invocaciones de macros y se ejecutan las expresiones de operador _Pragma unary. [RECORTE]
  5. Cada miembro del conjunto de caracteres de origen en un literal de caracteres o un literal de cadena, así como cada secuencia de escape y nombre de carácter universal en un literal de caracteres o un literal de cadena no sin formato, se convierte en el miembro correspondiente del conjunto de caracteres de ejecución; [RECORTE]
  6. Los tokens literales de cadena adyacentes se concatenan.
  7. Los caracteres de espacio en blanco que separan las fichas ya no son significativos. Cada token de preprocesamiento se convierte en un token. (2.7) Los tokens resultantes se analizan sintáctica y semánticamente y se traducen como una unidad de traducción. [RECORTE]
  8. Las unidades de traducción traducidas y las unidades de instanciación se combinan de la siguiente manera: [SNIP]
  9. Todas las referencias de entidades externas se resuelven. Los componentes de la biblioteca están vinculados para satisfacer referencias externas a entidades no definidas en la traducción actual. Todos los resultados del traductor se recopilan en una imagen del programa que contiene la información necesaria para la ejecución en su entorno de ejecución. (énfasis mío)

[nota al pie] Las implementaciones deben comportarse como si ocurrieran estas fases separadas, aunque en la práctica las diferentes fases podrían plegarse juntas.

Los errores especificados ocurren durante esta última etapa de compilación, más comúnmente conocida como vinculación. Básicamente significa que compiló un montón de archivos de implementación en archivos de objetos o bibliotecas y ahora desea que funcionen juntos.

Digamos que definiste el símbolo aen a.cpp. Ahora, b.cpp declaró ese símbolo y lo usó. Antes de vincular, simplemente supone que ese símbolo se definió en algún lugar , pero aún no le importa dónde. La fase de vinculación es responsable de encontrar el símbolo y vincularlo correctamente b.cpp(bueno, en realidad al objeto o biblioteca que lo usa).

Si está utilizando Microsoft Visual Studio, verá que los proyectos generan .libarchivos. Contienen una tabla de símbolos exportados y una tabla de símbolos importados. Los símbolos importados se resuelven en las bibliotecas con las que se vincula y los símbolos exportados se proporcionan para las bibliotecas que usan ese.lib (si corresponde).

Existen mecanismos similares para otros compiladores / plataformas.

Mensajes de error son comunes error LNK2001, error LNK1120, error LNK2019para Microsoft Visual Studio y undefined reference to symbolName de GCC .

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

generará los siguientes errores con GCC :

/home/AbiSfw/ccvvuHoX.o: In function `main':
prog.cpp:(.text+0x10): undefined reference to `x'
prog.cpp:(.text+0x19): undefined reference to `foo()'
prog.cpp:(.text+0x2d): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD1Ev[B::~B()]+0xb): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD0Ev[B::~B()]+0x12): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1Y[typeinfo for Y]+0x8): undefined reference to `typeinfo for X'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1B[typeinfo for B]+0x8): undefined reference to `typeinfo for A'
collect2: ld returned 1 exit status

y errores similares con Microsoft Visual Studio :

1>test2.obj : error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
1>test2.obj : error LNK2001: unresolved external symbol "int x" (?x@@3HA)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall A::~A(void)" (??1A@@UAE@XZ)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall X::foo(void)" (?foo@X@@UAEXXZ)
1>...\test2.exe : fatal error LNK1120: 4 unresolved externals

Las causas comunes incluyen:

Luchian Grigore
fuente
16
Personalmente, creo que los mensajes de error del enlazador de MS son tan legibles como los errores de GCC. También tienen la ventaja de incluir tanto los nombres destrozados como los no destrozados para el externo no resuelto. Tener el nombre destrozado puede ser útil cuando necesita mirar las bibliotecas o los archivos de objetos directamente para ver cuál podría ser el problema (por ejemplo, una discrepancia de convención de llamada). Además, no estoy seguro de qué versión de MSVC produjo los errores aquí, pero las versiones más nuevas incluyen el nombre (tanto mutilado como no) de la función que hace referencia al símbolo externo no resuelto.
Michael Burr
55
David Drysdale escribió un excelente artículo sobre cómo funcionan los enlazadores: Guía para principiantes sobre enlazadores . Dado el tema de esta pregunta, pensé que podría resultar útil.
Pressacco
@TankorSmash ¿Utiliza gcc? MinGW para ser más preciso.
doug65536
1
@luchian sería bueno si agregas el correcto, arreglando los errores anteriores
Phalgun
178

Miembros de la clase:

Un virtualdestructor puro necesita una implementación.

Declarar un destructor puro aún requiere que lo defina (a diferencia de una función normal):

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

Esto sucede porque los destructores de clase base se llaman cuando el objeto se destruye implícitamente, por lo que se requiere una definición.

virtual los métodos deben implementarse o definirse como puros.

Esto es similar a los no virtualmétodos sin definición, con el razonamiento adicional de que la declaración pura genera una vtable ficticia y puede obtener el error del vinculador sin usar la función:

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 esto funcione, declare X::foo()como puro:

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

virtualMiembros no pertenecientes a la clase

Algunos miembros deben definirse incluso si no se usan explícitamente:

struct A
{ 
    ~A();
};

Lo siguiente produciría el error:

A a;      //destructor undefined

La implementación puede ser en línea, en la propia definición de clase:

struct A
{ 
    ~A() {}
};

o afuera:

A::~A() {}

Si la implementación está fuera de la definición de clase, pero en un encabezado, los métodos deben marcarse inlinepara evitar una definición múltiple.

Todos los métodos de miembros utilizados deben definirse si se usan.

Un error común es olvidar calificar el nombre:

struct A
{
   void foo();
};

void foo() {}

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

La definición debería ser

void A::foo() {}

staticlos miembros de datos deben definirse fuera de la clase en una sola unidad de traducción :

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

Se puede proporcionar un inicializador para un static constmiembro de datos de tipo integral o de enumeración dentro de la definición de clase; sin embargo, odr-use de este miembro aún requerirá una definición del alcance del espacio de nombres como se describió anteriormente. C ++ 11 permite la inicialización dentro de la clase para todos static constlos miembros de datos.

Luchian Grigore
fuente
Solo pensé que querrías enfatizar que hacer ambas cosas es posible, y que el dtor no es realmente una excepción. (no es obvio por su redacción a primera vista.)
Deduplicador el
112

Error al vincular con bibliotecas / archivos de objetos apropiados o compilar archivos de implementación

Comúnmente, cada unidad de traducción generará un archivo de objeto que contiene las definiciones de los símbolos definidos en esa unidad de traducción. Para usar esos símbolos, debe vincular con esos archivos de objetos.

En gcc , debe especificar todos los archivos de objetos que se vincularán en la línea de comandos, o compilar los archivos de implementación juntos.

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

El libraryNameaquí es sólo el nombre dado testimonio de la biblioteca, sin adiciones específicas de la plataforma. Entonces, por ejemplo, en Linux, los archivos de la biblioteca generalmente se llaman libfoo.sopero solo escribiría -lfoo. En Windows ese mismo archivo podría ser llamado foo.lib, pero usaría el mismo argumento. Es posible que deba agregar el directorio donde se pueden encontrar esos archivos -L‹directory›. Asegúrese de no escribir un espacio después -lo-L .

Para XCode : agregue las rutas de búsqueda de encabezado de usuario -> agregue la ruta de búsqueda de biblioteca -> arrastre y suelte la referencia de biblioteca real en la carpeta del proyecto.

Bajo MSVS , los archivos agregados a un proyecto automáticamente tienen sus archivos de objeto vinculados entre sí y libse generaría un archivo (en uso común). Para usar los símbolos en un proyecto separado, debe incluir los libarchivos en la configuración del proyecto. Esto se hace en la sección Linker de las propiedades del proyecto, en Input -> Additional Dependencies. (la ruta al libarchivo debe agregarse Linker -> General -> Additional Library Directories) Cuando se usa una biblioteca de terceros que se proporciona con unlib archivo, el no hacerlo generalmente genera el error.

También puede suceder que olvide agregar el archivo a la compilación, en cuyo caso no se generará el archivo objeto. En gcc agregarías los archivos a la línea de comando. En MSVS, agregar el archivo al proyecto hará que lo compile automáticamente (aunque los archivos pueden, manualmente, excluirse individualmente de la compilación).

En la programación de Windows, el signo revelador de que no vinculó una biblioteca necesaria es que el nombre del símbolo no resuelto comienza con __imp_. Busque el nombre de la función en la documentación, y debería decir qué biblioteca necesita usar. Por ejemplo, MSDN coloca la información en un cuadro en la parte inferior de cada función en una sección llamada "Biblioteca".

Luchian Grigore
fuente
1
Sería bueno si pudiera cubrir explícitamente el error común en gcc main.clugar de gcc main.c other.c (lo que los principiantes suelen hacer antes de que sus proyectos sean tan grandes como para construir archivos .o).
MM
101

Declarado pero no definió una variable o función.

Una declaración variable típica es

extern int x;

Como esto es solo una declaración, se necesita una sola definición . Una definición correspondiente sería:

int x;

Por ejemplo, lo siguiente generaría un error:

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

Observaciones similares se aplican a las funciones. Declarar una función sin definirla conduce al error:

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

Tenga cuidado de que la función que implemente coincida exactamente con la que declaró. Por ejemplo, puede tener calificadores cv no coincidentes:

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)

Otros ejemplos de desajustes incluyen

  • Función / variable declarada en un espacio de nombres, definido en otro.
  • Función / variable declarada como miembro de la clase, definida como global (o viceversa).
  • El tipo de retorno de la función, el número y los tipos de parámetros y la convención de llamada no están todos exactamente de acuerdo.

El mensaje de error del compilador a menudo le dará la declaración completa de la variable o función que se declaró pero nunca se definió. Compárelo estrechamente con la definición que proporcionó. Asegúrese de que cada detalle coincida.

Luchian Grigore
fuente
En VS, los archivos cpp que coinciden con los del encabezado #includesno agregado al directorio de origen también se incluyen en la categoría de definiciones faltantes.
Laurie Stearn
86

El orden en que se especifican las bibliotecas vinculadas interdependientes es incorrecto.

El orden en que las bibliotecas están vinculadas importa si las bibliotecas dependen unas de otras. En general, si la biblioteca Adepende de la biblioteca B, libA DEBE aparecer anteslibB en las banderas del vinculador.

Por ejemplo:

// B.h
#ifndef B_H
#define B_H

struct B {
    B(int);
    int x;
};

#endif

// B.cpp
#include "B.h"
B::B(int xx) : x(xx) {}

// A.h
#include "B.h"

struct A {
    A(int x);
    B b;
};

// A.cpp
#include "A.h"

A::A(int x) : b(x) {}

// main.cpp
#include "A.h"

int main() {
    A a(5);
    return 0;
};

Crea las 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

Así que para repetir de nuevo, el orden HACE importa!

Svalorzen
fuente
Lo curioso es que en mi caso tenía un archivo de objeto que depende de una biblioteca compartida. Tuve que modificar el Makefile y colocar la biblioteca DESPUÉS del objeto con gcc 4.8.4 en Debian. En Centos 6.5 con gcc 4.4, el Makefile funcionó sin problemas.
Marco Sulla
74

¿Qué es una "referencia indefinida / símbolo externo no resuelto"?

Trataré de explicar qué es una "referencia indefinida / símbolo externo no resuelto".

nota: uso g ++ y Linux y todos los ejemplos son para eso

Por ejemplo tenemos un 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;
}

y

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

Hacer archivos de objetos

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

Después de la fase de ensamblador tenemos un archivo de objeto, que contiene cualquier símbolo para exportar. Mira los 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]

He rechazado algunas líneas de salida, porque no importan

Entonces, vemos los siguientes 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 no ​​exporta nada y no hemos visto sus símbolos

Vincula nuestros archivos de objetos

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

y ejecutarlo

$ ./prog
123

Linker ve símbolos exportados y lo vincula. Ahora intentamos descomentar líneas en src2.cpp como aquí

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

y reconstruir un archivo objeto

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

OK (sin errores), porque solo construimos archivos de objetos, la vinculación aún no se ha realizado. Intenta 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

Ha sucedido porque nuestro local_var_name es estático, es decir, no es visible para otros módulos. Ahora más profundamente. Obtenga la salida de la fase de traducción

$ 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

Entonces, hemos visto que no hay una etiqueta para local_var_name, es por eso que linker no la ha encontrado. Pero somos piratas informáticos :) y podemos solucionarlo. Abra src1.s en su editor de texto y cambie

.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4

a

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

es decir, deberías tener como abajo

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

hemos cambiado la visibilidad de local_var_name y establecemos su valor en 456789. Intenta construir un archivo de objeto a partir de él

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

ok, ver salida de readelf (símbolos)

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

ahora local_var_name tiene Bind GLOBAL (was LOCAL)

enlace

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

y ejecutarlo

$ ./prog 
123456789

ok, lo hackeamos :)

Por lo tanto, como resultado, se produce un "error de símbolo externo de referencia indefinido / no resuelto" cuando el vinculador no puede encontrar símbolos globales en los archivos de objetos.

Kastaneda
fuente
71

Los símbolos se definieron en un programa en C y se usaron en código C ++.

La función (o variable) void foo()se definió en un programa en C e intenta usarla en un programa en C ++:

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

El vinculador de C ++ espera que los nombres se alteren, por lo que debe declarar la función como:

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

De manera equivalente, en lugar de definirse en un programa en C, la función (o variable) void foo()se definió en C ++ pero con enlace C:

extern "C" void foo();

e intenta usarlo en un programa C ++ con enlace C ++.

Si se incluye una biblioteca completa en un archivo de encabezado (y se compiló como código C); la inclusión deberá ser la siguiente;

extern "C" {
    #include "cheader.h"
}
Luchian Grigore
fuente
55
O, por el contrario, si desarrolla una biblioteca en C, una buena regla es proteger los archivos de encabezado rodeando todas las declaraciones exportadas con #ifdef __cplusplus [\n] extern"C" { [\n] #endify #ifdef __cplusplus [\n] } [\n] #endif(al [\n]ser un retorno de carro real, pero no puedo escribir esto correctamente en el comentario).
Bentoy13
Como en el comentario anterior, la sección 'Crear encabezados de lenguaje mixto' aquí ayudó: oracle.com/technetwork/articles/servers-storage-dev/…
zanbri
Realmente me ayudó! Este fue mi caso cuando busqué esta pregunta
knightofhorizon
Esto también puede ocurrir si se incluye el archivo de cabecera C ++ ordinaria por accidente rodeado de C externo : extern "C" { #include <myCppHeader.h> }.
ComFreek
68

Si todo lo demás falla, vuelva a compilar.

Recientemente pude deshacerme de un error externo no resuelto en Visual Studio 2012 simplemente volviendo a compilar el archivo ofensivo. Cuando reconstruí, el error desapareció.

Esto generalmente ocurre cuando dos (o más) bibliotecas tienen una dependencia cíclica. La biblioteca A intenta usar símbolos en B.lib y la biblioteca B intenta usar símbolos de A.lib. Tampoco existen para empezar. Cuando intentas compilar A, el paso de enlace fallará porque no puede encontrar B.lib. A.lib será generado, pero no dll. Luego compila B, que tendrá éxito y generará B.lib. Re-compilar A ahora funcionará porque ahora se encuentra B.lib.

sgryzko
fuente
1
Correcto: esto sucede cuando las bibliotecas tienen una dependencia cíclica.
Luchian Grigore
1
Extendí su respuesta y la vinculé en la principal. Gracias.
Luchian Grigore
57

Importar / exportar incorrectamente métodos / clases a través de módulos / dll (específico del compilador).

MSVS requiere que especifique qué símbolos exportar e importar usando __declspec(dllexport)y__declspec(dllimport) .

Esta doble funcionalidad generalmente se obtiene mediante el uso de una macro:

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

La macro THIS_MODULEsolo se definiría en el módulo que exporta la función. De esa manera, la declaración:

DLLIMPEXP void foo();

se expande a

__declspec(dllexport) void foo();

y le dice al compilador que exporte la función, ya que el módulo actual contiene su definición. Al incluir la declaración en un módulo diferente, se expandiría a

__declspec(dllimport) void foo();

y le dice al compilador que la definición está en una de las bibliotecas con las que se vinculó (ver también 1) ).

Puedes importar / exportar clases similares:

class DLLIMPEXP X
{
};
Luchian Grigore
fuente
2
Para completar, esta respuesta debe mencionar visibilitylos .defarchivos de GCC y Windows , ya que estos también influyen en el nombre y la presencia del símbolo.
rubenvb
@rubenvb No he usado .defarchivos en años. Siéntase libre de agregar una respuesta o editar esta.
Luchian Grigore
57

Este es uno de los mensajes de error más confusos que todos los programadores de VC ++ han visto una y otra vez. Hagamos las cosas claras primero.

A. ¿Qué es el símbolo? En resumen, un símbolo es un nombre. Puede ser un nombre de variable, un nombre de función, un nombre de clase, un nombre de typedef, o cualquier cosa, excepto los nombres y signos que pertenecen al lenguaje C ++. Es definido por el usuario o introducido por una biblioteca de dependencias (otra definida por el usuario).

B. ¿Qué es externo? En VC ++, cada archivo fuente (.cpp, .c, etc.) se considera como una unidad de traducción, el compilador compila una unidad a la vez y genera un archivo de objeto (.obj) para la unidad de traducción actual. (Tenga en cuenta que cada archivo de encabezado incluido en este archivo fuente se procesará previamente y se considerará como parte de esta unidad de traducción) Todo lo que se encuentra dentro de una unidad de traducción se considera interno, todo lo demás se considera externo. En C ++, puede hacer referencia a un símbolo externo utilizando palabras clave como extern, __declspec (dllimport)etc.

C. ¿Qué es "resolver"? Resolver es un término de tiempo de enlace. En el tiempo de enlace, el enlazador intenta encontrar la definición externa para cada símbolo en los archivos de objetos que no pueden encontrar su definición internamente. El alcance de este proceso de búsqueda incluye:

  • Todos los archivos de objetos que se generaron en tiempo de compilación
  • Todas las bibliotecas (.lib) que se especifican explícita o implícitamente como dependencias adicionales de esta aplicación de construcción.

Este proceso de búsqueda se llama resolver.

D. Finalmente, ¿por qué símbolo externo no resuelto? Si el vinculador no puede encontrar la definición externa de un símbolo que no tiene una definición interna, informa un error de símbolo externo no resuelto.

E. Posibles causas de LNK2019 : error de símbolo externo no resuelto. Ya sabemos que este error se debe a que el enlazador no pudo encontrar la definición de símbolos externos, las posibles causas pueden clasificarse como:

  1. La definición existe

Por ejemplo, si tenemos una función llamada foo definida en a.cpp:

int foo()
{
    return 0;
}

En b.cpp queremos llamar a la función foo, así que agregamos

void foo();

para declarar la función foo () y llamarla en otro cuerpo de función, diga bar():

void bar()
{
    foo();
}

Ahora, cuando cree este código, recibirá un error LNK2019 quejándose de que foo es un símbolo no resuelto. En este caso, sabemos que foo () tiene su definición en a.cpp, pero diferente de la que estamos llamando (valor de retorno diferente). Este es el caso de que exista definición.

  1. La definición no existe

Si queremos llamar a algunas funciones en una biblioteca, pero la biblioteca de importación no se agrega a la lista de dependencias adicional (establecida desde:) Project | Properties | Configuration Properties | Linker | Input | Additional Dependencyde la configuración de su proyecto. Ahora el enlazador reportará un LNK2019 ya que la definición no existe en el alcance de búsqueda actual.

Nima Soroush
fuente
1
Gracias por la explicación sistemática. Podía entender las otras respuestas aquí después de leer esta.
displayName el
56

Implementaciones de plantillas no visibles.

Las plantillas no especializadas deben tener sus definiciones visibles para todas las unidades de traducción que las usan. Eso significa que no puede separar la definición de una plantilla a un archivo de implementación. Si debe separar la implementación, la solución habitual es tener un implarchivo que incluya al final del encabezado que declara la plantilla. Una situación común es:

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

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

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

Para solucionar esto, debe mover la definición de X::foo al archivo de encabezado o algún lugar visible para la unidad de traducción que lo utiliza.

Las plantillas especializadas pueden implementarse en un archivo de implementación y la implementación no tiene que ser visible, pero la especialización debe declararse previamente.

Para más explicaciones y otra posible solución (instanciación explícita) vea esta pregunta y respuesta .

Luchian Grigore
fuente
1
Puede encontrar más información sobre "las plantillas deben definirse en el encabezado" en stackoverflow.com/questions/495021
PlasmaHH
41

referencia indefinida WinMain@16o referencia de punto de entrada 'inusual' similarmain() (especialmente para)

Es posible que no haya elegido el tipo de proyecto correcto con su IDE real. El IDE puede querer vincular, por ejemplo, proyectos de aplicaciones de Windows a dicha función de punto de entrada (como se especifica en la referencia que falta más arriba), en lugar de la int main(int argc, char** argv);firma comúnmente utilizada .

Si su IDE admite proyectos de consola simple, es posible que desee elegir este tipo de proyecto, en lugar de un proyecto de aplicación de Windows.


Aquí están case1 y case2 manejados con más detalle a partir de un mundo real problema.

πάντα ῥεῖ
fuente
2
No puedo evitar señalar esta pregunta y el hecho de que esto se debe con mayor frecuencia a no tener ninguna función principal que no tenerla WinMain. Los programas válidos de C ++ necesitan a main.
Chris
36

Además, si está utilizando bibliotecas de terceros, asegúrese de tener los binarios correctos de 32/64 bits

Dula
fuente
34

Microsoft ofrece una #pragmareferencia de la biblioteca correcta en el momento del enlace;

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

Además de la ruta de la biblioteca, incluido el directorio de la biblioteca, este debe ser el nombre completo de la biblioteca.

Niall
fuente
34

El paquete Visual Studio NuGet debe actualizarse para la nueva versión del conjunto de herramientas

Acabo de tener este problema al intentar vincular libpng con Visual Studio 2013. El problema es que el archivo del paquete solo tenía bibliotecas para Visual Studio 2010 y 2012.

La solución correcta es esperar que el desarrollador lance un paquete actualizado y luego actualice, pero funcionó para mí pirateando una configuración adicional para VS2013, apuntando a los archivos de la biblioteca VS2012.

Edité el paquete (en la packagescarpeta dentro del directorio de la solución) buscando packagename\build\native\packagename.targetsy dentro de ese archivo, copiando todas las v110secciones. Cambié v110a v120en los campos de condición solo teniendo mucho cuidado de dejar todas las rutas de archivo como v110. Esto simplemente permitió que Visual Studio 2013 se vincule a las bibliotecas para 2012, y en este caso, funcionó.

Malvinoso
fuente
1
Esto parece demasiado específico: quizás un nuevo hilo sería un mejor lugar para esta respuesta.
Luchian Grigore
3
@LuchianGrigore: Quería publicar aquí, ya que esa pregunta era exactamente este problema, pero se marcó como un duplicado de esta pregunta, por lo que no pude responderla allí. Entonces publiqué mi respuesta aquí.
Malvineous
Esa pregunta ya tiene una respuesta aceptada. Está marcado como duplicado porque la causa general se menciona arriba. ¿Qué pasaría si tuviéramos una respuesta aquí para cada problema con una biblioteca que no está incluida?
Luchian Grigore
66
@LuchianGrigore: este problema no es específico de una biblioteca, afecta a todas las bibliotecas que utilizan el sistema de administración de paquetes de Visual Studio. Simplemente encontré la otra pregunta porque ambos tuvimos problemas con libpng. También tuve el mismo problema (con la misma solución) para libxml2, libiconv y glew. Esa pregunta es acerca de un problema con el sistema de administración de paquetes de Visual Studio, y mi respuesta explica la razón y proporciona una solución alternativa. Alguien acaba de ver "externo no resuelto" y asumió que era un problema de enlace estándar cuando en realidad es un problema de administración de paquetes.
Malvineous
34

Supongamos que tiene un gran proyecto escrito en c ++ que tiene miles de archivos .cpp y miles de archivos .h. Y digamos que el proyecto también depende de diez bibliotecas estáticas. Digamos que estamos en Windows y construimos nuestro proyecto en Visual Studio 20xx. Cuando presiona Ctrl + F7 Visual Studio para comenzar a compilar la solución completa (supongamos que solo tenemos un proyecto en la solución)

¿Cuál es el significado de la compilación?

  • Visual Studio busca en el archivo .vcxproj y comienza a compilar cada archivo que tiene la extensión .cpp. El orden de compilación no está definido, por lo que no debe suponer que el archivo main.cpp se compila primero
  • Si los archivos .cpp dependen de archivos .h adicionales para encontrar símbolos que pueden o no estar definidos en el archivo .cpp
  • Si existe un archivo .cpp en el que el compilador no pudo encontrar un símbolo, un error de tiempo del compilador genera el mensaje No se pudo encontrar el símbolo x
  • Para cada archivo con extensión .cpp se genera un archivo de objeto .o también Visual Studio escribe la salida en un archivo llamado ProjectName.Cpp.Clean.txt que contiene todos los archivos de objetos que el vinculador debe procesar.

El segundo paso de la compilación lo realiza Linker.Linker debería fusionar todo el archivo de objeto y finalmente construir la salida (que puede ser un archivo ejecutable o una biblioteca)

Pasos para vincular un proyecto

  • Analice todos los archivos de objetos y encuentre la definición que solo se declaró en encabezados (por ejemplo: el código de un método de una clase como se menciona en las respuestas anteriores, o la inicialización de una variable estática que es miembro dentro de una clase)
  • Si no se puede encontrar un símbolo en los archivos de objetos, también se busca en Bibliotecas adicionales. Para agregar una nueva biblioteca a un proyecto Propiedades de configuración -> Directorios VC ++ -> Directorios de biblioteca y aquí especificó una carpeta adicional para buscar bibliotecas y propiedades de configuración -> Linker -> Entrada para especificar el nombre de la biblioteca. -Si el Linker no pudo encontrar el símbolo que escribes en un .cpp, genera un error de tiempo del linker que puede sonar como error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

Observación

  1. Una vez que Linker encuentra un símbolo, no lo busca en otras bibliotecas.
  2. El orden de vinculación de las bibliotecas sí importa .
  3. Si Linker encuentra un símbolo externo en una biblioteca estática, incluye el símbolo en la salida del proyecto. Sin embargo, si la biblioteca es compartida (dinámica) no incluye el código (símbolos) en la salida, pero los bloqueos en tiempo de ejecución pueden ocurrir

Cómo resolver este tipo de error

Error de tiempo del compilador:

  • Asegúrese de escribir su proyecto sintáctico c ++ correcto.

Error de tiempo del vinculador

  • Defina todos los símbolos que declara en sus archivos de encabezado
  • Utilícelo #pragma oncepara permitir que el compilador no incluya un encabezado si ya estaba incluido en el .cpp actual que se compila
  • Asegúrese de que su biblioteca externa no contenga símbolos que puedan entrar en conflicto con otros símbolos que haya definido en sus archivos de encabezado
  • Cuando usa la plantilla para asegurarse de incluir la definición de cada función de plantilla en el archivo de encabezado para permitir que el compilador genere el código apropiado para cualquier instanciación.
eFarzad
fuente
¿No es tu respuesta específica para Visual Studio? La pregunta no especifica ninguna herramienta IDE / compilador, por lo que hace que su respuesta sea inútil para la parte de estudio no visual.
Victor Polevoy
Tienes razón . Pero cada proceso IDE de compilación / vinculación se realiza de manera ligeramente diferente. Pero los archivos se procesan exactamente de la misma manera (incluso g ++ hace lo mismo cuando analiza las banderas ...)
El problema no se trata realmente de IDE sino de una respuesta para vincular problemas. Los problemas de vinculación no están relacionados con el IDE sino con el proceso de compilación y compilación.
Victor Polevoy
Sí, pero el proceso de compilación / vinculación se realiza en g ++ / Visual Studio (compilador proporcionado por Microsoft para VS) / Eclipse / Net Beans de la misma manera
29

Un error en el compilador / IDE

Recientemente tuve este problema, y ​​resultó que era un error en Visual Studio Express 2013 . Tuve que eliminar un archivo fuente del proyecto y volver a agregarlo para superar el error.

Pasos para probar si cree que podría ser un error en el compilador / IDE:

  • Limpie el proyecto (algunos IDE tienen una opción para hacerlo, también puede hacerlo manualmente eliminando los archivos de objetos)
  • Intente iniciar un nuevo proyecto, copiando todo el código fuente del original.
desarrolladorbmw
fuente
55
Creer que sus herramientas están rotas probablemente lo alejará de la verdadera causa. Es mucho más probable que haya cometido un error que un compilador causó su problema. Limpiar su solución o volver a crear su configuración de compilación puede corregir los errores de compilación, pero eso no significa que haya un error en el compilador. El enlace "resultó que era un error" no está confirmado por Microsoft y no es reproducible.
JDiMatteo 01 de
44
@JDiMatteo Hay 21 respuestas sobre esta pregunta y, por lo tanto, una cantidad significativa de respuestas no será una solución "probable". Si descarta todas las respuestas que están por debajo de su umbral de probabilidad, esta página se vuelve inútil, ya que la mayoría de los casos comunes se detectan fácilmente de todos modos.
developerbmw
27

Use el enlazador para ayudar a diagnosticar el error

La mayoría de los enlazadores modernos incluyen una opción detallada que se imprime en diversos grados;

  • Invocación de enlace (línea de comando),
  • Datos sobre qué bibliotecas se incluyen en la etapa de enlace,
  • La ubicación de las bibliotecas,
  • Buscar rutas utilizadas.

Para gcc y clang; normalmente agregaría -v -Wl,--verboseo -v -Wl,-va la línea de comando. Más detalles se pueden encontrar aquí;

Para MSVC, /VERBOSE(en particular /VERBOSE:LIB) se agrega a la línea de comando de enlace.

Niall
fuente
26

El archivo .lib vinculado está asociado a un .dll

Tuve el mismo problema. Digamos que tengo proyectos MyProject y TestProject. Había vinculado efectivamente el archivo lib de MyProject al TestProject. Sin embargo, este archivo lib se produjo cuando se creó la DLL para MyProject. Además, no contenía código fuente para todos los métodos en MyProject, sino solo acceso a los puntos de entrada de la DLL.

Para resolver el problema, construí MyProject como LIB y vinculé TestProject a este archivo .lib (copio y pego el archivo .lib generado en la carpeta TestProject). Entonces puedo construir nuevamente MyProject como una DLL. Se está compilando ya que la biblioteca a la que está vinculado TestProject contiene código para todos los métodos en las clases de MyProject.

Kiriloff
fuente
25

Dado que las personas parecen estar dirigidas a esta pregunta cuando se trata de errores de enlazador, voy a agregar esto aquí.

Una posible razón para los errores de enlazador con GCC 5.2.0 es que ahora se elige una nueva biblioteca ABI de libstdc ++ por defecto.

Si obtiene errores de vinculador sobre referencias indefinidas a símbolos que involucran tipos en el espacio de nombres std :: __ cxx11 o la etiqueta [abi: cxx11], entonces probablemente indica que está intentando vincular archivos de objetos compilados con diferentes valores para _GLIBCXX_USE_CXX11_ABI macro. Esto ocurre comúnmente cuando se vincula a una biblioteca de terceros que se compiló con una versión anterior de GCC. Si la biblioteca de terceros no se puede reconstruir con el nuevo ABI, deberá volver a compilar su código con el antiguo ABI.

Entonces, si de repente obtiene errores de enlazador al cambiar a un GCC después de 5.1.0, esto sería algo que debe verificar.

Plankalkül
fuente
20

Un contenedor alrededor de GNU ld que no admite scripts de enlazador

Algunos archivos .so son realmente scripts de enlazador GNU ld , por ejemplo, el archivo libtbb.so es un archivo de texto ASCII con este contenido:

INPUT (libtbb.so.2)

Es posible que algunas compilaciones más complejas no admitan esto. Por ejemplo, si incluye -v en las opciones del compilador, puede ver que mainwin gcc wrapper mwdip descarta los archivos de comando de script de enlazador en la lista de salida detallada de las bibliotecas para vincular. Una solución simple es reemplazar el comando de entrada de script de enlazador archivo con una copia del archivo en su lugar (o un enlace simbólico), por ejemplo

cp libtbb.so.2 libtbb.so

O puede reemplazar el argumento -l con la ruta completa de .so, por ejemplo, en lugar de -ltbbdo/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2

JDiMatteo
fuente
20

Su enlace consume bibliotecas antes que los archivos de objeto que se refieren a ellas

  • Está intentando compilar y vincular su programa con la cadena de herramientas GCC.
  • Su enlace especifica todas las bibliotecas y rutas de búsqueda de bibliotecas necesarias
  • Si libfoodepende libbar, entonces su enlace se pone correctamente libfooantes libbar.
  • Su enlace falla con undefined reference to algo errores.
  • Pero todos los indefinidos algo que s se declaran en los ficheros de cabecera que tiene #includedy son, de hecho se define en las bibliotecas que está enlazado.

Los ejemplos están en C. También podrían ser C ++

Un ejemplo mínimo de una biblioteca estática que usted mismo creó.

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

Usted construye su biblioteca estática:

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

Usted compila su programa:

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

Intenta vincularlo libmy_lib.ay falla:

$ 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

El mismo resultado si compila y vincula en un solo paso, 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

Un ejemplo mínimo que involucra una biblioteca de sistema compartida, la biblioteca de compresión libz

eg2.c

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

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

Compila tu programa:

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

Intenta vincular tu programa libzy falla:

$ gcc -o eg2 -lz eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

Lo mismo si compila y enlaza de una 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

Y una variación en el ejemplo 2 que involucra pkg-config:

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

¿Que estas haciendo mal?

En la secuencia de archivos de objetos y bibliotecas que desea vincular para hacer su programa, está colocando las bibliotecas antes de los archivos de objetos que se refieren a ellas. Necesita colocar las bibliotecas después de los archivos de objeto que se refieren a ellas.

Ejemplo de enlace 1 correctamente:

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

Éxito:

$ ./eg1 
Hello World

Ejemplo de enlace 2 correctamente:

$ gcc -o eg2 eg2.o -lz

Éxito:

$ ./eg2 
1.2.8

Enlace la pkg-configvariación del ejemplo 2 correctamente:

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

La explicación

La lectura es opcional de aquí en adelante .

Por defecto, un comando de enlace generado por GCC, en su distribución, consume los archivos en el enlace de izquierda a derecha en la secuencia de la línea de comandos. Cuando encuentra que un archivo se refiere a algo y no contiene una definición para él, buscará una definición en los archivos más a la derecha. Si finalmente encuentra una definición, la referencia se resuelve. Si alguna referencia permanece sin resolver al final, el enlace falla: el enlazador no busca hacia atrás.

Primero, ejemplo 1 , con biblioteca estáticamy_lib.a

Una biblioteca estática es un archivo indexado de archivos de objetos. Cuando el vinculador encuentra -lmy_liben la secuencia de vinculación y descubre que esto se refiere a la biblioteca estática ./libmy_lib.a, quiere saber si su programa necesita alguno de los archivos de objeto libmy_lib.a.

Solo hay un archivo de objeto libmy_lib.a, es decir my_lib.o, y solo hay una cosa definida my_lib.o, es decir, la función hw.

El vinculador decidirá que su programa necesita my_lib.osi y solo si ya sabe que su programa hace referencia hw, en uno o más de los archivos de objetos que ya ha agregado al programa, y ​​que ninguno de los archivos de objetos que ya ha agregado contiene un definición para hw.

Si eso es cierto, entonces el enlazador extraerá una copia de my_lib.ola biblioteca y la agregará a su programa. Luego, su programa contiene una definición para hw, por lo que sus referencias a hwse resuelven .

Cuando intentas vincular el programa como:

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

el enlazador no se ha agregado eg1.o al programa cuando ve -lmy_lib. Porque en ese punto, no ha visto eg1.o. Su programa aún no hace ninguna referencia a hw: todavía no hace ninguna referencia en absoluto , porque todas las referencias que hace están en eg1.o.

Por lo tanto, el enlazador no se agrega my_lib.oal programa y no tiene más uso para libmy_lib.a.

Luego, lo encuentra eg1.oy lo agrega como programa. Un archivo de objeto en la secuencia de enlace siempre se agrega al programa. Ahora, el programa hace referencia a hw, y no contiene una definición de hw; pero no queda nada en la secuencia de enlace que pueda proporcionar la definición que falta. La referencia a hwtermina sin resolver y el enlace falla.

Segundo, ejemplo 2 , con biblioteca compartidalibz

Una biblioteca compartida no es un archivo de archivos de objetos ni nada parecido. Es mucho más parecido a un programa que no tiene una mainfunción y, en cambio, expone varios otros símbolos que define, para que otros programas puedan usarlos en tiempo de ejecución.

Configurar hoy en día muchas distribuciones de Linux GCC su cadena de herramientas para que sus conductores de idiomas ( gcc, g++, gfortranetc) instruir a los enlazador sistema ( ld) a las bibliotecas compartidas de enlace de un a demanda base. Tienes una de esas distribuciones.

Esto significa que cuando el enlazador encuentra -lzen la secuencia de enlace, y descubre que esto se refiere a la biblioteca compartida (por ejemplo) /usr/lib/x86_64-linux-gnu/libz.so, quiere saber si las referencias que ha agregado a su programa que aún no están definidas tienen definiciones que son exportado porlibz

Si eso es cierto, entonces el enlazador no copiará ningún fragmento libzni lo agregará a su programa; en su lugar, solo buscará el código de su programa para que:

  • En tiempo de ejecución, el cargador de programas del sistema cargará una copia libzen el mismo proceso que su programa cada vez que cargue una copia de su programa, para ejecutarlo.

  • En tiempo de ejecución, cada vez que su programa hace referencia a algo definido libz, esa referencia utiliza la definición exportada por la copia de libzen el mismo proceso.

Su programa quiere referirse solo a una cosa que tiene una definición exportada por libz, a saber, la función zlibVersion, a la que se hace referencia solo una vez, en eg2.c. Si el vinculador agrega esa referencia a su programa y luego encuentra la definición exportada por libz, la referencia se resuelve

Pero cuando intentas vincular el programa como:

gcc -o eg2 -lz eg2.o

el orden de los eventos es incorrecto de la misma manera que en el ejemplo 1. En el momento en que el vinculador encuentra -lz, no hay referencias a nada en el programa: están todos dentro eg2.o, lo que aún no se ha visto. Entonces el enlazador decide que no tiene ningún uso libz. Cuando llega eg2.o, lo agrega al programa, y ​​luego tiene una referencia indefinida a zlibVersion, la secuencia de enlace ha finalizado; esa referencia no está resuelta y el enlace falla.

Por último, la pkg-configvariación del ejemplo 2 tiene una explicación ahora obvia. Después de la expansión de la carcasa:

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

se convierte en:

gcc -o eg2 -lz eg2.o

que es solo el ejemplo 2 nuevamente.

Puedo reproducir el problema en el ejemplo 1, pero no en el ejemplo 2

El enlace:

gcc -o eg2 -lz eg2.o

funciona bien para ti!

(O: ese enlace funcionó bien para usted en, digamos, Fedora 23, pero falla en Ubuntu 16.04)

Esto se debe a que la distribución en la que funciona el enlace es una de las que no configura su cadena de herramientas GCC para vincular bibliotecas compartidas según sea necesario .

En el pasado, era normal que los sistemas tipo Unix vinculen bibliotecas estáticas y compartidas por diferentes reglas. Las bibliotecas estáticas en una secuencia de enlace se vincularon según fuera necesario según se explica en el ejemplo 1, pero las bibliotecas compartidas se vincularon incondicionalmente.

Este comportamiento es económico en el momento del enlace porque el enlazador no tiene que pensar si el programa necesita una biblioteca compartida: si es una biblioteca compartida, vincúlela. Y la mayoría de las bibliotecas en la mayoría de los enlaces son bibliotecas compartidas. Pero también hay desventajas:

  • No es económico en tiempo de ejecución , porque puede hacer que las bibliotecas compartidas se carguen junto con un programa, incluso si no las necesita.

  • Las diferentes reglas de vinculación para bibliotecas estáticas y compartidas pueden ser confusas para los programadores inexpertos, que pueden no saber si -lfooen su vinculación se resolverá /some/where/libfoo.ao no /some/where/libfoo.so, y de todos modos es posible que no entiendan la diferencia entre bibliotecas compartidas y estáticas.

Esta compensación ha llevado a la situación cismática hoy. Algunas distribuciones han cambiado sus reglas de enlace de CCG para bibliotecas compartidas, de modo que el principio según sea necesario se aplica a todas las bibliotecas. Algunas distribuciones se han quedado con la vieja forma.

¿Por qué sigo teniendo este problema incluso si compilo y enlazo al mismo tiempo?

Si solo hago:

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

seguramente gcc tiene que compilar eg1.cprimero y luego vincular el archivo objeto resultante con libmy_lib.a. Entonces, ¿cómo puede no saber que se necesita el archivo de objeto cuando está haciendo el enlace?

Porque compilar y vincular con un solo comando no cambia el orden de la secuencia de vinculación.

Cuando ejecuta el comando anterior, gccdescubre que desea compilación + vinculación. Así que detrás de las escenas, se genera un comando de compilación, y lo ejecuta, a continuación, genera un comando de vinculación, y lo ejecuta, como si se hubiera ejecute los dos comandos:

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

Por lo que la vinculación falla igual que lo hace si no ejecuta estos dos comandos. La única diferencia que observa en la falla es que gcc ha generado un archivo de objeto temporal en el caso de compilación + enlace, porque no le está diciendo que lo use eg1.o. Vemos:

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

en vez de:

eg1.o: In function `main':

Ver también

El orden en que se especifican las bibliotecas vinculadas interdependientes es incorrecto

Poner las bibliotecas interdependientes en el orden incorrecto es solo una forma en la que puede obtener archivos que necesitan definiciones de cosas que vienen más adelante en el enlace que los archivos que proporcionan las definiciones. Poner las bibliotecas antes de los archivos de objetos que se refieren a ellas es otra forma de cometer el mismo error.

Mike Kinghan
fuente
18

Hacer amistad con las plantillas ...

Dado el fragmento de código de un tipo de plantilla con un operador amigo (o función);

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

El operator<<se declara como una función que no es de plantilla. Para cada tipo Tusado con Foo, debe haber una no plantilla operator<<. Por ejemplo, si hay un tipo Foo<int>declarado, debe haber una implementación de operador de la siguiente manera;

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

Como no está implementado, el vinculador no puede encontrarlo y genera el error.

Para corregir esto, puede declarar un operador de plantilla antes del Footipo y luego declarar como amigo, la instancia apropiada. La sintaxis es un poco incómoda, pero tiene el siguiente aspecto;

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

El código anterior limita la amistad del operador a la instanciación correspondiente de Foo, es decir, la operator<< <int>instanciación se limita al acceso a los miembros privados de la instanciación de Foo<int>.

Las alternativas incluyen;

  • Permitir que la amistad se extienda a todas las instancias de las plantillas, de la siguiente manera;

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
  • O bien, la implementación para el operator<<puede hacerse en línea dentro de la definición de clase;

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

Tenga en cuenta que cuando la declaración del operador (o función) solo aparece en la clase, el nombre no está disponible para la búsqueda "normal", solo para la búsqueda dependiente del argumento, de cppreference ;

Un nombre declarado por primera vez en una declaración de amigo dentro de la clase o la plantilla de clase X se convierte en un miembro del espacio de nombres más cerrado de X, pero no es accesible para la búsqueda (excepto la búsqueda dependiente de argumentos que considera X) a menos que haya una declaración coincidente en el ámbito del espacio de nombres. previsto...

Hay más información sobre la plantilla de amigos en cppreference y las preguntas frecuentes de C ++ .

Listado de código que muestra las técnicas anteriores .


Como nota al margen del ejemplo de código que falla; g ++ advierte sobre esto de la siguiente manera

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
fuente
16

Cuando sus rutas de inclusión son diferentes

Los errores del vinculador pueden ocurrir cuando un archivo de encabezado y su biblioteca compartida asociada (archivo .lib) no se sincronizan. Dejame explicar.

¿Cómo funcionan los enlazadores? El vinculador hace coincidir una declaración de función (declarada en el encabezado) con su definición (en la biblioteca compartida) comparando sus firmas. Puede obtener un error de enlazador si el enlazador no encuentra una definición de función que coincida perfectamente.

¿Es posible obtener un error de vinculador aunque la declaración y la definición parezcan coincidir? ¡Si! Puede que tengan el mismo aspecto en el código fuente, pero realmente depende de lo que vea el compilador. Esencialmente, podrías terminar con una situación 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 cómo, aunque ambas declaraciones de funciones se ven idénticas en el código fuente, pero son realmente diferentes según el compilador.

¿Podría preguntar cómo termina uno en una situación como esa? Incluye caminos, por supuesto! Si al compilar la biblioteca compartida, la ruta de inclusión conduce header1.hy termina usando header2.hen su propio programa, se quedará rascándose el encabezado preguntándose qué sucedió (juego de palabras).

A continuación se explica un ejemplo de cómo puede suceder esto en el mundo real.

Elaboración adicional con un ejemplo

Tengo dos proyectos: graphics.liby main.exe. Ambos proyectos dependen de common_math.h. Supongamos que la biblioteca exporta la siguiente función:

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

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

Y luego continúas e incluyes la biblioteca en tu propio proyecto.

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

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

¡Auge! Obtiene un error de vinculador y no tiene idea de por qué está fallando. La razón es que la biblioteca común usa diferentes versiones de la misma inclusión common_math.h(lo he hecho obvio aquí en el ejemplo al incluir una ruta diferente, pero puede que no siempre sea tan obvio. Quizás la ruta de inclusión sea diferente en la configuración del compilador) .

Tenga en cuenta que en este ejemplo, el vinculador le dirá que no puede encontrarlo draw(), cuando en realidad sabe que obviamente la biblioteca lo está exportando. Podrías pasar horas rascándote la cabeza preguntándote qué salió mal. La cuestión es que el vinculador ve una firma diferente porque los tipos de parámetros son ligeramente diferentes. En el ejemplo, vec3es un tipo diferente en ambos proyectos en lo que respecta al compilador. Esto podría suceder porque provienen de dos archivos de inclusión ligeramente diferentes (tal vez los archivos de inclusión provienen de dos versiones diferentes de la biblioteca).

Depuración del enlazador

DUMPBIN es tu amigo, si estás usando Visual Studio. Estoy seguro de que otros compiladores tienen otras herramientas similares.

El proceso es así:

  1. Tenga en cuenta el extraño nombre destrozado dado en el error del vinculador. (por ejemplo, dibujar @ gráficos @ XYZ).
  2. Volcar los símbolos exportados de la biblioteca en un archivo de texto.
  3. Busque el símbolo de interés exportado y observe que el nombre destrozado es diferente.
  4. Presta atención a por qué los nombres destrozados terminaron siendo diferentes. Podrá ver que los tipos de parámetros son diferentes, aunque se vean iguales en el código fuente.
  5. Razón por la cual son diferentes. En el ejemplo anterior, son diferentes debido a los diferentes archivos de inclusión.

[1] Por proyecto me refiero a un conjunto de archivos fuente que están vinculados para producir una biblioteca o un ejecutable.

EDITAR 1: reescribió la primera sección para que sea más fácil de entender. Por favor comente abajo para avisarme si algo más necesita ser reparado. ¡Gracias!

fafaro
fuente
15

UNICODEDefiniciones inconsistentes

Una compilación de UNICODE de Windows se construye con TCHARetc., que se define como wchar_tetc. Cuando no se construye con UNICODEdefinido como construcción con TCHARdefinido como charetc. Estos UNICODEy _UNICODEdefine afectan a todos los " T" tipos de cadena ; LPTSTR, LPCTSTRY sus alces.

Construir una biblioteca con UNICODEdefinido e intentar vincularlo en un proyecto donde UNICODEno está definido dará como resultado errores de vinculador ya que habrá una falta de coincidencia en la definición de TCHAR; charvs wchar_t.

El error generalmente incluye una función un valor con un tipo charo wchar_tderivado, estos también podrían incluir, std::basic_string<>etc. Al navegar a través de la función afectada en el código, a menudo habrá una referencia TCHARao std::basic_string<TCHAR>etc. Esta es una señal reveladora de que el código originalmente estaba destinado tanto a un UNICODE como a una compilación de caracteres de varios bytes (o "estrecho") .

Para corregir esto, cree todas las bibliotecas y proyectos necesarios con una definición coherente de UNICODE(y _UNICODE).

  1. Esto se puede hacer con cualquiera de los dos;

    #define UNICODE
    #define _UNICODE
  2. O en la configuración del proyecto;

    Propiedades del proyecto> General> Valores predeterminados del proyecto> Conjunto de caracteres

  3. O en la línea de comando;

    /DUNICODE /D_UNICODE

La alternativa también es aplicable, si UNICODE no está destinado a ser utilizado, asegúrese de que las definiciones no estén establecidas, y / o la configuración de caracteres múltiples se use en los proyectos y se aplique de manera consistente.

No olvide ser coherente entre las versiones "Release" y "Debug" también.

Niall
fuente
14

Limpiar y reconstruir

Una "limpieza" de la compilación puede eliminar la "madera muerta" que puede quedar por ahí de compilaciones anteriores, compilaciones fallidas, compilaciones incompletas y otros problemas de compilación relacionados con el sistema de compilación.

En general, el IDE o la compilación incluirán alguna forma de función "limpia", pero esto puede no estar configurado correctamente (por ejemplo, en un archivo MAKE manual) o puede fallar (por ejemplo, los binarios intermedios o resultantes son de solo lectura).

Una vez que se haya completado la "limpieza", verifique que la "limpieza" haya tenido éxito y que todo el archivo intermedio generado (por ejemplo, un archivo MAKE automatizado) se haya eliminado con éxito.

Este proceso puede verse como un recurso final, pero a menudo es un buen primer paso ; especialmente si el código relacionado con el error se ha agregado recientemente (ya sea localmente o desde el repositorio de origen).

Niall
fuente
10

Falta "extern" en constdeclaraciones / definiciones variables (solo C ++)

Para las personas que vienen de C, puede ser una sorpresa que en C ++ las constvariables globales tengan un enlace interno (o estático). En C este no fue el caso, ya que todas las variables globales están implícitamente extern(es decir, cuando staticfalta la palabra clave).

Ejemplo:

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

lo correcto sería usar un archivo de encabezado e incluirlo en file2.cpp y file1.cpp

extern const int test;
extern int test2;

Alternativamente, uno podría declarar la constvariable en file1.cpp con explícitoextern

Andreas H.
fuente
8

Aunque esta es una pregunta bastante antigua con múltiples respuestas aceptadas, me gustaría compartir cómo resolver un oscuro error de "referencia indefinida a".

Diferentes versiones de bibliotecas.

Estaba usando un alias para hacer referencia a std::filesystem::path: el sistema de archivos está en la biblioteca estándar desde C ++ 17 pero mi programa también necesitaba compilar en C ++ 14, así que decidí usar un alias variable:

#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 tengo tres archivos: main.cpp, file.h, file.cpp:

  • file.h # include's < experimental :: filesystem > y contiene el código anterior
  • file.cpp , la implementación de file.h, # include's " file.h "
  • main.cpp # include's < filesystem > y " file.h "

Tenga en cuenta las diferentes bibliotecas utilizadas en main.cpp y file.h. Como main.cpp # include'd " file.h " después de < filesystem >, la versión del sistema de archivos utilizada era la C ++ 17 . Solía ​​compilar el programa con los siguientes comandos:

$ g++ -g -std=c++17 -c main.cpp-> compila main.cpp a main.o
$ g++ -g -std=c++17 -c file.cpp-> compila file.cpp y file.h a file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs-> vincula main.o y file.o

De esta manera cualquier función contenida en file.o y se utiliza en main.o que requierepath_t dio errores de "referencia" indefinido debido main.o refiere std::filesystem::path, pero file.o a std::experimental::filesystem::path.

Resolución

Para solucionar esto, solo necesitaba cambiar <experimental :: filesystem> en file.h a <filesystem> .

Stypox
fuente
5

Al vincular contra bibliotecas compartidas, asegúrese de que los símbolos utilizados no estén ocultos.

El comportamiento predeterminado de gcc es que todos los símbolos son visibles. Sin embargo, cuando las unidades de traducción se crean con opción -fvisibility=hidden, solo las funciones / símbolos marcados con __attribute__ ((visibility ("default")))son externos en el objeto compartido resultante.

Puede verificar si los símbolos que está buscando son externos invocando:

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

los símbolos ocultos / locales se muestran nmcon un tipo de símbolo en minúscula, por ejemplo, en tlugar de `T para la sección de código:

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

También puede usar nmcon la opción -Cde solicitar los nombres (si se utilizó C ++).

Similar a Windows-dlls, uno marcaría las funciones públicas con una definición, por ejemplo DLL_PUBLICdefinida como:

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

DLL_PUBLIC int my_public_function(){
  ...
}

Que corresponde aproximadamente a la versión de Windows / MSVC:

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

Se puede encontrar más información sobre la visibilidad en el wiki de gcc.


Cuando se compila una unidad de traducción con -fvisibility=hiddenlos símbolos resultantes, todavía tienen un enlace externo (se muestra con el tipo de símbolo en mayúscula nm) y se puede usar para un enlace externo sin problema si los archivos de objeto se vuelven parte de una biblioteca estática. El enlace se vuelve local solo cuando los archivos de objeto están vinculados a una biblioteca compartida.

Para encontrar qué símbolos en un archivo de objeto están ocultos, ejecute:

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2
Ead
fuente
Debe usar nm -CDo nm -gCDpara ver símbolos externos. También vea Visibilidad en el wiki de GCC.
jww
2

Diferentes arquitecturas

Puede ver un mensaje como:

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

En ese caso, significa que los símbolos disponibles son para una arquitectura diferente a la que está compilando.

En Visual Studio, esto se debe a una "Plataforma" incorrecta, y debe seleccionar la correcta o instalar la versión adecuada de la biblioteca.

En Linux, puede deberse a una carpeta de biblioteca incorrecta (usando en liblugar de, lib64por ejemplo).

En MacOS, existe la opción de enviar ambas arquitecturas en el mismo archivo. Puede ser que el enlace espere que ambas versiones estén allí, pero solo una está. También puede ser un problema con la carpeta lib/ incorrecta lib64donde se recoge la biblioteca.

Matthieu Brucher
fuente