¿Qué son los errores de referencia no definida/símbolo externo sin resolver? ¿Cuáles son las causas comunes y cómo solucionarlas/prevenirlas?
¿Qué es un error de referencia no definida/símbolo externo no resuelto y cómo lo soluciono?
Luciano Grigore
Luciano Grigore
Miembros de la clase:
Un puro virtual
destructor necesita una implementación.
Declarar un destructor puro aún requiere que lo definas (a diferencia de una función normal):
struct X
{
virtual ~X() = 0;
};
struct Y : X
{
~Y() {}
};
int main()
{
Y y;
}
//X::~X(){} //uncomment this line for successful definition
Esto sucede porque se llama a los destructores de clase base 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 no-virtual
métodos sin definición, con el razonamiento adicional de que la declaración pura genera una vtable ficticia y es posible que obtenga el error del enlazador sin usar la función:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
int main()
{
Y y; //linker error although there was no call to X::foo
}
Para que esto funcione, declara X::foo()
tan puro:
struct X
{
virtual void foo() = 0;
};
No-virtual
miembros de la clase
Algunos miembros deben definirse incluso si no se usan explícitamente:
struct A
{
~A();
};
Lo siguiente daría el error:
A a; //destructor undefined
La implementación puede estar en línea, en la propia definición de la clase:
struct A
{
~A() {}
};
o afuera:
A::~A() {}
Si la implementación está fuera de la definición de clase, pero en un encabezado, los métodos deben marcarse como inline
para evitar una definición múltiple.
Todos los métodos de miembros utilizados deben definirse si se utilizan.
Un error común es olvidar calificar el nombre:
struct A
{
void foo();
};
void foo() {}
int main()
{
A a;
a.foo();
}
La definición debe ser
void A::foo() {}
static
los miembros de datos deben definirse fuera de la clase en un unidad de traducción única:
struct X
{
static int x;
};
int main()
{
int x = X::x;
}
//int X::x; //uncomment this line to define X::x
Se puede proporcionar un inicializador para un static
const
miembro de datos de tipo integral o enumeración dentro de la definición de clase; sin embargo, el uso de odr de este miembro aún requerirá una definición de ámbito de espacio de nombres como se describe anteriormente. C++ 11 permite la inicialización dentro de la clase para todos static const
miembros de datos.
-
Solo pensé que tal vez quiera enfatizar que hacer ambas cosas es posible, y el dtor no es en realidad una excepción. (no es obvio a partir de su redacción a primera vista).
– Deduplicador
20 sep 2014 a las 19:26
-
Sería bueno si pudiera cubrir explícitamente el error común de
gcc main.c
en lugar degcc main.c other.c
(lo que los principiantes suelen hacer antes de que sus proyectos se vuelvan tan grandes como para crear archivos .o).–MM
4 de noviembre de 2019 a las 20:43
Luciano Grigore
Declaró pero no definió una variable o función.
Una declaración de variable típica es
extern int x;
Como esto es sólo una declaración, una definición única se necesita Una definición correspondiente sería:
int x;
Por ejemplo, lo siguiente generaría un error:
extern int x;
int main()
{
x = 0;
}
//int x; // uncomment this line for successful definition
Comentarios similares se aplican a las funciones. Declarar una función sin definirla 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, es posible que tenga calificadores de cv que no coincidan:
void foo(int& x);
int main()
{
int x;
foo(x);
}
void foo(const int& x) {} //different function, doesn't provide a definition
//for void foo(int& x)
Otros ejemplos de desajustes incluyen
- Función/variable declarada en un espacio de nombres, definida en otro.
- Función/variable declarada como miembro de clase, definida como global (o viceversa).
- El tipo de devolución de la función, el número y los tipos de parámetros y la convención de llamadas no coinciden exactamente.
El mensaje de error del compilador a menudo le dará la declaración completa de la variable o función que se declaró pero nunca se definió. Compáralo de cerca con la definición que proporcionaste. Asegúrate de que todos los detalles coincidan.
-
En VS, los archivos cpp coinciden con los del encabezado
#includes
no agregado al directorio de origen también se incluye en la categoría de definiciones faltantes.–Laurie Stearn
30 de marzo de 2018 a las 12:18
Comunidad
El orden en 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
luego libA
DEBER aparecer antes libB
en las banderas del enlazador.
Por ejemplo:
// B.h
#ifndef B_H
#define B_H
struct B {
B(int);
int x;
};
#endif
// B.cpp
#include "B.h"
B::B(int xx) : x(xx) {}
// A.h
#include "B.h"
struct A {
A(int x);
B b;
};
// A.cpp
#include "A.h"
A::A(int x) : b(x) {}
// main.cpp
#include "A.h"
int main() {
A a(5);
return 0;
};
Crear las bibliotecas:
$ g++ -c A.cpp
$ g++ -c B.cpp
$ ar rvs libA.a A.o
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o
ar: creating libB.a
a - B.o
Compilar:
$ g++ main.cpp -L. -lB -lA
./libA.a(A.o): In function `A::A(int)':
A.cpp:(.text+0x1c): undefined reference to `B::B(int)'
collect2: error: ld returned 1 exit status
$ g++ main.cpp -L. -lA -lB
$ ./a.out
Así que para repetir de nuevo, el orden LO HACE ¡importar!
-
En VS, los archivos cpp coinciden con los del encabezado
#includes
no agregado al directorio de origen también se incluye en la categoría de definiciones faltantes.–Laurie Stearn
30 de marzo de 2018 a las 12:18
¿Qué es una “referencia indefinida/símbolo externo no resuelto”?
Intentaré 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 los símbolos para exportar. Mira los símbolos
$ readelf --symbols src1.o
Num: Value Size Type Bind Vis Ndx Name
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 _ZL14local_var_name # [1]
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_var_name # [2]
He rechazado algunas líneas de la salida porque no importan
Entonces, vemos 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
Vincular nuestros archivos de objetos
$ g++ src1.o src2.o -o prog
y ejecutarlo
$ ./prog
123
Linker ve los símbolos exportados y los vincula. Ahora tratamos de descomentar líneas en src2.cpp como aquí
// src2.cpp
extern "C" int printf (const char*, ...);
extern int global_var_name;
extern int local_var_name;
void print ()
{
printf("%d%d\n", global_var_name, local_var_name);
}
y reconstruir un archivo de objeto
$ g++ -c src2.cpp -o src2.o
OK (sin errores), porque solo creamos un archivo de objeto, 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 ocurrido porque nuestro local_var_name es estático, es decir, no es visible para otros módulos. Ahora más profundamente. Obtener el resultado de la fase de traducción
$ g++ -S src1.cpp -o src1.s
// src1.s
look src1.s
.file "src1.cpp"
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
.globl global_var_name
.data
.align 4
.type global_var_name, @object
.size global_var_name, 4
global_var_name:
.long 123
.text
.globl main
.type main, @function
main:
; assembler code, not interesting for us
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
Entonces, hemos visto que no hay una etiqueta para local_var_name, es por eso que 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
para
.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 a continuación
.file "src1.cpp"
.globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
.globl global_var_name
.align 4
.type global_var_name, @object
.size global_var_name, 4
global_var_name:
.long 123
.text
.globl main
.type main, @function
main:
; ...
hemos cambiado la visibilidad de local_var_name y establecido su valor en 456789. Intente crear un archivo de objeto a partir de él.
$ g++ -c src1.s -o src2.o
ok, vea la salida readelf (símbolos)
$ readelf --symbols src1.o
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 local_var_name
ahora local_var_name tiene Bind GLOBAL (era LOCAL)
Enlace
$ g++ src1.o src2.o -o prog
y ejecutarlo
$ ./prog
123456789
ok, lo hackeamos 🙂
Entonces, como resultado, ocurre una “referencia indefinida/error de símbolo externo no resuelto” cuando el enlazador no puede encontrar símbolos globales en los archivos de objetos.
@LuchianGrigore ‘siéntase libre de agregar una respuesta’ Preferí agregar el enlace relevante (en mi humilde opinión) su respuesta principal, si desea permitirlo.
– πάντα ῥεῖ
3 de marzo de 2014 a las 22:36
@jave.web: Si bien eso sucede, el programador suele notar que no tiene
this
puntero y sin acceso a los miembros de la clase. Es bastante raro completar la compilación y solo fallar durante la vinculación, cuando a una función miembro no estática le falta su nombre calificado.– Ben Voigt
27 de abril de 2015 a las 22:06
@jave.web: Este fue exactamente mi problema. ¡Gracias! Soy nuevo en cpp, pero por lo que puedo decir, estaba teniendo exactamente el problema que, según Ben Voigt, era bastante raro. Creo que su solución sería una gran respuesta.
– RoG
20 de octubre de 2016 a las 6:42
Pueden ser útiles, al igual que muchas respuestas a preguntas que se marcan como demasiado generales.
–Albert van der Horst
16 de agosto de 2019 a las 19:07
Me gustaría ver un ejemplo reproducible mínimo como algo que le pedimos a la mayoría de los nuevos usuarios, sinceramente. No quiero decir nada con eso, es solo que no podemos esperar que las personas sigan las reglas que no nos imponemos a nosotros mismos.
– Danilo
8 sep 2019 a las 18:54