error - undefined reference to class function c++
¿Qué es una referencia indefinida/error de símbolo externo no resuelto y cómo puedo solucionarlo? (29)
Cuando tus caminos de inclusión son diferentes
Los errores del enlazador pueden ocurrir cuando un archivo de encabezado y su biblioteca compartida asociada (archivo .lib) no están sincronizados. Dejame explicar.
¿Cómo funcionan los enlazadores? El enlazador 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 vinculador si el vinculador 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? ¡Sí! Pueden tener el mismo aspecto en el código fuente, pero realmente depende de lo que ve 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 parecen idénticas en el código fuente, pero son realmente diferentes según el compilador.
Podrías preguntarte cómo terminas en una situación así. ¡Incluya caminos por supuesto! Si al compilar la biblioteca compartida, la ruta de inclusión incluye header1.h
y terminas usando header2.h
tu propio programa, te quedarás rascando el encabezado preguntándote qué sucedió (intento de juego de palabras).
A continuación se explica un ejemplo de cómo puede suceder esto en el mundo real.
Mayor elaboración con un ejemplo.
Tengo dos proyectos: graphics.lib
y main.exe
. Ambos proyectos dependen 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 de vinculador y no tienes 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 la inclusión sea diferente en la configuración del compilador) .
Tenga en cuenta que en este ejemplo, el enlazador le diría que no pudo encontrar draw()
, cuando en realidad sabe que obviamente está siendo exportado por la biblioteca. Podría pasar horas rascándose la cabeza preguntándose qué salió mal. El problema 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).
Depurando el enlazador
DUMPBIN es tu amigo, si estás utilizando Visual Studio. Estoy seguro de que otros compiladores tienen otras herramientas similares.
El proceso es así:
- Tenga en cuenta el nombre extravagante dado en el error del vinculador. (p. ej., draw @ graphics @ XYZ).
- Volcar los símbolos exportados de la biblioteca en un archivo de texto.
- Busque el símbolo de interés exportado y observe que el nombre mutilado es diferente.
- Preste atención a por qué los nombres destrozados terminaron diferentes. Podrías ver que los tipos de parámetros son diferentes, aunque se vean iguales en el código fuente.
- Por lo que 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 de origen que están vinculados entre sí para producir una biblioteca o un ejecutable.
EDITAR 1: reescribe la primera sección para que sea más fácil de entender. Por favor comente a continuación para hacerme saber si es necesario arreglar algo más. ¡Gracias!
¿Qué son los errores de símbolos externos de referencia / resolución no definidos? ¿Cuáles son las causas comunes y cómo solucionarlos / prevenirlos?
Siéntase libre de editar / añadir su propio.
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 regular):
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.
virtual
métodos virtual
deben implementarse o definirse como puros.
Esto es similar a virtual
métodos no virtual
sin definición, con el razonamiento agregado de que la declaración pura genera un dtable vtable 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;
};
Miembros no virtual
clase.
Algunos miembros deben definirse incluso si no se utilizan 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 la clase, pero en un encabezado, los métodos deben marcarse como en inline
para evitar una definición múltiple.
Todos los métodos de miembro 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 debe ser
void A::foo() {}
static
miembros de datos static
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 miembro de datos const
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 de ámbito de espacio de nombres como se describe anteriormente. C ++ 11 permite la inicialización dentro de la clase para todos static const
miembros de datos static const
.
Al enlazar con 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__ ((visibility ("default")))
son externos en el objeto compartido resultante.
Puede verificar si los símbolos que busca 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
para desmarcar los nombres (si se usó C ++).
Similar a Windows-dlls, uno marcaría 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(){
...
}
Lo 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 se compila una unidad de traducción con -fvisibility=hidden
los símbolos resultantes, todavía tiene un enlace externo (se muestra con mayúsculas y minúsculas nm
) y se puede usar para un enlace externo sin problemas si los archivos de objetos forman parte de bibliotecas estáticas. La vinculación se convierte en local solo cuando los archivos de objetos 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
Haciéndose amigo de las plantillas ...
Dado el fragmento de código de un tipo de plantilla con un operador amigo (o función);
template <typename T>
class Foo {
friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};
Se operator<<
está declarando como una función sin plantilla. Para cada tipo T
usado con Foo
, tiene que haber una plantilla sin plantilla operator<<
. Por ejemplo, si hay un tipo Foo<int>
declarado, entonces 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 enlazador 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 instanciación adecuada. La sintaxis es un poco incómoda, pero se ve como sigue;
// 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 creación de instancias correspondiente Foo
, es decir, la operator<< <int>
creación de instancias está limitada para acceder a los miembros privados de la creación de instancias de Foo<int>
.
Las alternativas incluyen;
Permitiendo 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<<
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", solo para la búsqueda dependiente del argumento, desde 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 miembro del espacio de nombres de X más íntimo, pero no es accesible para la búsqueda (excepto la búsqueda dependiente del argumento que considera X) a menos que una declaración coincidente en el ámbito del espacio de nombres sea previsto...
Hay más información sobre la plantilla de amigos en cppreference y en las Preguntas frecuentes de C ++ .
Listado de códigos mostrando las técnicas anteriores .
Como una nota al lado 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)
Limpiar y reconstruir
Una "limpieza" de la compilación puede eliminar la "madera muerta" que se puede dejar alrededor 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 puede que no esté configurada correctamente (por ejemplo, en un archivo de configuración 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 se haya eliminado con éxito todo el archivo intermedio generado (por ejemplo, un makefile automatizado).
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).
Su enlace consume bibliotecas antes que los archivos de objetos que se refieren a ellos
- Está intentando compilar y vincular su programa con la cadena de herramientas de GCC.
- Su enlace especifica todas las bibliotecas necesarias y las rutas de búsqueda de bibliotecas
- Si
libfoo
depende delibbar
, entonces su enlace se pone correctamentelibfoo
anteslibbar
. - Tu vinculación falla con
undefined reference to
algo errores. - Pero todos los algo indefinidos se declaran en los archivos de encabezado que tiene
#include
y, de hecho, se definen en las bibliotecas que está enlazando.
Los ejemplos están en C. También podrían ser C ++
Un ejemplo mínimo que involucra una biblioteca estática que usted mismo construyó.
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;
}
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
Intentas enlazarlo 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 compilas y vinculas 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 enlazas 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 en el ejemplo 2 que involucra pkg-config
:
$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o
eg2.o: In function `main'':
eg2.c:(.text+0x5): undefined reference to `zlibVersion''
¿Que estas haciendo mal?
En la secuencia de archivos de objetos y bibliotecas que desea vincular para hacer su programa, está colocando las bibliotecas antes que los archivos de objetos que se refieren a ellos. Debe colocar las bibliotecas después de los archivos de objetos que se refieren a ellos.
Enlace el ejemplo 1 correctamente:
$ gcc -o eg1 eg1.o -L. -lmy_lib
Éxito:
$ ./eg1
Hello World
Enlace el ejemplo 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 explicación
La lectura es opcional de aquí en adelante .
De manera predeterminada, un comando de vinculación generado por GCC, en su distribución, consume los archivos en la vinculación de izquierda a derecha en la secuencia de la línea de órdenes. Cuando encuentra que un archivo se refiere a algo y no contiene una definición para él, buscará una definición en los archivos que se encuentran 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 librería estática.my_lib.a
Una biblioteca estática es un archivo indexado de archivos de objetos. Cuando el enlazador encuentra -lmy_lib
en la secuencia de enlaces y se da cuenta de que esto se refiere a la biblioteca estática ./libmy_lib.a
, quiere saber si su programa necesita alguno de los archivos objeto libmy_lib.a
.
Solo hay un archivo de objeto libmy_lib.a
, es decir my_lib.o
, y solo hay una cosa definida en my_lib.o
, la función hw
.
El enlazador decidirá que su programa necesita my_lib.o
si y solo si ya sabe que su programa se refiere a hw
, en uno o más de los archivos de objeto que ya se ha agregado al programa, y que ninguno de los archivos de objeto que ya ha agregado contiene una definición de 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 enlazar 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 a ese punto, no lo 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 son 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
.
A continuación, encuentra eg1.o
, y lo agrega a ser programa. Un archivo de objeto en la secuencia de vinculación siempre se agrega al programa. Ahora, el programa hace una 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 resolverse , y el enlace falla.
Segundo, ejemplo 2 , con biblioteca compartida.libz
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.
Muchas distribuciones de Linux hoy configuran su cadena de herramientas GCC para que sus conductores de idiomas ( gcc
, g++
, gfortran
etc.) indican al sistema enlazador ( ld
) para vincular bibliotecas compartidas en una de las necesidades base. Tienes una de esas distros.
Esto significa que cuando el vinculador se encuentra -lz
en la secuencia de vinculación y se da cuenta de que se refiere a la biblioteca compartida (por ejemplo) /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 exportado porlibz
Si eso es cierto, entonces el enlazador no copiará ningún fragmento libz
y los agregará a su programa; en lugar de eso, solo controlará el código de su programa para que:
En el 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, siempre que su programa se refiera a algo que se define en
libz
, esa referencia utiliza la definición exportada por la copialibz
en el mismo proceso.
Su programa quiere referirse a una sola cosa que tiene una definición exportada libz
, a saber, la función zlibVersion
, a la que se hace referencia solo una vez, en eg2.c
. Si el enlazador agrega esa referencia a su programa y luego encuentra la definición exportada por libz
, la referencia se resuelve
Pero cuando intentas enlazar 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 enlazador encuentra -lz
, no hay referencias a nada en el programa: están todos eg2.o
, lo que aún no se ha visto. Así que el enlazador decide que no sirve para nada libz
. Cuando alcanza eg2.o
, lo agrega al programa, y luego tiene una referencia indefinida a zlibVersion
, la secuencia de vinculación se termina; 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 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 otra vez.
Puedo reproducir el problema en el ejemplo 1, pero no en el ejemplo 2
La vinculación:
gcc -o eg2 -lz eg2.o
Funciona bien para ti!
(O: ese vínculo funcionó bien para usted en, por ejemplo, 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 similares a Unix vinculen bibliotecas estáticas y compartidas mediante diferentes reglas. Las bibliotecas estáticas en una secuencia de vinculación se vincularon según las necesidades según lo explicado en el ejemplo 1, pero las bibliotecas compartidas se vincularon sin condiciones.
Este comportamiento es económico en el momento del enlace porque el enlazador no tiene que considerar si el programa necesita una biblioteca compartida: si se trata de 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 , ya que puede hacer que las bibliotecas compartidas se carguen junto con un programa, incluso si no las necesita.
Las diferentes reglas de enlace para bibliotecas estáticas y compartidas pueden ser confusas para los programadores inexpertos, quienes pueden no saber si
-lfoo
en su enlace se resolverán/some/where/libfoo.a
o no/some/where/libfoo.so
, y podrían no entender la diferencia entre las bibliotecas compartidas y las estáticas.
Esta compensación ha llevado a la situación cismática de hoy. Algunas distribuciones han cambiado sus reglas de enlace GCC para bibliotecas compartidas, de modo que el principio según sea necesario se aplique a todas las bibliotecas. Algunas distros se han pegado con el viejo camino.
¿Por qué sigo teniendo este problema incluso si compilo y vinculo al mismo tiempo?
Si acabo de hacer:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
seguramente gcc tiene que compilar eg1.c
primero, y luego vincular el archivo de objeto resultante con libmy_lib.a
. Entonces, ¿cómo puede no saber que se necesita el archivo objeto cuando se está enlazando?
Porque compilar y enlazar con un solo comando no cambia el orden de la secuencia de vinculación.
Cuando ejecuta el comando anterior, se da gcc
cuenta de que desea compilación + enlace. 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 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 solo una de las formas en que puede obtener archivos que necesitan definiciones de elementos que se incorporan más adelante en el enlace que los archivos que proporcionan las definiciones. Poner las bibliotecas antes de los archivos objeto que se refieren a ellas es otra forma de cometer el mismo error.
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 de origen del proyecto y volver a agregarlo para superar el error.
Pasos a seguir si crees que podría ser un error en el compilador / IDE:
- Limpie el proyecto (algunos IDE tienen una opción para hacer esto, también puede hacerlo manualmente eliminando los archivos de objetos)
- Intente iniciar un nuevo proyecto, copiando todo el código fuente del original.
Usa el enlazador para ayudar a diagnosticar el error.
Los enlazadores más modernos incluyen una opción verbosa que se imprime en diversos grados;
- Invocación de enlace (línea de comando),
- Datos sobre qué bibliotecas están incluidas en la etapa de enlace,
- La ubicación de las bibliotecas,
- Buscar rutas utilizadas.
Para gcc y clang; normalmente se agregaría -v -Wl,--verbose
o -v -Wl,-v
a la línea de comando. Más detalles se pueden encontrar aquí;
- Página de manual de Linux ld .
- Página del enlazador LLVM .
- "Una introducción a GCC" capítulo 9 .
Para MSVC, /VERBOSE
(en particular /VERBOSE:LIB
) se agrega a la línea de comando del enlace.
- La página de MSDN en la
/VERBOSE
opción del enlazador .
UNICODE
Definiciones inconsistentes
Una compilación de UNICODE de Windows se construye con TCHAR
etc., que se define como wchar_t
etc. Cuando no se compila con, se UNICODE
define como compilación con TCHAR
definido como char
etc. Estas UNICODE
y _UNICODE
definiciones afectan a todos los " T
" tipos de cadena ; LPTSTR
, LPCTSTR
y sus alces.
La construcción de una biblioteca con UNICODE
definición y el intento de vincularla en un proyecto en el UNICODE
que no está definida resultará en errores de vinculador, ya que habrá una discrepancia en la definición de TCHAR
; char
vs wchar_t
.
El error generalmente incluye una función, un valor con un tipo derivado char
o wchar_t
, esto también podría incluir, std::basic_string<>
etc. Cuando se navega a través de 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 originalmente diseñado para un UNICODE y un carácter de varios bytes (o "estrecho") .
Para corregir esto, genere 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 no se hayan definido las definiciones y / o se use la configuración de múltiples caracteres en los proyectos y se aplique de manera consistente.
No se olvide de ser coherente entre las compilaciones "Release" y "Debug" también.
Falta "extern" en const
las declaraciones / definiciones de variables (solo C ++)
Para las personas que provienen de C, puede ser una sorpresa que en C ++ las const
variables globales tengan vínculos internos (o estáticos). 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
}
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
Declarado pero no define una variable o función.
Una declaración de 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 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 coincida exactamente con la que declaró. Por ejemplo, puede tener calificadores cv no coincidentes:
void foo(int& x);
int main()
{
int x;
foo(x);
}
void foo(const int& x) {} //different function, doesn''t provide a definition
//for void foo(int& x)
Otros ejemplos de desajustes incluyen
- Función / variable declarada en un espacio de nombres, definida en otro.
- Función / variable declarada como miembro de la clase, definida como global (o viceversa).
- El tipo de retorno de función, el número y los tipos de parámetros 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 de cerca con la definición que proporcionó. Asegúrese de que cada detalle coincida.
El orden en el que se especifican las bibliotecas vinculadas interdependientes es incorrecto.
El orden en 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 de 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;
};
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 SI importa!
Error al enlazar con bibliotecas / archivos de objetos apropiados o compilar archivos de implementación
Comúnmente, cada unidad de traducción generará un archivo de objeto que contiene las definiciones de los símbolos definidos en esa unidad de traducción. Para usar esos símbolos, debes vincularlos con esos archivos de objetos.
En gcc , especificaría todos los archivos de objetos que se vincularán entre sí en la línea de comandos o compilará los archivos de implementación.
g++ -o test objectFile1.o objectFile2.o -lLibraryName
El nombre de biblioteca aquí es solo el nombre simple de la biblioteca, sin adiciones específicas de la plataforma. Entonces, por ejemplo, en los archivos de la biblioteca de Linux se suele llamar libfoo.so
pero solo escribirías -lfoo
. En Windows, ese mismo archivo podría llamarse foo.lib
, pero foo.lib
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 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 real de la biblioteca en la carpeta del proyecto.
Bajo MSVS , los archivos agregados a un proyecto automáticamente tienen sus archivos de objeto vinculados entre sí y se generaría un archivo lib
(de uso común). Para usar los símbolos en un proyecto separado, deberá incluir los archivos lib
en la configuración del proyecto. Esto se hace en la sección Vinculador de las propiedades del proyecto, en Input -> Additional Dependencies
. (la ruta al archivo lib
debe agregar en el Linker -> General -> Additional Library Directories
) Cuando se utiliza una biblioteca de terceros que se proporciona con un archivo lib
, el no hacerlo generalmente produce el error.
También puede ocurrir que olvide agregar el archivo a la compilación, en cuyo caso no se generará el archivo objeto. En gcc usted agregaría los archivos a la línea de comando. En MSVS, agregar el archivo al proyecto lo hará compilar automáticamente (aunque los archivos pueden, manualmente, excluirse individualmente de la compilación).
En la programación de Windows, el signo que indica que no vinculó una biblioteca necesaria es que el nombre del símbolo sin resolver comienza con __imp_
. Busque el nombre de la función en la documentación y debe indicar 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".
Implementaciones de plantillas no visibles.
Las plantillas no especializadas deben tener sus definiciones visibles para todas las unidades de traducción que las utilizan. Eso significa que no puede separar la definición de una plantilla en 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 usa.
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 ser declarada previamente.
Para una explicación más detallada y otra posible solución (ejemplificación explícita) vea esta pregunta y respuesta .
Importación / exportación incorrecta de métodos / clases a través de módulos / dll (específico del compilador).
MSVS requiere que especifique qué símbolos exportar e importar usando __declspec(dllexport)
y __declspec(dllimport)
.
Esta funcionalidad dual se obtiene generalmente 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 ).
Puede igualmente importar / exportar clases:
class DLLIMPEXP X
{
};
Los símbolos se definieron en un programa en C y se usaron en el código C ++.
La función (o variable) void foo()
se definió en un programa en C e intenta usarla en un programa en C ++:
void foo();
int main()
{
foo();
}
El enlazador de C ++ espera que los nombres estén alterados, por lo que debe declarar la función como:
extern "C" void foo();
int main()
{
foo();
}
De manera equivalente, en lugar de estar definido en un programa de C, la función (o variable) void foo()
se definió en C ++ pero con enlace C:
extern "C" void foo();
e intentas usarlo en un programa de C ++ con un enlace de C ++.
Si se incluye una biblioteca completa en un archivo de encabezado (y se compiló como código C); la inclusión deberá ser como sigue;
extern "C" {
#include "cheader.h"
}
Además, si está utilizando bibliotecas de terceros, asegúrese de tener los archivos binarios de 32/64 bits correctos
Este es uno de los mensajes de error más confusos que todos los programadores de VC ++ han visto una y otra vez. Vamos a hacer las cosas primero la claridad.
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 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 dependencia (otra definida por el usuario).
B. ¿Qué es externo? En VC ++, cada archivo de origen (.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 que se incluye en este archivo de origen 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 usando palabras clave como extern
, __declspec (dllimport)
y así sucesivamente.
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 objeto que no pueden encontrar su definición internamente. El alcance de este proceso de búsqueda incluye:
- Todos los archivos de objetos que se generaron en tiempo de compilación.
- Todas las bibliotecas (.lib) que se especifican explícita o implícitamente como dependencias adicionales de esta aplicación de construcción.
Este proceso de búsqueda se llama resolver.
D. Finalmente, ¿por qué símbolo externo no resuelto? Si el enlazador no puede encontrar la definición externa para 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 vinculador no pudo encontrar la definición de los símbolos externos, las posibles causas se pueden clasificar de la siguiente manera:
- Definición existe
Por ejemplo, si tenemos una función llamada foo definida en a.cpp:
int foo()
{
return 0;
}
En b.cpp queremos llamar a la función foo, así que agregamos
void foo();
para declarar la función foo () y llamarla en otro cuerpo de función, diga bar()
:
void bar()
{
foo();
}
Ahora, cuando construya este código, recibirá un error LNK2019 quejándose de que foo es un símbolo sin resolver. 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 que existe 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 en: Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
) de la configuración de su proyecto. Ahora el enlazador informará un LNK2019 ya que la definición no existe en el alcance de búsqueda actual.
La compilación de un programa en C ++ se realiza en varios pasos, según lo especificado en 2.2 (créditos a Keith Thompson para la referencia) :
La precedencia entre las reglas de sintaxis de traducción se especifica en 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 los indicadores de fin de línea) si es necesario. [RECORTE]
- Cada instancia de un carácter de barra invertida (/) seguido inmediatamente por un carácter de nueva línea se elimina, uniendo las líneas de origen físicas para formar líneas de origen lógicas. [RECORTE]
- El archivo de origen se descompone en tokens de preprocesamiento (2.5) y secuencias de caracteres de espacios en blanco (incluidos los comentarios). [RECORTE]
- Las directivas de preprocesamiento se ejecutan, las invocaciones de macros se expanden y se ejecutan las expresiones de operador _Pragma unario. [RECORTE]
- Cada miembro del conjunto de caracteres de origen en un literal de carácter o un literal de cadena, así como cada secuencia de escape y nombre de carácter universal en un literal de carácter o un literal de cadena sin formato, se convierte en el miembro correspondiente del conjunto de caracteres de ejecución; [RECORTE]
- Se concatenan tokens literales de cadena adyacente.
- Los caracteres de espacio en blanco que separan tokens ya no son significativos. Cada token de preprocesamiento se convierte en un token. (2.7). Los tokens resultantes se analizan sintácticamente y semánticamente y se traducen como una unidad de traducción. [RECORTE]
- Las unidades de traducción y las unidades de traducción traducidas 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. Toda la salida del traductor se recopila en una imagen de 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 estas fases separadas ocurrieran, aunque en la práctica las diferentes fases se pueden plegar juntas.
Los errores especificados ocurren durante esta última etapa de la 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 trabajen juntos.
Digamos que a.cpp
símbolo a
en a.cpp
. Ahora, b.cpp
declaró ese símbolo y lo usó. Antes de vincular, simplemente asume 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 a b.cpp
(bueno, en realidad al objeto o biblioteca que lo usa).
Si está utilizando Microsoft Visual Studio, verá 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 contra las bibliotecas con las que se vincula, y los símbolos exportados se proporcionan para las bibliotecas que usan ese .lib
(si existe).
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 la 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:
- Error al enlazar con bibliotecas / archivos de objetos apropiados o compilar archivos de implementación
- Variable declarada e indefinida o función.
- Problemas comunes con los miembros de tipo de clase
- Implementaciones de plantillas no visibles.
- Los símbolos se definieron en un programa en C y se usaron en el código C ++.
- Importación / exportación incorrecta de métodos / clases a través de módulos / dll. (MSVS específico)
- Dependencia de biblioteca circular
- referencia indefinida a `WinMain @ 16 ''
- Orden de biblioteca interdependiente
- Múltiples archivos fuente del mismo nombre.
- Error al escribir o no incluir la extensión .lib cuando se utiliza el
#pragma
(Microsoft Visual Studio) - Problemas con la plantilla de amigos.
- Definiciones inconsistentes de
UNICODE
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 de la biblioteca, este debe ser el nombre completo de la biblioteca.
Supongamos que tienes 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 solo tenemos un proyecto en la solución)
¿Cuál es el significado de la compilación?
- Visual Studio busca en el archivo .vcxproj y comienza a compilar cada archivo que tiene la extensión .cpp. El orden de compilación no está definido. Por lo tanto, no debe asumir que el archivo main.cpp se compila primero.
- Si los archivos .cpp dependen de archivos .h adicionales para encontrar los símbolos que pueden o no estar definidos en el archivo .cpp
- Si existe un archivo .cpp en el que el compilador no pudo encontrar un símbolo, un error de tiempo del compilador genera el mensaje El símbolo x no se pudo encontrar
- Para cada archivo con extensión .cpp se genera un archivo de objeto .o y, además, Visual Studio escribe la salida en un archivo denominado ProjectName.Cpp.Clean.txt que contiene todos los archivos de objetos que debe procesar el vinculador.
El segundo paso de la compilación lo realiza Linker. Linker debe combinar todo el archivo objeto y, finalmente, compilar 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 las respuestas anteriores, o evento, la inicialización de una variable estática que es miembro dentro de una clase)
- Si no se puede encontrar un símbolo en los archivos de objetos, también se busca en bibliotecas adicionales. Para agregar una nueva biblioteca a un proyecto Propiedades de configuración -> Directorios de VC ++ -> Directorios de bibliotecas 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 enlazador no pudo encontrar el símbolo que escribes en un .cpp, genera un error de tiempo del vinculador que puede sonar como
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
Observación
- Una vez que el enlazador encuentra un símbolo, no lo busca en otras bibliotecas.
- El orden de las bibliotecas de enlace 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 los fallos en tiempo de ejecución pueden ocurrir
Cómo resolver este tipo de error
Error de tiempo del compilador:
- Asegúrese de escribir su proyecto sintáctico c ++ correcto.
Error de tiempo del enlazador
- Defina todos los símbolos que declara en sus archivos de encabezado
- Se usa
#pragma once
para permitir que el compilador no incluya 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 definió en sus archivos de encabezado
- Cuando usa la plantilla para asegurarse de incluir la definición de cada función de plantilla en el archivo de encabezado para permitir que el compilador genere el código apropiado para cualquier instancia.
¿Qué es una "referencia externa / símbolo externo no resuelto"
Trataré de explicar qué es una "referencia externa / símbolo externo no resuelto".
nota: uso g ++ y Linux y todos los ejemplos son para eso
Por ejemplo tenemos algo de 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 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 de objeto, que contiene los símbolos para exportar. Mira los simbolos
$ 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
Por lo tanto, 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 los símbolos exportados y los enlaza. Ahora intentamos descomentar las 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), ya que solo compilamos un archivo de objeto, la vinculación aún no se ha realizado. Tratar de 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
Ocurrió porque nuestro nombre_var local es estático, es decir, no es 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 una 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 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 hemos establecido su valor en 456789. Intente construir un archivo de objeto a partir de él
$ g++ -c src1.s -o src2.o
ok, ver la salida de lectura (símbolos)
$ readelf --symbols src1.o
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 local_var_name
ahora local_var_name ha enlazado GLOBAL (era LOCAL)
enlazar
$ g++ src1.o src2.o -o prog
y ejecutarlo
$ ./prog
123456789
ok, lo hackeamos :)
Entonces, como resultado, se produce un "error de referencia / símbolo externo no resuelto indefinido" cuando el vinculador no puede encontrar símbolos globales en los archivos de objeto.
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 para MyProject al TestProject. Sin embargo, este archivo lib se produjo cuando se construyó la DLL para MyProject. Además, no contenía el código fuente de todos los métodos en MyProject, pero solo accedía a los puntos de entrada de la DLL.
Para resolver el problema, construí MyProject como un LIB y vinculé TestProject a este archivo .lib (copio el archivo .lib generado en la carpeta TestProject). Entonces puedo construir de nuevo MyProject como un DLL. Se está compilando ya que la biblioteca a la que está vinculado TestProject contiene código para todos los métodos en clases en MyProject.
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í al piratear una configuración adicional para VS2013, apuntando a los archivos de la biblioteca VS2012.
Edité el paquete (en la carpeta de packages
dentro del directorio de la solución) encontrando packagename/build/native/packagename.targets
y dentro de ese archivo, copiando todas las secciones v110
. Cambié la v110
a v120
en los campos de condición, solo teniendo mucho cuidado de dejar las rutas de nombre de archivo como v110
. Esto simplemente permitió que Visual Studio 2013 se vinculara a las bibliotecas para 2012, y en este caso, funcionó.
Si todo lo demás falla, recompila.
Hace poco pude deshacerme de un error externo no resuelto en Visual Studio 2012 solo por volver a compilar el archivo ofensivo. Cuando volví a construir, el error desapareció.
Esto suele ocurrir 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 intenta compilar A, el paso del enlace fallará porque no puede encontrar B.lib. Se generará A.lib, 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.
Un envoltorio alrededor de GNU ld que no admite scripts de vinculador
Algunos archivos .so son en realidad scripts del enlazador ld de GNU , por ejemplo, el archivo libtbb.so es un archivo de texto ASCII con este contenido:
INPUT (libtbb.so.2)
Algunas construcciones más complejas pueden no ser compatibles con esto. Por ejemplo, si incluye -v en las opciones del compilador, puede ver que mainwin gcc wrapper mwdip descarta los archivos de comandos del script del vinculador en la lista de salida detallada de las bibliotecas a las que se debe vincular. Una solución alternativa 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 de .so, por ejemplo, en lugar de -ltbb
hacerlo/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
referencia indefinida a WinMain@16
o similar (inusual) main()
referencia de punto de entrada (especialmente para visual-studio ).
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 arriba), en lugar de la int main(int argc, char** argv);
comúnmente utilizada int main(int argc, char** argv);
firma.
Si su IDE es compatible con proyectos de consola simple, es posible que desee elegir este tipo de proyecto, en lugar de un proyecto de aplicación de Windows.
Aquí se muestran case2 y case2 con más detalle a partir de un problema del mundo real .
Dado que las personas parecen estar dirigidas a esta pregunta cuando se trata de errores de vinculador, voy a agregar esto aquí.
Una posible razón para 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 de 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 enlazar archivos de objetos que se compilaron con diferentes valores para _GLIBCXX_USE_CXX11_ABI macro. Esto suele suceder 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, entonces deberá recompilar su código con la antigua ABI.
Por lo tanto, si repentinamente se producen errores en el enlazador al cambiar a un GCC después de la versión 5.1.0, esto sería una cosa para verificar.
Aunque esta es una pregunta bastante antigua con múltiples respuestas aceptadas, me gustaría compartir cómo resolver un error oscuro "referencia indefinida a".
Diferentes versiones de bibliotecas.
Estaba usando un alias para referirme std::filesystem::path
: el sistema de archivos está en la biblioteca estándar desde C ++ 17 pero mi programa también necesitaba compilar en C ++ 14, así que decidí usar un alias variable:
#if (defined _GLIBCXX_EXPERIMENTAL_FILESYSTEM) //is the included filesystem library experimental? (C++14 and newer: <experimental/filesystem>)
using path_t = std::experimental::filesystem::path;
#elif (defined _GLIBCXX_FILESYSTEM) //not experimental (C++17 and newer: <filesystem>)
using path_t = std::filesystem::path;
#endif
Digamos que tengo tres archivos: main.cpp, file.h, file.cpp:
- file.h # include''s < experimental :: filestystem > y contiene el código anterior
- file.cpp , la implementación de file.h, # include " file.h "
- main.cpp # include''s < filestystem > y " file.h "
Tenga en cuenta las diferentes bibliotecas utilizadas en main.cpp y file.h. Dado que main.cpp # include''d " file.h " después de < filestystem >, la versión del sistema de archivos utilizado 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 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 "referencia indefinida" porque main.o refiere std::filesystem::path
, pero file.o a std::experimental::filesystem::path
.
Resolución
Para arreglar esto, solo necesitaba cambiar <experimental :: filestystem> en file.h a <filestystem> .