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

CorePress2024-01-25  11

¿Qué son los errores de referencia no definidos/símbolos externos no resueltos? ¿Cuáles son las causas comunes y cómo soluciono y evito estos errores?

1

@jave.web: Mientras eso sucede, el programador generalmente nota que no tiene este puntero ni acceso a los miembros de la clase. Es bastante raro completar la compilación y solo fallar durante la vinculación, cuando a una función miembro no estática le falta su nombre completo.

- Ben Voigt

27 de abril, 2015 a las 22:06

2

@jave.web: Este fue exactamente mi problema. ¡Gracias! Soy nuevo en cpp, pero hasta donde sé, estaba teniendo exactamente el problema que, según Ben Voigt, era bastante raro. Creo que tu solución sería una excelente respuesta.

-RoG

20/10/2016 a las 6:42

1

@Snaptastic ver stackoverflow.com/a/12574407/673730 - Un error común es olvidar calificar el nombre :)

- Luchian Grigore

20/10/2016 a las 19:59

9

Pueden ser útiles, al igual que muchas respuestas a preguntas marcadas como demasiado generales.

-Albert van der Horst

16/08/2019 a las 19:07

3

Me gustaría ver mínimoUn ejemplo reproducible como algo que le pedimos a la mayoría de los usuarios nuevos, honestamente. No quiero decir nada con esto, es simplemente: no podemos esperar que la gente siga las reglas que nosotros no nos imponemos.

-Danilo

8 de septiembre de 2019 a las 18:54



------------------------------------

Digamos que tiene el siguiente código:

// a.cpp
int get() { return 0; }
// b.cpp
int get(); // usually, one doesn't write this directly, but gets these
           // declarations from included header files
int x = get();

Al compilar b.cpp, el compilador simplemente asume que el símbolo get() se definió en alguna parte, pero todavía no le importa dónde. La fase de vinculación es responsable de encontrar el símbolo y vincular correctamente los archivos objeto producidos a partir de a.cp.p y b.cpp.

Si un archivo.cpp no ​​define get, aparecerá un error del vinculador que indicará "referencia no definida" o "símbolo externo no resuelto".

Redacción estándar de C++

La compilación de un programa C++ se realiza en varias fases especificadas en [lex.phases], la última de las cuales es relevante:

9. Se resuelven todas las referencias a entidades externas. Los componentes de la biblioteca están vinculados para satisfacer referencias externas a entidades no definidas en la traducción actual. Toda la salida del traductor se recopila en una imagen del programa que contiene la información necesaria para la ejecución en su entorno de ejecución.

Consulte la respuesta de Keith Thompson para obtener un resumen de estas fases.

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

Errores del enlazador en la práctica

Si estás usando Microsoft Visual Studio, verás que los proyectos generan archivos .lib. Estos contienen una tabla de símbolos exportados y una tabla de símbolos importados. Los símbolos importados se resuelven con 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.

Los mensajes de error comunes son el error LNK2001, el error LNK1120, el error LNK2019 para Microsoft Visual Studio y una referencia indefinida al nombre del símbolo para 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 similareserrores 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
Causas comunes No vincular con bibliotecas/archivos de objetos apropiados o compilar archivos de implementación Variable o función declarada e indefinida. Problemas comunes con miembros de tipo clase Implementaciones de plantillas no visibles. Los símbolos se definieron en un programa C y se utilizaron en código C++. Importar/exportar métodos/clases incorrectamente entre módulos/dll. (específico de MSVS) Dependencia de la biblioteca circular referencia indefinida a `WinMain@16' Orden de biblioteca interdependiente Múltiples archivos fuente con el mismo nombre Escribir mal o no incluir la extensión .lib al usar #pragma (Microsoft Visual Studio) Problemas con amigos de plantilla Definiciones UNICODE inconsistentes Falta información "externa" en declaraciones/definiciones de variables constantes (solo C++) Estudios visualesCódigo dio no configurado para un proyecto de múltiples archivos Errores en Mac OS X al crear un dylib, pero un .so en otros sistemas Unix-y está bien Respondido

24 de septiembre de 2012 a las 22:27

Luchian Grigore

Luchian Grigore

255k

66

66 insignias de oro

458

458 insignias de plata

626

626 insignias de bronce

10

25

Personalmente, creo que los mensajes de error del vinculador de MS son tan legibles como los errores de GCC. También tienen la ventaja de incluir los nombres destrozados y no destrozados para lo externo no resuelto. Tener el nombre alterado puede ser útil cuando necesita mirar las bibliotecas o archivos de objetos directamente para ver cuál podría ser el problema (por ejemplo, una discrepancia en la convención de llamada). Además, no estoy seguro de qué versión de MSVC produjo los errores aquí, pero las versiones más recientes incluyen el nombre (tanto destrozado como no destrozado) de la función que hace referencia al símbolo externo no resuelto.

- Michael Burr

30 dic 2013 a las 20:29

12

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

22 de mayo de 2015 a las 14:48

@TankorSmash ¿Usas gcc? MinGW para ser más preciso.

- doug65536

15 de octubre de 2016 a las 1:17

5

@luchian, sería bueno si agregaras lo correcto, corrigiendo los errores anteriores

- Phalgun

21/02/2018 a las 23:35

2

Acabo de encontrar otra posible razón para el error del compilador de símbolos no resuelto. Una función se definió originalmente como en línea dentro de un encabezado, pero la cambié a una declaración y la definípor separado dentro de un archivo fuente. Esto falló con un error del compilador de símbolos no resuelto hasta que eliminé la palabra clave en línea tanto de la declaración como de la definición.

- Jake Levi

25/01/2022 a las 17:42



------------------------------------

Miembros de la clase: Un destructor virtual puro necesita una implementación.

Declarar un destructor puro aún requiere que lo definas (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 la clase base se llaman cuando el objeto se destruye implícitamente, por lo que se requiere una definición.

Los métodos virtuales deben implementarse o definirse como puros.

Esto es similar al m no virtualmétodos sin definición, con el razonamiento añadido de que la declaración pura genera una tabla virtual ficticia y es posible que obtenga el error del vinculador sin utilizar 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, declara X::foo() como puro:

struct X
{
    virtual void foo() = 0;
};
Miembros de la clase no virtual

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 estar 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 como en línea para evitar una definición múltiple.

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

Un error común es olvidar calificar el nombre:
struct A
{
   void foo();
};

void foo() {}

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

La definiciónLa idea debe ser

void A::foo() {}
Los miembros de datos estáticos deben definirse fuera de la clase en una única 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 miembro de datos constante estático de tipo integral o de enumeración dentro de la definición de clase; sin embargo, el uso odr de este miembro seguirá requiriendo una definición del alcance del espacio de nombres como se describe anteriormente. C++ 11 permite la inicialización dentro de la clase para todos los miembros de datos constantes estáticos.

Respondido

24 de septiembre de 2012 a las 23:38

Luchian Grigore

Luchian Grigore

255k

66

66 insignias de oro

458

plata 458dges

626

626 insignias de bronce

1

Solo pensé que tal vez quisieras enfatizar que hacer ambas cosas es posible y que el médico no es en realidad una excepción. (No es obvio por su redacción a primera vista).

-Desduplicador

20/09/2014 a las 19:26



------------------------------------

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

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

En gcc, especificaría todos los archivos objeto que se vincularán entre sí en la línea de comando o compilaría los archivos de implementación juntos.

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

-l... debe estar a la derecha de cualquier archivo .o/.c/.cpp.

El nombre de la biblioteca aquí es solo el nombre básico de la biblioteca, sin adiciones específicas de la plataforma. Entonces, por ejemplo. En Linux, los archivos de la biblioteca generalmente se llaman libfoo.so pero solo escribirías -lfoo. En Windows, ese mismo archivo podría llamarse foo.lib, pero usaría el mismo argumento. Es posible que tengas que agregar el directorio donde se pueden encontrar esos archivos usando -L`directorio`. Asegúrate de no escribirun espacio después de -l o -L.

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

En MSVS, los archivos agregados a un proyecto automáticamente tienen sus archivos objeto vinculados entre sí y se genera un archivo lib (de uso común). Para utilizar los símbolos en un proyecto independiente, deberá Es necesario incluir los archivos lib en la configuración del proyecto. Esto se hace en la sección Vinculador de las propiedades del proyecto, en Entrada -> Dependencias adicionales. (la ruta al archivo lib debe ser agregado en el vinculador -> General -> Directorios de bibliotecas adicionales) Cuando se utiliza una biblioteca de terceros que se proporciona con un archivo lib, no hacerlo generalmente genera un error.

También puede pasar que te olvidespara 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 se compile automáticamente (aunque los archivos pueden, manualmente, excluirse individualmente de la compilación).

En la programación de Windows, la señal reveladora 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 indicar 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".

Respondido

24 de septiembre, 2012 a las 23:37

Luchian Grigore

Luchian Grigore

255k

66

66 insignias de oro

458

458 insignias de plata

626

626 insignias de bronce

1

4

Sería bueno si pudieras cubrir explícitamente el error común de gcc main.c en lugar de gcc main.c other.c (que los principiantes suelen hacer antes de que sus proyectos crezcan tanto como para crear archivos .o). .

&norteguion; M.M

4 de noviembre de 2019 a las 20:43



------------------------------------

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

Una declaración de variable típica es

extern int x;

Como esto es sólo una declaración, se necesita una única 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

Se aplican observaciones similares a las funciones. Declarar una función sin definirla genera el 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, es posible que los calificadores de cv no coincidan:

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 discrepancias incluyen

Función/variable declarada en un espacio de nombres, definida en otro. Función/variable declarada como miembro de clase, definida como global (o viceversa). El tipo de retorno de la función, el número y tipo de parámetro y la convención de llamada no coinciden exactamente.

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úrate de que todos los detalles coincidan.

1

1

En VS, los archivos cpp que coinciden con los del encabezado #includes que no se agregan al directorio de origen también entran en la categoría de definiciones faltantes.

- Laurie Stearn

30/03/2018 a las 12:18



------------------------------------

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

El orden en el que se vinculan las bibliotecas SÍ importa si las bibliotecas dependen unas de otras. En general, si la biblioteca A depende de la biblioteca B, entonces libA DEBE aparecer antes que libB en los indicadores 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;
};

Crear 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

Entonces, para repetir nuevamente, el orden SÍ¡Importa!

3

Un dato curioso es que en mi caso tenía un archivo objeto que depende de una biblioteca compartida. Tuve que modificar el Makefile y poner 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 Sila

17/09/2016 a las 16:28

Creo que el orden de las bibliotecas en la línea de comando sólo importa para el enlazador GNU (GCC). Con MSVC la orden no tiene efecto, y tampoco con el enlazador de Apple.

- prapin

18/10/2023 a las 18:10

Si hay una referencia en el orden de los "enlaces", compártala.

- Nube Cho

6 dic 2023 a las 23:20



------------------------------------

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

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

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

El vinculador de C++ espera que los nombres estén alterados, por lo que debes declarar la función como:

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

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

extern "C" void foo();

e intenta utilizarlo 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"
}

3

8

O por el contrario, si desarrolla una biblioteca C, una buena regla es proteger los archivos de encabezado rodeando todas las declaraciones exportadas con #ifdef __cplusplus [\n] extern"C" { [\n] #endif y #ifdef __cplusplus [\n] } [\n] #endif ([\n] es un retorno de carro real pero no puedo escribir esto correctamente en el comentario).

Bentoy13

8 julio 2015 a las 14:37

Como en el comentario anterior, la opción 'Creación de encabezados en idiomas mixtos' La sección aquí ayudó: oracle.com/technetwork/articles/servers-storage-dev/…

- zanbri

23/11/2016 a las 13:58

Esto también puede suceder si incluye su archivo de encabezado C++ normal por accidente rodeado de C externo: extern "C" { #include <myCppHeader.h> }.

-ComFreek

25/03/2020 a las 10:15



------------------------------------

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

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

nota: yo useg++ y Linux y todos los ejemplos son para ello

Por ejemplo, tenemos algún 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);
}

Crear archivos objeto

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

Después de la fase de ensamblador, tenemos un archivo objeto que contiene los símbolos 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 la salida porque no importan

Entonces, vemos símbolos de seguimiento 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

Vincular nuestros archivos de objetos

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

y ejecutarlo

$ ./prog
123

El vinculador ve los símbolos exportados y los 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), debido a que solo creamos un archivo objeto, el enlace aún no está 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

Esto ha sucedido porque nuestro local_var_name es estático, es decir, no es visible para otros módulos. Ahora más profundamente. Obtener el resultado 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 ninguna etiqueta para local_var_name, es por eso que el vinculador no la ha encontrado. Pero somos hackers :) 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 establecimos su valor en 456789. Intente crear un archivo objeto a partir de él

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

ok, mira la 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 (era LOCAL)

enlace

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

y ejecutarlo

$ ./prog 
123456789

Vale, lo hackeamos :)

Entonces, como resultado, una "referencia indefinida/externa no resueltaError de símbolo" ocurre cuando el vinculador no puede encontrar símbolos globales en los archivos objeto.

Respondido al

3 de diciembre de 2013 a las 18:11

sgryzko

sgryzko

2,407

2

2 insignias de oro

23

23 insignias de plata

37

37 insignias de bronce

0



------------------------------------

Implementaciones de plantillas no visibles.

Las plantillas no especializadas deben tener sus definiciones visibles para todas las unidades de traducción.sombrero usarlos. Eso significa que no puedes 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 archivo impl 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 este problema, debe mover la definición de X::foo al archivo de encabezado o a algún lugar visible para la unidad de traducción que lo utiliza.

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

Para obtener más explicaciones y otra posible solución (creación de instancias explícita), consulte esta pregunta y respuesta.

0



------------------------------------

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

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 definición de tipo o cualquier cosa excepto aquellos nombres y signos que pertenecen al lenguaje C++. Está definido por el usuario o introducido por una biblioteca de dependencia (otro definido por el usuario).

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

C. ¿Qué es “resolver”? Resolver es un término de tiempo de vinculación. En tiempo de vinculación, el vinculador intenta encontrar la definición externa de cada símbolo en los archivos objeto que no puede encontrar su definición internamente. El alcance de este proceso de búsqueda incluye:

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

Este proceso de búsqueda esllamado resolución.

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 definición interna, informa un error de símbolo externo sin resolver.

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 se pueden ordenar como:

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, digamos bar():

void bar()
{
    foo();
}

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

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 dependencia adicional (establecida desde: Proyecto | Propiedades | Propiedades de configuración | Vinculador | Entrada | Dependencia adicional) de la configuración de su proyecto. Ahora el vinculador informará un LNK2019 ya que la definición no existe en el ámbito de búsqueda actual.

Respondido

13 de abril de 2015 a las 16:42

Nima Soroush

Nima Soroush

12.4k

4

4 insignias de oro

53

53 insignias de plata

54

54 bronce insignias

0



------------------------------------

Importación/exportación incorrecta de métodos/clases entre 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 se suele obtener mediante el uso de una macro:

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

La macro THIS_MODULE solo se definiría en el módulo que exporta la función. De esa forma, 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 que vinculó (consulte también 1)).

Puedes importar/exportar clases de manera similar:

class DLLIMPEXP X
{
};

2

4

Para ser completa, esta respuesta debe mencionar la visibilidad de GCC y la visibilidad de Windows. Archivos .def, ya que estos también influyen en el nombre del símbolo y presencia.

Rubenvb

23 dic 2012 a las 15:39

@rubenvb Hace mucho que no uso archivos .def. No dudes en agregar una respuesta o editar esta.

- Luchian Grigore

28 dic 2012 a las 19:14



------------------------------------

referencia no definida a WinMain@16 o una referencia de punto de entrada main() 'inusual' similar (especialmente para visual-studio).

Es posible que no hayas elegido el tipo de proyecto correcto con tu IDE actual. el yoEs posible que DE desee vincularse, p. La aplicación de Windows se proyecta en dicha función de punto de entrada (como se especifica en la referencia que falta arriba), en lugar de la comúnmente utilizada int main(int argc, char** argv); firma.

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

Aquí se analizan con más detalle el caso 1 y el caso 2 a partir de un problema del mundo real.

0



------------------------------------

Microsoft ofrece un #pragma para hacer referencia a la biblioteca correcta en el momento del enlace;

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

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



------------------------------------

El paquete NuGet de Visual Studio 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 lo actualice, pero a mí me funcionó pirateando una configuración adicional para VS2013, apuntando a los archivos de la biblioteca VS2012.

Edité el paquete (en la carpeta de paquetes dentro de la dirección de la solución).tory) buscando nombre_paquete\build\native\nombrepaquete.targets y dentro de ese archivo, copiando todas las secciones v110. Cambié v110 a v120 en los campos de condición, solo teniendo mucho cuidado de dejar todas las rutas de los nombres de archivos como v110. Esto simplemente permitió que Visual Studio 2013 se vinculara a las bibliotecas de 2012 y, en este caso, funcionó.

4

1

Esto parece demasiado específico; tal vez un hilo nuevo sería un mejor lugar para esta respuesta.

- Luchian Grigmineral

17/01/2015 a las 13:56

3

@LuchianGrigore: Quería publicar aquí porque esa pregunta era exactamente este problema, pero estaba marcada como un duplicado de esta pregunta, por lo que no pude responderla allí. Así que publiqué mi respuesta aquí.

- Malvínico

17/01/2015 a las 14:00

Esa pregunta ya tiene una respuesta aceptada. Está marcado como duplicado porque el generalLa causa se enumera arriba. ¿Qué pasaría si tuviéramos aquí una respuesta para cada problema con una biblioteca que no esté incluida?

- Luchian Grigore

17/01/2015 a las 14:29

6

@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 gew. Esa pregunta es sobre un problema con el paquete de Visual Studio.e sistema de gestión, y mi respuesta explica el motivo y proporciona una solución alternativa. Alguien acaba de ver problemas externos no resueltos. y supuse que era un problema de vinculación estándar cuando en realidad es un problema de administración de paquetes.

- Malvínico

17/01/2015 a las 14:40



------------------------------------

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 toda la solución (supongamos que tenemos jSolo un proyecto en la solución)

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

Visual Studio busca 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 tanto, no debe asumir que el archivo main.cpp se compila primero. Si los archivos .cpp dependen de archivos .h adicionales para encontrar símbolos que puede o no estar definido 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 objeto .o y además Visual Studio escribe el resultado en un archivo llamado ProjectName.Cpp.Clean.txt que contiene todos los archivos objeto que debe procesar el vinculador.

El segundo paso de la compilación iEsto lo realiza Linker. Linker debe fusionar todo el archivo objeto y generar finalmente la salida (que puede ser un 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 los encabezados (por ejemplo: el código de un método de una clase como se menciona en respuestas anteriores, o el evento de 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 objeto, 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 -> Vinculador -> Entrada para especificar el nombre de la biblioteca. -Si el Linker no pudo encontrard el símbolo que escribe en un .cpp, genera un error de tiempo del vinculador que puede sonar como error LNK2001: símbolo externo no resuelto "void __cdecl foo(void)" (?foo@@YAXXZ)

Observación

Una vez que el vinculador encuentra un símbolo, no lo busca en otras bibliotecas. El orden de vinculación de bibliotecas sí importa. 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 el tiempo de ejecución puede fallar. ocurrir

Cómo solucionar este tipo de error

Error de hora del compilador:

Asegúrese de escribir la sintaxis correcta de su proyecto en C++.

Error de hora del vinculador

Defina todos los símbolos que declara en sus archivos de encabezado Utilice #pragma una vez para siempreBajar el compilador para no incluir un encabezado si ya estaba incluido en el .cpp actual que está compilado. 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 utilice la plantilla, asegúrese 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 instancia. Respondido al

11 de agosto de 2015 a las 15:33

usuario4272649

usuario4272649

4

¿No es tu respuesta específica para Visual Studio? La pregunta no especifica ninguna herramienta de compilación/IDE, por lo que su respuesta es inútil para partes que no son de Visual Studio.

Vicepresidente.

13/08/2015 a las 9:23

Tienes razón. Pero cada proceso de compilación/vinculación de IDE se realiza de manera ligeramente diferente. Pero los archivos se procesan exactamente igual (incluso g++ hace lo mismo cuando analiza las banderas...)

usuario4272649

13/08/2015 a las 13:01

En realidad, el problema no tiene que ver con el IDE, sino con una respuesta a los problemas de vinculación. Los problemas de vinculación no están relacionados con el IDE sino con el compilador y el proceso de compilación.

Vicepresidente.

13/08/2015 a las 13:02

Sí, pero el proceso de compilación/vinculación se realiza en g++/Visual Studio (compilador proporcionado por Microsoft para VS)/Eclipse/Ne.t Frijoles de la misma manera

usuario4272649

13/08/2015 a las 13:06



------------------------------------

Utilice el vinculador para ayudar a diagnosticar el error.

La mayoría de los enlazadores modernos incluyen una opción detallada que se imprime en distintos 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, Rutas de búsqueda utilizadas.

Para gcc y clang; normalmente agregaría -v -Wl,--verbose o -v -Wl,-v a la línea de comando. Puede encontrar más detalles aquí;

Página de manual de Linux. Página del vinculador LLVM. "Una introducción al CCG" Capítulo 9.

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

La página de MSDN en la opción del vinculador /VERBOSE.



------------------------------------

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 solucionar el error.

Pasos a seguir si crees que podría ser un error en el compilador/IDE:

Limpiar el proyecto (algunos IDE tienen una opción para hacer esto, también puedes hacerlo manualmente eliminando los archivos objeto) Intente iniciar un nuevo proyecto, copiando todo el código fuente del original.

2

5

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 que un compilador haya causado el problema. Limpiar su solución o volver a crear su configuración de compilación puede corregir 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

1 de febrero de 2016 a las 20:26

4

@JDiMatteo Hay 21 respuestas a esta pregunta y, por lo tanto, una cantidad significativa de respuestas no serán "probables" solución. Si descarta todas las respuestas que están por debajo de su umbral de probabilidad, entonces esta página se vuelve inútil ya que la mayoría de los casos comunes se detectan fácilmente de todos modos.

- brettwhiteman

1 de febrero de 2016 a las 22:00



------------------------------------

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 el 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 una LIB y vinculé TestProject a este archivo .lib (copio y pego el archivo .lib generado en la carpeta TestProject). Luego puedo volver a compilar 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.

1

Esto se parece mucho a "No sé lo que estoy haciendo, así que solo intento cosas aleatorias hasta que el enlazador deja de quejarse; No sé qué problema hice desaparecer”. Esto no es útil.

-IInbsp;Inspectable

2 de diciembre de 2023 a las 9:50



------------------------------------

Dado que la gente parece dirigirse a esta pregunta cuando se trata de errores del vinculador, voy a agregar esto aquí.

Una posible reaEl problema de los errores del vinculador con GCC 5.2.0 es que ahora se elige una nueva biblioteca ABI libstdc++ de forma predeterminada.

Si obtiene errores del vinculador sobre referencias no definidas 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 objeto que fueron compilados con valores diferentes. para la macro _GLIBCXX_USE_CXX11_ABI. Esto sucede 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 la nueva ABI, deberá volver a compilar su código con la antigua ABI.

Entonces, si de repente recibes errores del vinculador al cambiar a GCC después de la versión 5.1.0, esto sería algo que deberías revisar.



------------------------------------

Su enlace consume bibliotecas antes que los archivos objeto que hacen referencia a ellas. Está intentando compilar y vincular su programa con la cadena de herramientas GCC. Su enlace especifica todas las bibliotecas necesarias y las rutas de búsqueda de bibliotecas. Si libfoo depende de libbar, entonces su enlace coloca correctamente libfoo antes de libbar. Su vinculación falla con una referencia indefinida a algunos errores. Pero todas las cosas indefinidas se declaran en los archivos de encabezado que tiene #incluidos y, de hecho, están definidos en las bibliotecas que está vinculando.

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

Un ejemplo mínimo que involucra una biblioteca estática queconstruido usted mismo

mi_lib.c

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

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

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

Tú construyes tu biblioteca estática:

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

Compilas tu programa:

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

Intentas vincularlo con libmy_lib.a y fallas:

$ 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 con libz y 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 compilas y vinculas de una sola 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 del 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 objeto y bibliotecas que desea vincular para realizar su programa, estas colocando las bibliotecas bantes de los archivos objeto que hacen referencia a ellos. Debe colocar las bibliotecas después de los archivos objeto que hacen referencia a ellos.

Vincular el ejemplo 1 correctamente:

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

Éxito:

$ ./eg1 
Hello World

Vincular el ejemplo 2 correctamente:

$ gcc -o eg2 eg2.o -lz

Éxito:

$ ./eg2 
1.2.8

Vincula correctamente la variación pkg-config del ejemplo 2:

$ gcc -o eg2 eg2.o $(pkg-config --libs zlib) 
$ ./eg2
1.2.8
La explicación

La lectura es opcional a partir de ahora.

De forma predeterminada, un comando de vinculación generado por GCC, en su distribución, consume los archivos en el enlace de izquierda a derecha en secuencia de línea de comando. Cuando descubre que un archivo hace referencia a algo y no contiene una definición para ello, buscará una definición en archivos más a la derecha. Si finalmente encuentra una definición, el La referencia está resuelta. Si al final queda alguna referencia sin resolver, elEl enlace falla: el enlazador no busca hacia atrás.

Primero, ejemplo 1, con la biblioteca estática my_lib.a

Una biblioteca estática es un archivo indexado de archivos objeto. Cuando el enlazador encuentra -lmy_lib en la secuencia de enlace y descubre que esto se refiere a la biblioteca estática ./libmy_lib.a, quiere saber si su programa necesita cualquiera de los archivos objeto en libmy_lib.a.

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

El vinculador decidirá que su programa necesita my_lib.o si y sólo si ya lo sabe su programa hace referencia a hw, en uno o más de los archivos objeto que ya tiene agregado al programa, y ​​que ninguno de los archivos objeto que ya ha agregado contiene una definición paracómo.

Si eso es cierto, entonces el vinculador extraerá una copia de my_lib.o de la biblioteca y añádelo a tu programa. Entonces, su programa contiene una definición para hw, entonces se resuelven sus referencias a hw.

Cuando intentas vincular el programa como:

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

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

Entonces el vinculador no agrega my_lib.o al programa y no tiene más utilizar para libmy_lib.a.

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

Segundo, ejemplo 2, con la biblioteca compartida libz

Una biblioteca compartida no es un archivo de archivos objeto ni nada parecido. Es mucho más como un programa que no tiene una función principal y en su lugar, expone muchos otros símbolos que define, de modo que otros los programas pueden usarlos en tiempo de ejecución.

Hoy en día, muchas distribuciones de Linux configuran su cadena de herramientas GCC para que sus controladores de idioma (gcc,g++,gfortran, etc.) Indique al vinculador del sistema (ld) que vincule bibliotecas compartidas según sea necesario. Tienes una de esas distribuciones.

Esto significa que cuando el vinculador encuentra -lz en el vínculosecuencia, y descubre que esto se refiere a la biblioteca compartida (digamos) /usr/lib/x86_64-linux-gnu/libz.so, quiere saber si alguna referencia que haya agregado a su programa que aún no esté definida tiene definiciones exportadas por libz

Si eso es cierto, entonces el vinculador no copiará ningún fragmento de libz y agréguelos a su programa; en su lugar, simplemente modificará el código de su programa. para que:-

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

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

Su programa quiere hacer referencia solo auna cosa que tiene una definición exportada por libz, es decir, la función zlibVersion, a la que se hace referencia sólo una vez, en eg2.c. Si el vinculador agrega esa referencia a su programa y luego encuentra la definición exportado por libz, la referencia está resuelta

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 en eg2.o, que aún no se ha visto. Entonces el El enlazador decide que libz no sirve para nada. Cuando llega a eg2.o, lo agrega al programa, y luego tiene una referencia indefinida a zlibVersion, la secuencia de vinculación finaliza; esa referencia no está resuelta y el vínculo falla.

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

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 vínculo:

gcc -o eg2 -lz eg2.o

¡Funciona muy bien para ti!

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

Eso es porque 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 aquel entonces, era normal que los sistemas tipo Unix vincularan archivos estáticos y compartidos. bibliotecas según reglas diferentes. Se vincularon bibliotecas estáticas en una secuencia de enlace según sea necesario, como se explica en el ejemplo 1, pero las bibliotecas compartidas estaban vinculadas incondicionalmente.

EstoEl comportamiento de s es económico en el momento del enlace porque el enlazador no tiene que reflexionar si el programa necesita una biblioteca compartida: si es una biblioteca compartida, vincularlo. 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 causar que las bibliotecas compartidas sean cargado junto con un programa incluso si no los necesita.

Las diferentes reglas de vinculación para bibliotecas estáticas y compartidas pueden resultar confusas a programadores inexpertos, que tal vez no sepan si -lfoo en su enlace se va a resolver en /some/where/libfoo.a o en /some/where/libfoo.so, y es posible que no comprenda la diferencia entre bibliotecas compartidas y estáticas de todos modos.

Esta compensación ha llevado a la situación cismática actual. Algunas distribuciones tienen chanModificó sus reglas de vinculación de GCC para bibliotecas compartidas para que, según sea necesario, El principio se aplica a todas las bibliotecas. Algunas distribuciones se han quedado con la antigua camino.

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

Si lo hago:

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

seguramente gcc tiene que compilar eg1.c primero y luego vincular el resultado archivo de objeto con libmy_lib.a. Entonces, ¿cómo es posible que no conozca ese archivo objeto? ¿Se necesita cuando se realiza el enlace?

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

Cuando ejecutas el comando anterior, gcc descubre que deseas compilar + enlace. Entonces, detrás de escena, genera un comando de compilación y ejecuta luego genera un comando de vinculación y lo ejecuta, como si hubiera ejecutado el dos comandos:

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

Así que el enlace falla igual que si ejecutas esos dos comandos. El La única diferencia que notas en el fallo es que gcc ha generado un archivo de objeto temporal en el caso de compilación + enlace, porque no lo estás diciendo utilizar eg1.o. Vemos:

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

en lugar de:

eg1.o: In function `main':
Ver también

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

Poner las bibliotecas interdependientes en el orden incorrecto es sólo una forma en el que puede obtener archivos que necesitan definiciones de lo que viene más adelante en el enlace que los archivos que proporcionan las definiciones. Anteponer las bibliotecas a la archivos objeto que hacen referencia a ellos es otra forma de cometer el mismo error.



------------------------------------

Un contenedor de GNU ld que no admite scripts de vinculación

Algunos archivos .so son en realidad scripts del enlazador GNU ld, p.e. 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 el contenedor gcc de mainwin, mwdip, descarta los archivos de comando del script del vinculador en la lista de salida detallada de bibliotecas para vincular. Una solución sencilla es reemplazar el comando de entrada del script del vinculador. archivo con una copia del archivo (o un enlace simbólico), por ejemplo

cp libtbb.so.2 libtbb.so

O puede reemplazar el argumento -l con la ruta completa del .so, p.e. en lugar de -ltbb do /home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2



------------------------------------

Plantillas para hacerse amigo...

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 operador<< se declara como una función que no es de plantilla. Para cada tipo T usado con Foo, debe haber un operador sin plantilla<<. Por ejemplo, si hay un tipo Foo<int> declarado, entonces debe haber una implementación del operador de la siguiente manera;

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

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

Para corregir esto, puededeclare un operador de plantilla antes del tipo Foo y luego declare como amigo, la instanciación 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, el operador<< <int> La creación de instancias está limitada al acceso a los miembros privados de la creación de instancias 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 operador<< se puede hacer 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", enly para búsqueda dependiente de argumentos, desde cppreference;

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

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

Lista de códigos que muestran las técnicas anteriores.

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

advertencia: declaración de amigo 'std::ostream& operador<<(...)' declara una función que no es de plantilla [-Wnon-template-friend]

nota: (si esto no es lo que pretendía, asegúrese de que la plantilla de funcióne ya ha sido declarado y agregue <> después del nombre de la función aquí)



------------------------------------

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 están sincronizados. Déjame explicarte.

¿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 del vinculador si el vinculador no encuentra una definición de función que coincida perfectamente.

¿Es posible seguir recibiendo un error del vinculador a pesar de que la declaración¿La acción y la definición parecen coincidir? ¡Sí! Puede que tengan el mismo aspecto en el código fuente, pero realmente depende de lo que vea el compilador. Básicamente, 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 parecen idénticas en el código fuente, en realidad son diferentes según el compilador.

Quizás te preguntes 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 a header1.h y terminas usando header2.h en tu propio programa, te quedarás rascándote el encabezado preguntándote qué pasó (nunca mejor dicho).

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

Mayor elaboración con un ejemplo.

Tengo dos proyectos: gráficos.lib y 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! Recibes un error del vinculador y no tienes idea de por qué falla. La razón es que la biblioteca común usa diferentes versiones del mismo include common_math.h (lo he hecho obvio aquí en el ejemplo al incluir una ruta diferente, pero puede que no siempre sea tan obvio. Tal vez la ruta de inclusión sea diferente en el configuración del compilador).

Tenga en cuenta que en este ejemplo, el vinculador le dirá que no pudo encontrar 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 enlazador ve una diferencia.t firma porque los tipos de parámetros son ligeramente diferentes. En el ejemplo, vec3 es de 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).

Depurando el enlazador

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

El proceso es el siguiente:

Tenga en cuenta el extraño nombre destrozado que aparece en el error del vinculador. (por ejemplo, dibujar@gráficos@XYZ). Vuelque los símbolos exportados de la biblioteca a un archivo de texto. Busque el símbolo de interés exportado y observe que el nombre alterado es diferente. Preste atención a por qué los nombres destrozados terminaron diferentes. usted sería capaz de sObserve que los tipos de parámetros son diferentes, aunque se vean iguales en el código fuente. 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 entre sí para producir una biblioteca o un ejecutable.

EDICIÓN 1: Se reescribió la primera sección para que sea más fácil de entender. Por favor comente a continuación para informarme si es necesario solucionar algo más. ¡Gracias!

0



------------------------------------

Definiciones UNICODE inconsistentes

Una compilación UNICODE de Windows se crea con TCHAR, etc. definido como wchar_t, etc. Cuando no se compila con UNICODE definido como compilación con TCHAR definido como char, etc. Estas definiciones UNICODE y _UNICODE afectan a todos los tipos de cadenas "T"; LPTSTR, LPCTSTR y sus alces.

Construir una biblioteca con UNICODE definido e intentar vincularla en un proyecto donde UNICODE no está definido dará como resultado errores del vinculador ya que habrá una discrepancia en la definición de TCHAR; char frente a wchar_t.

El error generalmente incluye una función y un valor con un tipo derivado char o wchar_t, estos podrían incluir std::basic_string<> etc. también. Al explorar la función afectada en el código, a menudo habrá una referencia a TCHAR o std::basic_string<TCHAR> etc. Esta es una señal reveladora de que el código fue diseñado originalmente para UNICODE y Multi-Byte Ch.estructura de carácter (o "estrecha").

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

Esto se puede hacer con cualquiera de los dos;

#define UNICODE
#define _UNICODE

O en la configuración del proyecto;

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

O en la línea de comando;

/DUNICODE /D_UNICODE

La alternativa también es aplicable, si no se pretende utilizar UNICODE, asegúrese de que las definiciones no estén configuradas y/o que la configuración de varios caracteres se utilice en los proyectos y se aplique de manera consistente.

No olvides ser coherente también entre las compilaciones "Versión" y "Depuración".



------------------------------------

Limpiar y reconstruir

Un ambiente "limpio" de la construcción puede eliminar la "madera muerta" que pueden quedar tirados por 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 algún tipo de interfaz "limpia". función, pero es posible que no esté 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 el producto "limpio" haya completado, verifique que el proceso "limpio" se ha realizado correctamente y todos los archivos intermedios generados (por ejemplo, un archivo MAKE automatizado) se han eliminado correctamente.

Este proceso puede verse como un último recurso, pero suele ser un buen primer paso.; especialmente si el código relacionado con el error se agregó recientemente (ya sea localmente o desde el repositorio fuente).



------------------------------------

Falta "externo" en declaraciones/definiciones de variables constantes (solo C++)

Para las personas que vienen de C, puede ser una sorpresa que en C++ las variables constantes globales tengan enlaces internos (o estáticos). En C este no era el caso, ya que todas las variables globales son implícitamente externas (es decir, cuando falta la palabra clave estática).

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 utilizar un archivo de encabezado e incluirlo en file2.cpp y file1.cpp

extern const int test;
extern int test2;

Alternativamente se podríadeclarar la variable constante en file1.cpp con extern explícito



------------------------------------

Aunque se trata de 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 referirme a std::filesystem::path: el sistema de archivos está en la biblioteca estándar desde C++17 pero mi programa también necesitaba compilarse en C++14, así que decidí usar un alias de 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::sistema de archivos> ay contiene el código anterior file.cpp, la implementación de file.h, "file.h" de #include main.cpp #incluye <sistema de archivos> y "archivo.h"

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

$ g++ -g -std=c++17 -c main.cpp -> compila main.cpp en main.o $ g++ -g -std=c++17 -c file.cpp -> compila file.cpp y file.h en file.o $ g++ -g -std=c++17 -o ejecutable main.o file.o -lstdc++fs -> enlaces main.o y file.o

De esta manera, cualquier función contenida en file.o y utilizada en main.o que requiriera path_t daba errores de "referencia indefinida" porque main.o se refería a std::filesystem::path pero file.o a std::experimental::sistema de archivos::ruta.

Resolución

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



------------------------------------

Al vincular 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 la opción -fvisibility=hidden, solo las funciones/símbolos marcados con __attribute__ ((visibilidad ("predeterminado"))) son externos en el objeto compartido resultante.

Puedes comprobar si los símbolos que estás buscando son externosinvocando:

# -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 mediante nm con el tipo de símbolo en minúsculas, por ejemplo t en lugar de `T para sección de código:

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

También puedes usar nm con la opción -C para exigir los nombres (si se usó C++).

De manera similar a los archivos DLL de Windows, se marcarían las funciones públicas con una definición, por ejemplo DLL_PUBLIC definida 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

Puede encontrar más información sobre la visibilidad en la wiki de gcc.

Cuando una unidad de traducción se compila con -fvisibility=hidden, los símbolos resultantes todavía tienen un enlace externo (se muestran con el tipo de símbolo en mayúsculas por nm) y se pueden usar para el enlace externo sin problemas si los archivos objeto pasan a formar parte de una biblioteca estática. . El vínculo se vuelve local.sólo cuando los archivos objeto están vinculados a una biblioteca compartida.

Para encontrar qué símbolos en un archivo 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

0



------------------------------------

Se produce un error de “Referencia no definida” cuando tenemos una referencia al nombre de un objeto (clase, función, variable, etc.) en nuestro programa y el vinculador no puede encontrar su definición cuando intenta buscarlo en todos los objetos vinculados. archivos y bibliotecas.

Por lo tanto, cuando el vinculador no puede encontrar la definición de un objeto vinculado, genera un error de "referencia no definida". Como se desprende claramente de la definición, este error ocurre en las últimas etapas de la vinculación p.proceso. Hay varias razones que causan un error de "referencia indefinida".

Alguna posible razón (más frecuente):

#1) No se proporciona ninguna definición para el objeto

Ésta es la razón más sencilla para provocar un error de "referencia no definida". El programador simplemente se ha olvidado de definir el objeto.

Considere el siguiente programa C++. Aquí solo especificamos el prototipo de la función y luego lo usamos en la función principal.

#include <iostream>
int func1();
int main()
{
     
    func1();
}

Salida:

main.cpp:(.text+0x5): undefined reference to 'func1()'
collect2: error ld returned 1 exit status

Entonces, cuando compilamos este programa, se emite el error del vinculador que dice "referencia indefinida a 'func1()'".

Para eliminar este error, corregimos el programa de la siguiente manera proporcionando la definición de la función func1. Ahora el programa proporciona el resultado adecuado.

#include <iostream>
using namespace std;
int func1();
 
int main()
{
     
    func1();
}
int func1(){
    cout<<"hello, world!!";
}

Salida:

hello, world!!

#2)ong Definición (las firmas no coinciden) de los objetos utilizados

Otra causa más del error de “referencia no definida” es cuando especificamos definiciones incorrectas. Usamos cualquier objeto en nuestro programa y su definición es algo diferente.

Considere el siguiente programa C++. Aquí hemos realizado una llamada a func1(). Su prototipo es int func1(). Pero su definición no coincide con su prototipo. Como vemos, la definición de la función contiene un parámetro para la función.

Por lo tanto, cuando se compila el programa, la compilación es exitosa debido a la coincidencia del prototipo y la llamada a la función. Pero cuando el vinculador intenta vincular la llamada a la función con su definición, encuentra el problema y emite el error como "referencia no definida".

#include <iostream>
using namespace std;
int func1();
int main()
{
     
    func1();
}
int func1(int n){
    cout<<"hello, world!!";
}

Salida:

main.cpp:(.text+0x5): undefined reference to 'func1()'
collect2: error ld returned 1 exit status

Para evitar tales eerrores, simplemente verificamos si las definiciones y el uso de todos los objetos coinciden en nuestro programa.

#3) Archivos objeto no vinculados correctamente

Este problema también puede dar lugar al error de "referencia indefinida". Aquí podemos tener más de un archivo fuente y podemos compilarlos de forma independiente. Cuando se hace esto, los objetos no están vinculados correctamente y el resultado es una "referencia indefinida".

Considere los siguientes dos programas C++. En el primer archivo, utilizamos la función "print ()" que está definida en el segundo archivo. Cuando compilamos estos archivos por separado, el primer archivo proporciona una "referencia indefinida" para la función de impresión, mientras que el segundo archivo proporciona una "referencia indefinida" para la función principal.

int print();
int main()
{
    print();
}

Salida:

main.cpp:(.text+0x5): undefined reference to 'print()'
collect2: error ld returned 1 exit status

int print() {
    return 42;
}

Salida:

(.text+0x20): undefined reference to 'main'
collect2: error ld returned 1 exit status

El camino haciaresolver este error es compilar ambos archivos simultáneamente (por ejemplo, usando g++).

Aparte de las causas ya comentadas, la “referencia indefinida” también puede ocurrir por las siguientes razones.

#4) Tipo de proyecto incorrecto

Cuando especificamos tipos de proyectos incorrectos en IDE de C++ como Visual Studio e intentamos hacer cosas que el proyecto no espera, obtenemos una "referencia indefinida".

#5) Sin biblioteca

Si un programador no ha especificado la ruta de la biblioteca correctamente o se ha olvidado por completo de especificarla, obtenemos una "referencia indefinida" para todas las referencias que el programa utiliza de la biblioteca.

#6) Los archivos dependientes no se compilan

Un programador debe asegurarse de compilar todas las dependencias del proyecto de antemano para que cuando compilamosEn el proyecto, el compilador encuentra todas las dependencias y compila correctamente. Si falta alguna de las dependencias, el compilador proporciona una "referencia indefinida".

Aparte de las causas comentadas anteriormente, el error de “referencia indefinida” puede ocurrir en muchas otras situaciones. Pero la conclusión es que el programador se equivocó y, para evitar este error, debe corregirlo.

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare