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

Resuelto Confused_boy asked hace 11 años • 0 respuestas

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

Confused_boy avatar Sep 25 '12 05:09 Confused_boy
Aceptado

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 get()el símbolo se definió en algún lugar , pero aún no le importa dónde. La fase de vinculación es responsable de encontrar el símbolo y vincular correctamente los archivos objeto producidos a partir de a.cppy b.cpp.

Si a.cppno lo definiera get, obtendría un error del vinculador que diría "referencia no definida" o "símbolo externo no resuelto".

Redacción estándar de C++

La compilación de un programa C++ se lleva a cabo 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 significa 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á utilizando Microsoft Visual Studio, verá que los proyectos generan .libarchivos. 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 los utilizan .lib(si corresponde).

Existen mecanismos similares para otros compiladores/plataformas.

Los mensajes de error comunes son error LNK2001,, para Microsoft Visual Studio y symbolName para GCC .error LNK1120error LNK2019undefined reference to

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

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
  • UNICODEDefiniciones inconsistentes
  • Falta "externo" en declaraciones/definiciones de variables constantes (solo C++)
  • Código de Visual Studio no configurado para un proyecto de varios archivos
  • Errores en Mac OS X al crear un dylib, pero un .so en otros sistemas Unix-y está bien
Luchian Grigore avatar Sep 24 '2012 22:09 Luchian Grigore

Miembros de la clase:

Un virtualdestructor 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 se llama a los destructores de la clase base cuando el objeto se destruye implícitamente, por lo que se requiere una definición.

virtualLos métodos deben implementarse o definirse como puros.

Esto es similar a virtuallos métodos sin definición, con el razonamiento adicional de que la declaración pura genera una tabla virtual ficticia y es posible que obtenga el error del vinculador sin usar la función:

struct X
{
    virtual void foo();
};
struct Y : X
{
   void foo() {}
};
int main()
{
   Y y; //linker error although there was no call to X::foo
}

Para que esto funcione, declara X::foo()como puro:

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

No virtualmiembros de la clase

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

struct A
{ 
    ~A();
};

Lo siguiente produciría el error:

A a;      //destructor undefined

La implementación puede 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 para inlineevitar 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ón debería ser

void A::foo() {}

staticLos miembros de datos 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 static constmiembro de datos 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 static constlos miembros de datos.

Luchian Grigore avatar Sep 24 '2012 23:09 Luchian Grigore

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

Normalmente, 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 usar 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

Aquí libraryNamese muestra 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.sopero solo se escribe -lfoo. En Windows, ese mismo archivo podría llamarse foo.lib, pero usarías el mismo argumento. Es posible que tengas que agregar el directorio donde se pueden encontrar esos archivos usando -L‹directory›. Asegúrate de no escribir un espacio después -lde 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 libse generará un archivo (en uso común). Para usar los símbolos en un proyecto separado, deberá incluir los libarchivos en la configuración del proyecto. Esto se hace en la sección Vinculador de las propiedades del proyecto, en formato Input -> Additional Dependencies. (la ruta al libarchivo debe agregarse en Linker -> General -> Additional Library Directories) Cuando se utiliza una biblioteca de terceros que se proporciona con un libarchivo, no hacerlo generalmente genera un error.

También puede suceder que olvides 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".

Luchian Grigore avatar Sep 24 '2012 23:09 Luchian Grigore