¿Qué son los errores de símbolo externo de referencia indefinida / sin resolver? ¿Cuáles son las causas comunes y cómo solucionarlas / prevenirlas?
Siéntase libre de editar / agregar el suyo.
¿Qué son los errores de símbolo externo de referencia indefinida / sin resolver? ¿Cuáles son las causas comunes y cómo solucionarlas / prevenirlas?
Siéntase libre de editar / agregar el suyo.
Respuestas:
La compilación de un programa C ++ se lleva a cabo en varios pasos, como se especifica en 2.2 (créditos a Keith Thompson para la referencia) :
La precedencia entre las reglas de sintaxis de la traducción se especifica mediante las siguientes fases [ver nota al pie] .
- Los caracteres del archivo de origen físico se asignan, de una manera definida por la implementación, al conjunto de caracteres de origen básico (introduciendo caracteres de nueva línea para indicadores de fin de línea) si es necesario. [RECORTE]
- Cada instancia de un carácter de barra diagonal inversa (\) seguida inmediatamente por un carácter de nueva línea se elimina, uniendo líneas de origen físicas para formar líneas de origen lógicas. [RECORTE]
- El archivo fuente se descompone en tokens de preprocesamiento (2.5) y secuencias de caracteres de espacio en blanco (incluidos los comentarios). [RECORTE]
- Se ejecutan las directivas de preprocesamiento, se amplían las invocaciones de macros y se ejecutan las expresiones de operador _Pragma unary. [RECORTE]
- Cada miembro del conjunto de caracteres de origen en un literal de caracteres o un literal de cadena, así como cada secuencia de escape y nombre de carácter universal en un literal de caracteres o un literal de cadena no sin formato, se convierte en el miembro correspondiente del conjunto de caracteres de ejecución; [RECORTE]
- Los tokens literales de cadena adyacentes se concatenan.
- Los caracteres de espacio en blanco que separan las fichas ya no son significativos. Cada token de preprocesamiento se convierte en un token. (2.7) Los tokens resultantes se analizan sintáctica y semánticamente y se traducen como una unidad de traducción. [RECORTE]
- Las unidades de traducción traducidas y las unidades de instanciación se combinan de la siguiente manera: [SNIP]
- Todas las referencias de entidades externas se resuelven. Los componentes de la biblioteca están vinculados para satisfacer referencias externas a entidades no definidas en la traducción actual. Todos los resultados del traductor se recopilan en una imagen del programa que contiene la información necesaria para la ejecución en su entorno de ejecución. (énfasis mío)
[nota al pie] Las implementaciones deben comportarse como si ocurrieran estas fases separadas, aunque en la práctica las diferentes fases podrían plegarse juntas.
Los errores especificados ocurren durante esta última etapa de compilación, más comúnmente conocida como vinculación. Básicamente significa que compiló un montón de archivos de implementación en archivos de objetos o bibliotecas y ahora desea que funcionen juntos.
Digamos que definiste el símbolo a
en a.cpp
. Ahora, b.cpp
declaró ese símbolo y lo usó. Antes de vincular, simplemente supone que ese símbolo se definió en algún lugar , pero aún no le importa dónde. La fase de vinculación es responsable de encontrar el símbolo y vincularlo correctamente b.cpp
(bueno, en realidad al objeto o biblioteca que lo usa).
Si está utilizando Microsoft Visual Studio, verá que los proyectos generan .lib
archivos. Contienen una tabla de símbolos exportados y una tabla de símbolos importados. Los símbolos importados se resuelven en las bibliotecas con las que se vincula y los símbolos exportados se proporcionan para las bibliotecas que usan ese.lib
(si corresponde).
Existen mecanismos similares para otros compiladores / plataformas.
Mensajes de error son comunes error LNK2001
, error LNK1120
, error LNK2019
para Microsoft Visual Studio y undefined reference to
symbolName de GCC .
El código:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
struct A
{
virtual ~A() = 0;
};
struct B: A
{
virtual ~B(){}
};
extern int x;
void foo();
int main()
{
x = 0;
foo();
Y y;
B b;
}
generará los siguientes errores con GCC :
/home/AbiSfw/ccvvuHoX.o: In function `main':
prog.cpp:(.text+0x10): undefined reference to `x'
prog.cpp:(.text+0x19): undefined reference to `foo()'
prog.cpp:(.text+0x2d): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD1Ev[B::~B()]+0xb): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD0Ev[B::~B()]+0x12): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1Y[typeinfo for Y]+0x8): undefined reference to `typeinfo for X'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1B[typeinfo for B]+0x8): undefined reference to `typeinfo for A'
collect2: ld returned 1 exit status
y errores similares con Microsoft Visual Studio :
1>test2.obj : error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
1>test2.obj : error LNK2001: unresolved external symbol "int x" (?x@@3HA)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall A::~A(void)" (??1A@@UAE@XZ)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall X::foo(void)" (?foo@X@@UAEXXZ)
1>...\test2.exe : fatal error LNK1120: 4 unresolved externals
Las causas comunes incluyen:
#pragma
(Microsoft Visual Studio)UNICODE
Definiciones inconsistentesvirtual
destructor puro necesita una implementación.Declarar un destructor puro aún requiere que lo defina (a diferencia de una función normal):
struct X
{
virtual ~X() = 0;
};
struct Y : X
{
~Y() {}
};
int main()
{
Y y;
}
//X::~X(){} //uncomment this line for successful definition
Esto sucede porque los destructores de clase base se llaman cuando el objeto se destruye implícitamente, por lo que se requiere una definición.
virtual
los métodos deben implementarse o definirse como puros.Esto es similar a los no virtual
métodos sin definición, con el razonamiento adicional de que la declaración pura genera una vtable ficticia y puede obtener el error del vinculador sin usar la función:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
int main()
{
Y y; //linker error although there was no call to X::foo
}
Para que esto funcione, declare X::foo()
como puro:
struct X
{
virtual void foo() = 0;
};
virtual
Miembros no pertenecientes a la claseAlgunos miembros deben definirse incluso si no se usan explícitamente:
struct A
{
~A();
};
Lo siguiente produciría el error:
A a; //destructor undefined
La implementación puede ser en línea, en la propia definición de clase:
struct A
{
~A() {}
};
o afuera:
A::~A() {}
Si la implementación está fuera de la definición de clase, pero en un encabezado, los métodos deben marcarse inline
para evitar una definición múltiple.
Todos los métodos de miembros utilizados deben definirse si se usan.
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 una sola unidad de traducción :struct X
{
static int x;
};
int main()
{
int x = X::x;
}
//int X::x; //uncomment this line to define X::x
Se puede proporcionar un inicializador para un static
const
miembro de datos de tipo integral o de enumeración dentro de la definición de clase; sin embargo, odr-use de este miembro aún requerirá una definición del alcance del espacio de nombres como se describió anteriormente. C ++ 11 permite la inicialización dentro de la clase para todos static const
los miembros de datos.
Comúnmente, cada unidad de traducción generará un archivo de objeto que contiene las definiciones de los símbolos definidos en esa unidad de traducción. Para usar esos símbolos, debe vincular con esos archivos de objetos.
En gcc , debe especificar todos los archivos de objetos que se vincularán en la línea de comandos, o compilar los archivos de implementación juntos.
g++ -o test objectFile1.o objectFile2.o -lLibraryName
El libraryName
aquí es sólo el nombre dado testimonio de la biblioteca, sin adiciones específicas de la plataforma. Entonces, por ejemplo, en Linux, los archivos de la biblioteca generalmente se llaman libfoo.so
pero solo escribiría -lfoo
. En Windows ese mismo archivo podría ser llamado foo.lib
, pero usaría el mismo argumento. Es posible que deba agregar el directorio donde se pueden encontrar esos archivos -L‹directory›
. Asegúrese de no escribir un espacio después -l
o-L
.
Para XCode : agregue las rutas de búsqueda de encabezado de usuario -> agregue la ruta de búsqueda de biblioteca -> arrastre y suelte la referencia de biblioteca real en la carpeta del proyecto.
Bajo MSVS , los archivos agregados a un proyecto automáticamente tienen sus archivos de objeto vinculados entre sí y lib
se generaría un archivo (en uso común). Para usar los símbolos en un proyecto separado, debe incluir los lib
archivos en la configuración del proyecto. Esto se hace en la sección Linker de las propiedades del proyecto, en Input -> Additional Dependencies
. (la ruta al lib
archivo debe agregarse Linker -> General -> Additional Library Directories
) Cuando se usa una biblioteca de terceros que se proporciona con unlib
archivo, el no hacerlo generalmente genera el error.
También puede suceder que olvide agregar el archivo a la compilación, en cuyo caso no se generará el archivo objeto. En gcc agregarías los archivos a la línea de comando. En MSVS, agregar el archivo al proyecto hará que lo compile automáticamente (aunque los archivos pueden, manualmente, excluirse individualmente de la compilación).
En la programación de Windows, el signo revelador de que no vinculó una biblioteca necesaria es que el nombre del símbolo no resuelto comienza con __imp_
. Busque el nombre de la función en la documentación, y debería decir qué biblioteca necesita usar. Por ejemplo, MSDN coloca la información en un cuadro en la parte inferior de cada función en una sección llamada "Biblioteca".
gcc main.c
lugar de gcc main.c other.c
(lo que los principiantes suelen hacer antes de que sus proyectos sean tan grandes como para construir archivos .o).
Una declaración variable típica es
extern int x;
Como esto es solo una declaración, se necesita una sola definición . Una definición correspondiente sería:
int x;
Por ejemplo, lo siguiente generaría un error:
extern int x;
int main()
{
x = 0;
}
//int x; // uncomment this line for successful definition
Observaciones similares se aplican a las funciones. Declarar una función sin definirla conduce al error:
void foo(); // declaration only
int main()
{
foo();
}
//void foo() {} //uncomment this line for successful definition
Tenga cuidado de que la función que implemente coincida exactamente con la que declaró. Por ejemplo, puede tener calificadores cv no coincidentes:
void foo(int& x);
int main()
{
int x;
foo(x);
}
void foo(const int& x) {} //different function, doesn't provide a definition
//for void foo(int& x)
Otros ejemplos de desajustes incluyen
El mensaje de error del compilador a menudo le dará la declaración completa de la variable o función que se declaró pero nunca se definió. Compárelo estrechamente con la definición que proporcionó. Asegúrese de que cada detalle coincida.
#includes
no agregado al directorio de origen también se incluyen en la categoría de definiciones faltantes.
El orden en que las bibliotecas están vinculadas importa si las bibliotecas dependen unas de otras. En general, si la biblioteca A
depende de la biblioteca B
, libA
DEBE aparecer anteslibB
en las banderas del vinculador.
Por ejemplo:
// B.h
#ifndef B_H
#define B_H
struct B {
B(int);
int x;
};
#endif
// B.cpp
#include "B.h"
B::B(int xx) : x(xx) {}
// A.h
#include "B.h"
struct A {
A(int x);
B b;
};
// A.cpp
#include "A.h"
A::A(int x) : b(x) {}
// main.cpp
#include "A.h"
int main() {
A a(5);
return 0;
};
Crea las bibliotecas:
$ g++ -c A.cpp
$ g++ -c B.cpp
$ ar rvs libA.a A.o
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o
ar: creating libB.a
a - B.o
Compilar:
$ g++ main.cpp -L. -lB -lA
./libA.a(A.o): In function `A::A(int)':
A.cpp:(.text+0x1c): undefined reference to `B::B(int)'
collect2: error: ld returned 1 exit status
$ g++ main.cpp -L. -lA -lB
$ ./a.out
Así que para repetir de nuevo, el orden HACE importa!
¿Qué es una "referencia indefinida / símbolo externo no resuelto"?
Trataré de explicar qué es una "referencia indefinida / símbolo externo no resuelto".
nota: uso g ++ y Linux y todos los ejemplos son para eso
Por ejemplo tenemos un código
// src1.cpp
void print();
static int local_var_name; // 'static' makes variable not visible for other modules
int global_var_name = 123;
int main()
{
print();
return 0;
}
y
// src2.cpp
extern "C" int printf (const char*, ...);
extern int global_var_name;
//extern int local_var_name;
void print ()
{
// printf("%d%d\n", global_var_name, local_var_name);
printf("%d\n", global_var_name);
}
Hacer archivos de objetos
$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o
Después de la fase de ensamblador tenemos un archivo de objeto, que contiene cualquier símbolo para exportar. Mira los símbolos
$ readelf --symbols src1.o
Num: Value Size Type Bind Vis Ndx Name
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 _ZL14local_var_name # [1]
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_var_name # [2]
He rechazado algunas líneas de salida, porque no importan
Entonces, vemos los siguientes símbolos para exportar.
[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable
src2.cpp no exporta nada y no hemos visto sus símbolos
Vincula nuestros archivos de objetos
$ g++ src1.o src2.o -o prog
y ejecutarlo
$ ./prog
123
Linker ve símbolos exportados y lo vincula. Ahora intentamos descomentar líneas en src2.cpp como aquí
// src2.cpp
extern "C" int printf (const char*, ...);
extern int global_var_name;
extern int local_var_name;
void print ()
{
printf("%d%d\n", global_var_name, local_var_name);
}
y reconstruir un archivo objeto
$ g++ -c src2.cpp -o src2.o
OK (sin errores), porque solo construimos archivos de objetos, la vinculación aún no se ha realizado. Intenta vincular
$ g++ src1.o src2.o -o prog
src2.o: In function `print()':
src2.cpp:(.text+0x6): undefined reference to `local_var_name'
collect2: error: ld returned 1 exit status
Ha sucedido porque nuestro local_var_name es estático, es decir, no es visible para otros módulos. Ahora más profundamente. Obtenga la salida de la fase de traducción
$ g++ -S src1.cpp -o src1.s
// src1.s
look src1.s
.file "src1.cpp"
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
.globl global_var_name
.data
.align 4
.type global_var_name, @object
.size global_var_name, 4
global_var_name:
.long 123
.text
.globl main
.type main, @function
main:
; assembler code, not interesting for us
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
Entonces, hemos visto que no hay una etiqueta para local_var_name, es por eso que linker no la ha encontrado. Pero somos piratas informáticos :) y podemos solucionarlo. Abra src1.s en su editor de texto y cambie
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
a
.globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
es decir, deberías tener como abajo
.file "src1.cpp"
.globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
.globl global_var_name
.align 4
.type global_var_name, @object
.size global_var_name, 4
global_var_name:
.long 123
.text
.globl main
.type main, @function
main:
; ...
hemos cambiado la visibilidad de local_var_name y establecemos su valor en 456789. Intenta construir un archivo de objeto a partir de él
$ g++ -c src1.s -o src2.o
ok, ver salida de readelf (símbolos)
$ readelf --symbols src1.o
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 local_var_name
ahora local_var_name tiene Bind GLOBAL (was LOCAL)
enlace
$ g++ src1.o src2.o -o prog
y ejecutarlo
$ ./prog
123456789
ok, lo hackeamos :)
Por lo tanto, como resultado, se produce un "error de símbolo externo de referencia indefinido / no resuelto" cuando el vinculador no puede encontrar símbolos globales en los archivos de objetos.
La función (o variable) void foo()
se definió en un programa en C e intenta usarla en un programa en C ++:
void foo();
int main()
{
foo();
}
El vinculador de C ++ espera que los nombres se alteren, por lo que debe declarar la función como:
extern "C" void foo();
int main()
{
foo();
}
De manera equivalente, en lugar de definirse en un programa en C, la función (o variable) void foo()
se definió en C ++ pero con enlace C:
extern "C" void foo();
e intenta usarlo en un programa C ++ con enlace C ++.
Si se incluye una biblioteca completa en un archivo de encabezado (y se compiló como código C); la inclusión deberá ser la siguiente;
extern "C" {
#include "cheader.h"
}
#ifdef __cplusplus [\n] extern"C" { [\n] #endif
y #ifdef __cplusplus [\n] } [\n] #endif
(al [\n]
ser un retorno de carro real, pero no puedo escribir esto correctamente en el comentario).
extern "C" { #include <myCppHeader.h> }
.
Si todo lo demás falla, vuelva a compilar.
Recientemente pude deshacerme de un error externo no resuelto en Visual Studio 2012 simplemente volviendo a compilar el archivo ofensivo. Cuando reconstruí, el error desapareció.
Esto generalmente ocurre cuando dos (o más) bibliotecas tienen una dependencia cíclica. La biblioteca A intenta usar símbolos en B.lib y la biblioteca B intenta usar símbolos de A.lib. Tampoco existen para empezar. Cuando intentas compilar A, el paso de enlace fallará porque no puede encontrar B.lib. A.lib será generado, pero no dll. Luego compila B, que tendrá éxito y generará B.lib. Re-compilar A ahora funcionará porque ahora se encuentra B.lib.
MSVS requiere que especifique qué símbolos exportar e importar usando __declspec(dllexport)
y__declspec(dllimport)
.
Esta doble funcionalidad generalmente se obtiene mediante el uso de una macro:
#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif
La macro THIS_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 se vinculó (ver también 1) ).
Puedes importar / exportar clases similares:
class DLLIMPEXP X
{
};
visibility
los .def
archivos de GCC y Windows , ya que estos también influyen en el nombre y la presencia del símbolo.
.def
archivos en años. Siéntase libre de agregar una respuesta o editar esta.
Este es uno de los mensajes de error más confusos que todos los programadores de VC ++ han visto una y otra vez. Hagamos las cosas claras primero.
A. ¿Qué es el símbolo? En resumen, un símbolo es un nombre. Puede ser un nombre de variable, un nombre de función, un nombre de clase, un nombre de typedef, o cualquier cosa, excepto los nombres y signos que pertenecen al lenguaje C ++. Es definido por el usuario o introducido por una biblioteca de dependencias (otra definida por el usuario).
B. ¿Qué es externo?
En VC ++, cada archivo fuente (.cpp, .c, etc.) se considera como una unidad de traducción, el compilador compila una unidad a la vez y genera un archivo de objeto (.obj) para la unidad de traducción actual. (Tenga en cuenta que cada archivo de encabezado incluido en este archivo fuente se procesará previamente y se considerará como parte de esta unidad de traducción) Todo lo que se encuentra dentro de una unidad de traducción se considera interno, todo lo demás se considera externo. En C ++, puede hacer referencia a un símbolo externo utilizando palabras clave como extern
, __declspec (dllimport)
etc.
C. ¿Qué es "resolver"? Resolver es un término de tiempo de enlace. En el tiempo de enlace, el enlazador intenta encontrar la definición externa para cada símbolo en los archivos de objetos que no pueden encontrar su definición internamente. El alcance de este proceso de búsqueda incluye:
Este proceso de búsqueda se llama resolver.
D. Finalmente, ¿por qué símbolo externo no resuelto? Si el vinculador no puede encontrar la definición externa de un símbolo que no tiene una definición interna, informa un error de símbolo externo no resuelto.
E. Posibles causas de LNK2019 : error de símbolo externo no resuelto. Ya sabemos que este error se debe a que el enlazador no pudo encontrar la definición de símbolos externos, las posibles causas pueden clasificarse como:
Por ejemplo, si tenemos una función llamada foo definida en a.cpp:
int foo()
{
return 0;
}
En b.cpp queremos llamar a la función foo, así que agregamos
void foo();
para declarar la función foo () y llamarla en otro cuerpo de función, diga bar()
:
void bar()
{
foo();
}
Ahora, cuando cree este código, recibirá un error LNK2019 quejándose de que foo es un símbolo no resuelto. En este caso, sabemos que foo () tiene su definición en a.cpp, pero diferente de la que estamos llamando (valor de retorno diferente). Este es el caso de que exista definición.
Si queremos llamar a algunas funciones en una biblioteca, pero la biblioteca de importación no se agrega a la lista de dependencias adicional (establecida desde:) Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
de la configuración de su proyecto. Ahora el enlazador reportará un LNK2019 ya que la definición no existe en el alcance de búsqueda actual.
Las plantillas no especializadas deben tener sus definiciones visibles para todas las unidades de traducción que las usan. Eso significa que no puede separar la definición de una plantilla a un archivo de implementación. Si debe separar la implementación, la solución habitual es tener un impl
archivo que incluya al final del encabezado que declara la plantilla. Una situación común es:
template<class T>
struct X
{
void foo();
};
int main()
{
X<int> x;
x.foo();
}
//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}
Para solucionar esto, debe mover la definición de X::foo
al archivo de encabezado o algún lugar visible para la unidad de traducción que lo utiliza.
Las plantillas especializadas pueden implementarse en un archivo de implementación y la implementación no tiene que ser visible, pero la especialización debe declararse previamente.
Para más explicaciones y otra posible solución (instanciación explícita) vea esta pregunta y respuesta .
referencia indefinida WinMain@16
o referencia de punto de entrada 'inusual' similarmain()
(especialmente paraestudio visual)
Es posible que no haya elegido el tipo de proyecto correcto con su IDE real. El IDE puede querer vincular, por ejemplo, proyectos de aplicaciones de Windows a dicha función de punto de entrada (como se especifica en la referencia que falta más arriba), en lugar de la int main(int argc, char** argv);
firma comúnmente utilizada .
Si su IDE admite proyectos de consola simple, es posible que desee elegir este tipo de proyecto, en lugar de un proyecto de aplicación de Windows.
Aquí están case1 y case2 manejados con más detalle a partir de un mundo real problema.
WinMain
. Los programas válidos de C ++ necesitan a main
.
El paquete Visual Studio NuGet debe actualizarse para la nueva versión del conjunto de herramientas
Acabo de tener este problema al intentar vincular libpng con Visual Studio 2013. El problema es que el archivo del paquete solo tenía bibliotecas para Visual Studio 2010 y 2012.
La solución correcta es esperar que el desarrollador lance un paquete actualizado y luego actualice, pero funcionó para mí pirateando una configuración adicional para VS2013, apuntando a los archivos de la biblioteca VS2012.
Edité el paquete (en la packages
carpeta dentro del directorio de la solución) buscando packagename\build\native\packagename.targets
y dentro de ese archivo, copiando todas las v110
secciones. Cambié v110
a v120
en los campos de condición solo teniendo mucho cuidado de dejar todas las rutas de archivo como v110
. Esto simplemente permitió que Visual Studio 2013 se vincule a las bibliotecas para 2012, y en este caso, funcionó.
Supongamos que tiene un gran proyecto escrito en c ++ que tiene miles de archivos .cpp y miles de archivos .h. Y digamos que el proyecto también depende de diez bibliotecas estáticas. Digamos que estamos en Windows y construimos nuestro proyecto en Visual Studio 20xx. Cuando presiona Ctrl + F7 Visual Studio para comenzar a compilar la solución completa (supongamos que solo tenemos un proyecto en la solución)
¿Cuál es el significado de la compilación?
El segundo paso de la compilación lo realiza Linker.Linker debería fusionar todo el archivo de objeto y finalmente construir la salida (que puede ser un archivo ejecutable o una biblioteca)
Pasos para vincular un proyecto
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
Observación
Cómo resolver este tipo de error
Error de tiempo del compilador:
Error de tiempo del vinculador
#pragma once
para permitir que el compilador no incluya un encabezado si ya estaba incluido en el .cpp actual que se compilaRecientemente tuve este problema, y resultó que era un error en Visual Studio Express 2013 . Tuve que eliminar un archivo fuente del proyecto y volver a agregarlo para superar el error.
Pasos para probar si cree que podría ser un error en el compilador / IDE:
La mayoría de los enlazadores modernos incluyen una opción detallada que se imprime en diversos grados;
Para gcc y clang; normalmente agregaría -v -Wl,--verbose
o -v -Wl,-v
a la línea de comando. Más detalles se pueden encontrar aquí;
Para MSVC, /VERBOSE
(en particular /VERBOSE:LIB
) se agrega a la línea de comando de enlace.
/VERBOSE
opción del vinculador .El archivo .lib vinculado está asociado a un .dll
Tuve el mismo problema. Digamos que tengo proyectos MyProject y TestProject. Había vinculado efectivamente el archivo lib de MyProject al TestProject. Sin embargo, este archivo lib se produjo cuando se creó la DLL para MyProject. Además, no contenía código fuente para todos los métodos en MyProject, sino solo acceso a los puntos de entrada de la DLL.
Para resolver el problema, construí MyProject como LIB y vinculé TestProject a este archivo .lib (copio y pego el archivo .lib generado en la carpeta TestProject). Entonces puedo construir nuevamente MyProject como una DLL. Se está compilando ya que la biblioteca a la que está vinculado TestProject contiene código para todos los métodos en las clases de MyProject.
Dado que las personas parecen estar dirigidas a esta pregunta cuando se trata de errores de enlazador, voy a agregar esto aquí.
Una posible razón para los errores de enlazador con GCC 5.2.0 es que ahora se elige una nueva biblioteca ABI de libstdc ++ por defecto.
Si obtiene errores de vinculador sobre referencias indefinidas a símbolos que involucran tipos en el espacio de nombres std :: __ cxx11 o la etiqueta [abi: cxx11], entonces probablemente indica que está intentando vincular archivos de objetos compilados con diferentes valores para _GLIBCXX_USE_CXX11_ABI macro. Esto ocurre comúnmente cuando se vincula a una biblioteca de terceros que se compiló con una versión anterior de GCC. Si la biblioteca de terceros no se puede reconstruir con el nuevo ABI, deberá volver a compilar su código con el antiguo ABI.
Entonces, si de repente obtiene errores de enlazador al cambiar a un GCC después de 5.1.0, esto sería algo que debe verificar.
Un contenedor alrededor de GNU ld que no admite scripts de enlazador
Algunos archivos .so son realmente scripts de enlazador GNU ld , por ejemplo, el archivo libtbb.so es un archivo de texto ASCII con este contenido:
INPUT (libtbb.so.2)
Es posible que algunas compilaciones más complejas no admitan esto. Por ejemplo, si incluye -v en las opciones del compilador, puede ver que mainwin gcc wrapper mwdip descarta los archivos de comando de script de enlazador en la lista de salida detallada de las bibliotecas para vincular. Una solución simple es reemplazar el comando de entrada de script de enlazador archivo con una copia del archivo en su lugar (o un enlace simbólico), por ejemplo
cp libtbb.so.2 libtbb.so
O puede reemplazar el argumento -l con la ruta completa de .so, por ejemplo, en lugar de -ltbb
do/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
libfoo
depende libbar
, entonces su enlace se pone correctamente libfoo
antes libbar
.undefined reference to
algo errores.#include
dy son, de hecho se define en las bibliotecas que está enlazado.Los ejemplos están en C. También podrían ser C ++
my_lib.c
#include "my_lib.h"
#include <stdio.h>
void hw(void)
{
puts("Hello World");
}
my_lib.h
#ifndef MY_LIB_H
#define MT_LIB_H
extern void hw(void);
#endif
eg1.c
#include <my_lib.h>
int main()
{
hw();
return 0;
}
Usted construye su biblioteca estática:
$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o
Usted compila su programa:
$ gcc -I. -c -o eg1.o eg1.c
Intenta vincularlo libmy_lib.a
y falla:
$ gcc -o eg1 -L. -lmy_lib eg1.o
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
El mismo resultado si compila y vincula en un solo paso, como:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
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 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 compila y enlaza de una vez:
$ gcc -o eg2 -I. -lz eg2.c
/tmp/ccxCiGn7.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status
Y una variación en el ejemplo 2 que involucra pkg-config
:
$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
En la secuencia de archivos de objetos y bibliotecas que desea vincular para hacer su programa, está colocando las bibliotecas antes de los archivos de objetos que se refieren a ellas. Necesita colocar las bibliotecas después de los archivos de objeto que se refieren a ellas.
Ejemplo de enlace 1 correctamente:
$ gcc -o eg1 eg1.o -L. -lmy_lib
Éxito:
$ ./eg1
Hello World
Ejemplo de enlace 2 correctamente:
$ gcc -o eg2 eg2.o -lz
Éxito:
$ ./eg2
1.2.8
Enlace la pkg-config
variación del ejemplo 2 correctamente:
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib)
$ ./eg2
1.2.8
La lectura es opcional de aquí en adelante .
Por defecto, un comando de enlace generado por GCC, en su distribución, consume los archivos en el enlace de izquierda a derecha en la secuencia de la línea de comandos. Cuando encuentra que un archivo se refiere a algo y no contiene una definición para él, buscará una definición en los archivos más a la derecha. Si finalmente encuentra una definición, la referencia se resuelve. Si alguna referencia permanece sin resolver al final, el enlace falla: el enlazador no busca hacia atrás.
Primero, ejemplo 1 , con biblioteca estáticamy_lib.a
Una biblioteca estática es un archivo indexado de archivos de objetos. Cuando el vinculador encuentra -lmy_lib
en la secuencia de vinculación y descubre que esto se refiere a la biblioteca estática ./libmy_lib.a
, quiere saber si su programa necesita alguno de los archivos de objeto libmy_lib.a
.
Solo hay un archivo de objeto libmy_lib.a
, es decir my_lib.o
, y solo hay una cosa definida my_lib.o
, es decir, la función hw
.
El vinculador decidirá que su programa necesita my_lib.o
si y solo si ya sabe que su programa hace referencia hw
, en uno o más de los archivos de objetos que ya ha agregado al programa, y que ninguno de los archivos de objetos que ya ha agregado contiene un definición para hw
.
Si eso es cierto, entonces el enlazador extraerá una copia de my_lib.o
la biblioteca y la agregará a su programa. Luego, su programa contiene una definición para hw
, por lo que sus referencias a hw
se resuelven .
Cuando intentas vincular el programa como:
$ gcc -o eg1 -L. -lmy_lib eg1.o
el enlazador no se ha agregado eg1.o
al programa cuando ve
-lmy_lib
. Porque en ese punto, no ha visto eg1.o
. Su programa aún no hace ninguna referencia a hw
: todavía no hace ninguna referencia en absoluto , porque todas las referencias que hace están en eg1.o
.
Por lo tanto, el enlazador no se agrega my_lib.o
al programa y no tiene más uso para libmy_lib.a
.
Luego, lo encuentra eg1.o
y lo agrega como programa. Un archivo de objeto en la secuencia de enlace siempre se agrega al programa. Ahora, el programa hace referencia a hw
, y no contiene una definición de hw
; pero no queda nada en la secuencia de enlace que pueda proporcionar la definición que falta. La referencia a hw
termina sin resolver y el enlace falla.
Segundo, ejemplo 2 , con biblioteca compartidalibz
Una biblioteca compartida no es un archivo de archivos de objetos ni nada parecido. Es mucho más parecido a un programa que no tiene una main
función y, en cambio, expone varios otros símbolos que define, para que otros programas puedan usarlos en tiempo de ejecución.
Configurar hoy en día muchas distribuciones de Linux GCC su cadena de herramientas para que sus conductores de idiomas ( gcc
, g++
, gfortran
etc) instruir a los enlazador sistema ( ld
) a las bibliotecas compartidas de enlace de un a demanda base. Tienes una de esas distribuciones.
Esto significa que cuando el enlazador encuentra -lz
en la secuencia de enlace, y descubre que esto se refiere a la biblioteca compartida (por ejemplo) /usr/lib/x86_64-linux-gnu/libz.so
, quiere saber si las referencias que ha agregado a su programa que aún no están definidas tienen definiciones que son exportado porlibz
Si eso es cierto, entonces el enlazador no copiará ningún fragmento libz
ni lo agregará a su programa; en su lugar, solo buscará el código de su programa para que:
En tiempo de ejecución, el cargador de programas del sistema cargará una copia libz
en el mismo proceso que su programa cada vez que cargue una copia de su programa, para ejecutarlo.
En tiempo de ejecución, cada vez que su programa hace referencia a algo definido
libz
, esa referencia utiliza la definición exportada por la copia de libz
en el mismo proceso.
Su programa quiere referirse solo a una cosa que tiene una definición exportada por libz
, a saber, la función zlibVersion
, a la que se hace referencia solo una vez, en eg2.c
. Si el vinculador agrega esa referencia a su programa y luego encuentra la definición exportada por libz
, la referencia se resuelve
Pero cuando intentas vincular el programa como:
gcc -o eg2 -lz eg2.o
el orden de los eventos es incorrecto de la misma manera que en el ejemplo 1. En el momento en que el vinculador encuentra -lz
, no hay referencias a nada en el programa: están todos dentro eg2.o
, lo que aún no se ha visto. Entonces el enlazador decide que no tiene ningún uso libz
. Cuando llega eg2.o
, lo agrega al programa, y luego tiene una referencia indefinida a zlibVersion
, la secuencia de enlace ha finalizado; esa referencia no está resuelta y el enlace falla.
Por último, la pkg-config
variación del ejemplo 2 tiene una explicación ahora obvia. Después de la expansión de la carcasa:
gcc -o eg2 $(pkg-config --libs zlib) eg2.o
se convierte en:
gcc -o eg2 -lz eg2.o
que es solo el ejemplo 2 nuevamente.
El enlace:
gcc -o eg2 -lz eg2.o
funciona bien para ti!
(O: ese enlace funcionó bien para usted en, digamos, Fedora 23, pero falla en Ubuntu 16.04)
Esto se debe a que la distribución en la que funciona el enlace es una de las que no configura su cadena de herramientas GCC para vincular bibliotecas compartidas según sea necesario .
En el pasado, era normal que los sistemas tipo Unix vinculen bibliotecas estáticas y compartidas por diferentes reglas. Las bibliotecas estáticas en una secuencia de enlace se vincularon según fuera necesario según se explica en el ejemplo 1, pero las bibliotecas compartidas se vincularon incondicionalmente.
Este comportamiento es económico en el momento del enlace porque el enlazador no tiene que pensar si el programa necesita una biblioteca compartida: si es una biblioteca compartida, vincúlela. Y la mayoría de las bibliotecas en la mayoría de los enlaces son bibliotecas compartidas. Pero también hay desventajas:
No es económico en tiempo de ejecución , porque puede hacer que las bibliotecas compartidas se carguen junto con un programa, incluso si no las necesita.
Las diferentes reglas de vinculación para bibliotecas estáticas y compartidas pueden ser confusas para los programadores inexpertos, que pueden no saber si -lfoo
en su vinculación se resolverá /some/where/libfoo.a
o no /some/where/libfoo.so
, y de todos modos es posible que no entiendan la diferencia entre bibliotecas compartidas y estáticas.
Esta compensación ha llevado a la situación cismática hoy. Algunas distribuciones han cambiado sus reglas de enlace de CCG para bibliotecas compartidas, de modo que el principio según sea necesario se aplica a todas las bibliotecas. Algunas distribuciones se han quedado con la vieja forma.
Si solo hago:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
seguramente gcc tiene que compilar eg1.c
primero y luego vincular el archivo objeto resultante con libmy_lib.a
. Entonces, ¿cómo puede no saber que se necesita el archivo de objeto cuando está haciendo el enlace?
Porque compilar y vincular con un solo comando no cambia el orden de la secuencia de vinculación.
Cuando ejecuta el comando anterior, gcc
descubre que desea compilación + vinculación. Así que detrás de las escenas, se genera un comando de compilación, y lo ejecuta, a continuación, genera un comando de vinculación, y lo ejecuta, como si se hubiera ejecute los dos comandos:
$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o
Por lo que la vinculación falla igual que lo hace si no ejecuta estos dos comandos. La única diferencia que observa en la falla es que gcc ha generado un archivo de objeto temporal en el caso de compilación + enlace, porque no le está diciendo que lo use eg1.o
. Vemos:
/tmp/ccQk1tvs.o: In function `main'
en vez de:
eg1.o: In function `main':
El orden en que se especifican las bibliotecas vinculadas interdependientes es incorrecto
Poner las bibliotecas interdependientes en el orden incorrecto es solo una forma en la que puede obtener archivos que necesitan definiciones de cosas que vienen más adelante en el enlace que los archivos que proporcionan las definiciones. Poner las bibliotecas antes de los archivos de objetos que se refieren a ellas es otra forma de cometer el mismo error.
Dado el fragmento de código de un tipo de plantilla con un operador amigo (o función);
template <typename T>
class Foo {
friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};
El operator<<
se declara como una función que no es de plantilla. Para cada tipo T
usado con Foo
, debe haber una no plantilla operator<<
. Por ejemplo, si hay un tipo Foo<int>
declarado, debe haber una implementación de operador de la siguiente manera;
std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}
Como no está implementado, el vinculador no puede encontrarlo y genera el error.
Para corregir esto, puede declarar un operador de plantilla antes del Foo
tipo y luego declarar como amigo, la instancia apropiada. La sintaxis es un poco incómoda, pero tiene el siguiente aspecto;
// forward declare the Foo
template <typename>
class Foo;
// forward declare the operator <<
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&);
template <typename T>
class Foo {
friend std::ostream& operator<< <>(std::ostream& os, const Foo<T>& a);
// note the required <> ^^^^
// ...
};
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&)
{
// ... implement the operator
}
El código anterior limita la amistad del operador a la instanciación correspondiente de Foo
, es decir, la operator<< <int>
instanciación se limita al acceso a los miembros privados de la instanciación de Foo<int>
.
Las alternativas incluyen;
Permitir que la amistad se extienda a todas las instancias de las plantillas, de la siguiente manera;
template <typename T>
class Foo {
template <typename T1>
friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
// ...
};
O bien, la implementación para el operator<<
puede hacerse en línea dentro de la definición de clase;
template <typename T>
class Foo {
friend std::ostream& operator<<(std::ostream& os, const Foo& a)
{ /*...*/ }
// ...
};
Tenga en cuenta que cuando la declaración del operador (o función) solo aparece en la clase, el nombre no está disponible para la búsqueda "normal", solo para la búsqueda dependiente del argumento, de cppreference ;
Un nombre declarado por primera vez en una declaración de amigo dentro de la clase o la plantilla de clase X se convierte en un miembro del espacio de nombres más cerrado de X, pero no es accesible para la búsqueda (excepto la búsqueda dependiente de argumentos que considera X) a menos que haya una declaración coincidente en el ámbito del espacio de nombres. previsto...
Hay más información sobre la plantilla de amigos en cppreference y las preguntas frecuentes de C ++ .
Listado de código que muestra las técnicas anteriores .
Como nota al margen del ejemplo de código que falla; g ++ advierte sobre esto de la siguiente manera
warning: friend declaration 'std::ostream& operator<<(...)' declares a non-template function [-Wnon-template-friend]
note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)
Los errores del vinculador pueden ocurrir cuando un archivo de encabezado y su biblioteca compartida asociada (archivo .lib) no se sincronizan. Dejame explicar.
¿Cómo funcionan los enlazadores? El vinculador hace coincidir una declaración de función (declarada en el encabezado) con su definición (en la biblioteca compartida) comparando sus firmas. Puede obtener un error de enlazador si el enlazador no encuentra una definición de función que coincida perfectamente.
¿Es posible obtener un error de vinculador aunque la declaración y la definición parezcan coincidir? ¡Si! Puede que tengan el mismo aspecto en el código fuente, pero realmente depende de lo que vea el compilador. Esencialmente, podrías terminar con una situación como esta:
// header1.h
typedef int Number;
void foo(Number);
// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically
Observe cómo, aunque ambas declaraciones de funciones se ven idénticas en el código fuente, pero son realmente diferentes según el compilador.
¿Podría preguntar cómo termina uno en una situación como esa? Incluye caminos, por supuesto! Si al compilar la biblioteca compartida, la ruta de inclusión conduce header1.h
y termina usando header2.h
en su propio programa, se quedará rascándose el encabezado preguntándose qué sucedió (juego de palabras).
A continuación se explica un ejemplo de cómo puede suceder esto en el mundo real.
Tengo dos proyectos: graphics.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! Obtiene un error de vinculador y no tiene idea de por qué está fallando. La razón es que la biblioteca común usa diferentes versiones de la misma inclusión common_math.h
(lo he hecho obvio aquí en el ejemplo al incluir una ruta diferente, pero puede que no siempre sea tan obvio. Quizás la ruta de inclusión sea diferente en la configuración del compilador) .
Tenga en cuenta que en este ejemplo, el vinculador le dirá que no puede encontrarlo draw()
, cuando en realidad sabe que obviamente la biblioteca lo está exportando. Podrías pasar horas rascándote la cabeza preguntándote qué salió mal. La cuestión es que el vinculador ve una firma diferente porque los tipos de parámetros son ligeramente diferentes. En el ejemplo, vec3
es 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).
DUMPBIN es tu amigo, si estás usando Visual Studio. Estoy seguro de que otros compiladores tienen otras herramientas similares.
El proceso es así:
[1] Por proyecto me refiero a un conjunto de archivos fuente que están vinculados para producir una biblioteca o un ejecutable.
EDITAR 1: reescribió la primera sección para que sea más fácil de entender. Por favor comente abajo para avisarme si algo más necesita ser reparado. ¡Gracias!
UNICODE
Definiciones inconsistentesUna compilación de UNICODE de Windows se construye con TCHAR
etc., que se define como wchar_t
etc. Cuando no se construye con UNICODE
definido como construcción con TCHAR
definido como char
etc. Estos UNICODE
y _UNICODE
define afectan a todos los " T
" tipos de cadena ; LPTSTR
, LPCTSTR
Y sus alces.
Construir una biblioteca con UNICODE
definido e intentar vincularlo en un proyecto donde UNICODE
no está definido dará como resultado errores de vinculador ya que habrá una falta de coincidencia en la definición de TCHAR
; char
vs wchar_t
.
El error generalmente incluye una función un valor con un tipo char
o wchar_t
derivado, estos también podrían incluir, std::basic_string<>
etc. Al navegar a través de la función afectada en el código, a menudo habrá una referencia TCHAR
ao std::basic_string<TCHAR>
etc. Esta es una señal reveladora de que el código originalmente estaba destinado tanto a un UNICODE como a una compilación de caracteres de varios bytes (o "estrecho") .
Para corregir esto, cree todas las bibliotecas y proyectos necesarios con una definición coherente de UNICODE
(y _UNICODE
).
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 UNICODE no está destinado a ser utilizado, asegúrese de que las definiciones no estén establecidas, y / o la configuración de caracteres múltiples se use en los proyectos y se aplique de manera consistente.
No olvide ser coherente entre las versiones "Release" y "Debug" también.
Una "limpieza" de la compilación puede eliminar la "madera muerta" que puede quedar por ahí de compilaciones anteriores, compilaciones fallidas, compilaciones incompletas y otros problemas de compilación relacionados con el sistema de compilación.
En general, el IDE o la compilación incluirán alguna forma de función "limpia", pero esto puede no estar configurado correctamente (por ejemplo, en un archivo MAKE manual) o puede fallar (por ejemplo, los binarios intermedios o resultantes son de solo lectura).
Una vez que se haya completado la "limpieza", verifique que la "limpieza" haya tenido éxito y que todo el archivo intermedio generado (por ejemplo, un archivo MAKE automatizado) se haya eliminado con éxito.
Este proceso puede verse como un recurso final, pero a menudo es un buen primer paso ; especialmente si el código relacionado con el error se ha agregado recientemente (ya sea localmente o desde el repositorio de origen).
const
declaraciones / definiciones variables (solo C ++)Para las personas que vienen de C, puede ser una sorpresa que en C ++ las const
variables globales tengan un enlace interno (o estático). En C este no fue el caso, ya que todas las variables globales están implícitamente extern
(es decir, cuando static
falta la palabra clave).
Ejemplo:
// file1.cpp
const int test = 5; // in C++ same as "static const int test = 5"
int test2 = 5;
// file2.cpp
extern const int test;
extern int test2;
void foo()
{
int x = test; // linker error in C++ , no error in C
int y = test2; // no problem
}
lo correcto sería usar un archivo de encabezado e incluirlo en file2.cpp y file1.cpp
extern const int test;
extern int test2;
Alternativamente, uno podría declarar la const
variable en file1.cpp con explícitoextern
Aunque esta es una pregunta bastante antigua con múltiples respuestas aceptadas, me gustaría compartir cómo resolver un oscuro error de "referencia indefinida a".
Estaba usando un alias para hacer referencia a std::filesystem::path
: el sistema de archivos está en la biblioteca estándar desde C ++ 17 pero mi programa también necesitaba compilar en C ++ 14, así que decidí usar un alias variable:
#if (defined _GLIBCXX_EXPERIMENTAL_FILESYSTEM) //is the included filesystem library experimental? (C++14 and newer: <experimental/filesystem>)
using path_t = std::experimental::filesystem::path;
#elif (defined _GLIBCXX_FILESYSTEM) //not experimental (C++17 and newer: <filesystem>)
using path_t = std::filesystem::path;
#endif
Digamos que tengo tres archivos: main.cpp, file.h, file.cpp:
Tenga en cuenta las diferentes bibliotecas utilizadas en main.cpp y file.h. Como main.cpp # include'd " file.h " después de < filesystem >, la versión del sistema de archivos utilizada era la C ++ 17 . Solía compilar el programa con los siguientes comandos:
$ g++ -g -std=c++17 -c main.cpp
-> compila main.cpp a main.o
$ g++ -g -std=c++17 -c file.cpp
-> compila file.cpp y file.h a file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs
-> vincula main.o y file.o
De esta manera cualquier función contenida en file.o y se utiliza en main.o que requierepath_t
dio errores de "referencia" indefinido debido main.o refiere std::filesystem::path
, pero file.o a std::experimental::filesystem::path
.
Para solucionar esto, solo necesitaba cambiar <experimental :: filesystem> en file.h a <filesystem> .
El comportamiento predeterminado de gcc es que todos los símbolos son visibles. Sin embargo, cuando las unidades de traducción se crean con opción -fvisibility=hidden
, solo las funciones / símbolos marcados con __attribute__ ((visibility ("default")))
son externos en el objeto compartido resultante.
Puede verificar si los símbolos que está buscando son externos invocando:
# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL
los símbolos ocultos / locales se muestran nm
con un tipo de símbolo en minúscula, por ejemplo, en t
lugar de `T para la sección de código:
nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL
También puede usar nm
con la opción -C
de solicitar los nombres (si se utilizó C ++).
Similar a Windows-dlls, uno marcaría las funciones públicas con una definición, por ejemplo DLL_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
Se puede encontrar más información sobre la visibilidad en el wiki de gcc.
Cuando se compila una unidad de traducción con -fvisibility=hidden
los símbolos resultantes, todavía tienen un enlace externo (se muestra con el tipo de símbolo en mayúscula nm
) y se puede usar para un enlace externo sin problema si los archivos de objeto se vuelven parte de una biblioteca estática. El enlace se vuelve local solo cuando los archivos de objeto están vinculados a una biblioteca compartida.
Para encontrar qué símbolos en un archivo de objeto están ocultos, ejecute:
>>> objdump -t XXXX.o | grep hidden
0000000000000000 g F .text 000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g F .text 000000000000000b .hidden HIDDEN_SYMBOL2
Diferentes arquitecturas
Puede ver un mensaje como:
library machine type 'x64' conflicts with target machine type 'X86'
En ese caso, significa que los símbolos disponibles son para una arquitectura diferente a la que está compilando.
En Visual Studio, esto se debe a una "Plataforma" incorrecta, y debe seleccionar la correcta o instalar la versión adecuada de la biblioteca.
En Linux, puede deberse a una carpeta de biblioteca incorrecta (usando en lib
lugar de, lib64
por ejemplo).
En MacOS, existe la opción de enviar ambas arquitecturas en el mismo archivo. Puede ser que el enlace espere que ambas versiones estén allí, pero solo una está. También puede ser un problema con la carpeta lib
/ incorrecta lib64
donde se recoge la biblioteca.