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


¿Qué son referencias indefinidas / errores de símbolos externos no resueltos? ¿Cuáles son las causas comunes y cómo corregirlas / prevenirlas?

Siéntase libre de editar / agregar el suyo.


1194
2017-09-24 22:27


origen


Respuestas:


La compilación de un programa en C ++ se lleva a cabo en varios pasos, según lo especificado por 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 a pie de página].

  1. Los caracteres del archivo de origen físico se asignan, de una manera definida por la implementación, al conjunto de caracteres fuente básico   (introduciendo caracteres de nueva línea para los indicadores de fin de línea) si   necesario. [RECORTE]
  2. Se elimina cada instancia de un carácter de barra inclinada invertida (\) seguida inmediatamente por un carácter de nueva línea, empalmando líneas de origen físico para   formar líneas de origen lógico. [RECORTE]
  3. El archivo fuente se descompone en tokens de preprocesamiento (2.5) y secuencias de caracteres en espacios en blanco (incluidos los comentarios). [RECORTE]
  4. Las directivas de preproceso se ejecutan, las invocaciones de macro se expanden y se ejecutan las expresiones de operador unario de pragma. [RECORTE]
  5. Cada carácter de origen establece un miembro en un literal de carácter o un literal de cadena, así como en cada secuencia de escape y nombre de carácter universal   en un literal de caracteres o literal de una cadena no cruda, se convierte en   el miembro correspondiente del juego de caracteres de ejecución; [RECORTE]
  6. Los tokens literales de cadena adyacentes están concatenados.
  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   traducido 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: [RECORTE]
  9. Todas las referencias de entidades externas se resuelven. Los componentes de la biblioteca están vinculados para satisfacer las referencias externas a entidades no definidas en el   traducción actual. Toda esa salida de traducción se recopila en un   imagen del programa que contiene la información necesaria para la ejecución en su   entorno de ejecución. (énfasis mío)

[nota] Las implementaciones deben comportarse como si estas fases separadas ocurrieran, aunque en la práctica las diferentes fases pueden ser dobladas 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 conjunto de archivos de implementación en archivos de objetos o bibliotecas y ahora desea que funcionen juntos.

Digamos que has definido el símbolo a en a.cpp. Ahora, b.cpp  declarado ese símbolo y lo usó. Antes de vincular, simplemente supone que ese símbolo se definió algun lado, pero todavía no le importa dónde. La fase de enlace es responsable de encontrar el símbolo y vincularlo correctamente a b.cpp (bueno, en realidad al objeto o biblioteca que lo usa).

Si está usando Microsoft Visual Studio, verá que los proyectos generan .lib archivos. Estos 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 enlaza, y los símbolos exportados se proporcionan para las bibliotecas que usan ese .lib (Si alguna).

Existen mecanismos similares para otros compiladores / plataformas.

Los mensajes de error comunes son error LNK2001, error LNK1120, error LNK2019 para Microsoft Visual Studio y undefined reference to  symbolName 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 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:


693
2017-09-24 22:27



Miembros de la clase:

Un puro virtual destructor 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 la clase base se invocan 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 novirtual métodos sin definición, con el razonamiento añadido de que la declaración pura genera un vtable ficticio y puede obtener el error del enlazador 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, declara X::foo() como puro:

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

No-virtual miembros de la clase

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

struct A
{ 
    ~A();
};

Lo siguiente arrojaría el error:

A a;      //destructor undefined

La implementación puede estar en línea, en la definición de la clase en sí:

struct A
{ 
    ~A() {}
};

o afuera:

A::~A() {}

Si la implementación está fuera de la definición de la clase, pero en un encabezado, los métodos deben marcarse como inline para 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() {}

static los miembros de datos deben definirse fuera de la clase en un unidad de traducción única:

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  const miembro de datos de tipo integral o de enumeración dentro de la definición de clase; sin embargo, el uso de odr de este miembro aún requerirá una definición de ámbito de espacio de nombres como se describió anteriormente. C ++ 11 permite la inicialización dentro de la clase para todos static constmiembros de datos.


148
2017-09-24 23:38



No se puede vincular con las bibliotecas / archivos de objeto 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, tiene que vincularse con esos archivos de objetos.

Debajo gcc deberías especificar todos los archivos de objetos que se van a vincular entre sí en la línea de comandos, o compilar los archivos de implementación juntos.

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

los libraryName aquí es simplemente el nombre de la biblioteca, sin adiciones específicas de la plataforma. Por ejemplo, en los archivos de biblioteca Linux usualmente se llaman libfoo.so pero solo escribirías -lfoo. En Windows, ese mismo archivo podría ser llamado foo.lib, pero usarías el mismo argumento. Es posible que deba agregar el directorio donde se pueden encontrar esos archivos usando -L‹directory›. Asegúrate de no escribir un espacio después -l o -L.

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

Debajo MSVS, los archivos agregados a un proyecto tienen automáticamente sus archivos objeto enlazados y un lib archivo se generaría (en uso común). Para usar los símbolos en un proyecto separado, necesidad de incluir el lib archivos en la configuración del proyecto. Esto se hace en la sección Enlazador de las propiedades del proyecto, en Input -> Additional Dependencies. (el camino hacia el lib archivo debe ser agregado en Linker -> General -> Additional Library Directories) Cuando se utiliza una biblioteca de terceros que se proporciona con una lib archivo, no hacerlo generalmente resulta en 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 lo hará compilarlo automáticamente (aunque los archivos pueden, de forma manual, excluirse individualmente de la compilación).

En la programación de Windows, el signo revelador de que no se ha vinculado 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 diga qué biblioteca necesita usar. Por ejemplo, MSDN coloca la información en un cuadro al final de cada función en una sección llamada "Biblioteca".


97
2017-09-24 23:37



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

Una declaración de variable típica es

extern int x;

Como esta es solo una declaración, una definición única es necesario. 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

Comentarios similares se aplican a las funciones. Declarar una función sin definirla lleva 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 implementa coincide exactamente con la que ha declarado. Por ejemplo, es posible que no coincida con los calificadores cv:

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 nombre, definido en otro.
  • Función / variable declarada como miembro de clase, definida como global (o viceversa).
  • El tipo de devolución de la función, el número y los tipos de los parámetros, y la convención de llamadas 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 de cerca con la definición que proporcionó. Asegúrese de que cada detalle coincida.


91
2017-09-24 23:38



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

El orden en que las bibliotecas están vinculadas 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 libB en las banderas del enlazador.

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

Entonces, para repetir nuevamente, la orden HACE ¡importar!


73
2017-07-10 11:46



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

Hacer archivos de objetos

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

Después de la fase de ensamblado, tenemos un archivo de 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 salida, porque no importan

Entonces, vemos seguir 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

Enlace nuestros archivos de objetos

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

y ejecutarlo

$ ./prog
123

Linker ve símbolos exportados y lo vincula. Ahora tratamos de 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 de objeto

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

OK (sin errores), porque solo creamos archivos de objetos, el enlace aún no está hecho. Trata de enlazar

$ 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 está visible para otros módulos. Ahora más profundamente. Obtener 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 etiqueta para local_var_name, por eso el enlazador no lo ha encontrado. Pero somos hackers :) y podemos arreglarlo. 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ía tener como a continuación

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

enlazar

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

y ejecutarlo

$ ./prog 
123456789

ok, lo pirateamos :)

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


63
2017-09-24 23:39



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

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

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

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

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

Equivalentemente, 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 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); el incluir tendrá que ser el siguiente;

extern "C" {
    #include "cheader.h"
}

60
2017-12-03 18:11



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

Hace poco pude deshacerme de un error externo no resuelto en Visual Studio 2012 solo al volver 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. Ni existe para comenzar con. Cuando intente compilar A, el paso de enlace fallará porque no puede encontrar B.lib. A.lib se generará, pero no dll. Luego compila B, que tendrá éxito y generará B.lib. Volver a compilar A ahora funcionará porque ahora se encuentra B.lib.


58
2017-09-24 23:39



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

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

Esta funcionalidad dual 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_MODULE solo 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 vinculó (vea también 1))

Puedes igualmente importar / exportar clases:

class DLLIMPEXP X
{
};

50
2017-09-24 23:38