Este team de expertos despúes de ciertos días de trabajo y de juntar de información, obtuvimos los datos necesarios, esperamos que te sea útil para tu trabajo.
Solución:
La compilación de un programa en C ++ se lleva a cabo en varios pasos, según lo especificado por 2.2 (créditos a Keith Thompson como referencia):
La precedencia entre las reglas sintácticas de traducción se especifica en las siguientes fases [see footnote].
- 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. [SNIP]
- Cada instancia de un carácter de barra invertida () seguida inmediatamente por un carácter de nueva línea se elimina, empalmando líneas de origen físico para formar líneas de origen lógicas. [SNIP]
- El archivo de origen se descompone en tokens de preprocesamiento (2.5) y secuencias de caracteres de espacio en blanco (incluidos los comentarios). [SNIP]
- Se ejecutan las directivas de preprocesamiento, macro las invocaciones se expanden y las expresiones del operador unario _Pragma se ejecutan. [SNIP]
- Cada miembro del juego de caracteres de origen en un carácter literal o un string literal, así como cada secuencia de escape y nombre de carácter universal en un carácter literal o no sin formato. string literal, se convierte en el miembro correspondiente del juego de caracteres de ejecución; [SNIP]
- Adyacente string los tokens literales están concatenados.
- Los caracteres de espacios 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. [SNIP]
- Las unidades de traducción traducidas y las unidades de instanciación se combinan de la siguiente manera: [SNIP]
- Se resuelven todas las referencias a entidades externas. Los componentes de la biblioteca están vinculados para satisfacer referencias externas a entidades no definidas en la traducción actual. Toda la salida del traductor se recopila en una imagen de programa que contiene la información necesaria para la ejecución en su entorno de ejecución. (énfasis mío)
[footnote] Las implementaciones deben comportarse como si estas fases separadas ocurrieran, aunque en la práctica diferentes fases pueden combinarse.
Los errores especificados ocurren durante esta última etapa de compilación, más comúnmente conocida como vinculación. Básicamente significa que ha compilado un montón de archivos de implementación en archivos de objetos o bibliotecas y ahora quiere que funcionen juntos.
Di que definiste el símbolo a
en a.cpp
. Ahora, b.cpp
declarado ese símbolo y lo usé. Antes de vincular, simplemente asume que ese símbolo fue definido algun lado, pero aún no le importa dónde. La fase de vinculación se encarga 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 .lib
archivos. Estos contienen una tabla de símbolos exportados y una tabla de símbolos importados. Los símbolos importados se resuelven con las bibliotecas con las que se vincula, y los símbolos exportados se proporcionan para las bibliotecas que usan esos .lib
(Si alguna).
Existen mecanismos similares para otros compiladores / plataformas.
Los mensajes de error comunes son error LNK2001
, error LNK1120
, error LNK2019
por Microsoft Visual Studio y undefined reference to
symbolName por 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)" ([email protected]@YAXXZ)
1>test2.obj : error LNK2001: unresolved external symbol "int x" ([email protected]@3HA)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall A::~A(void)" ([email protected]@[email protected])
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall X::foo(void)" ([email protected]@@UAEXXZ)
1>...test2.exe : fatal error LNK1120: 4 unresolved externals
Las causas comunes incluyen:
- No se puede vincular con bibliotecas / archivos de objeto apropiados o compilar archivos de implementación
- Variable o función declarada e indefinida.
- Problemas comunes con miembros de tipo de clase
- Implementaciones de plantillas no visibles.
- Los símbolos se definieron en un programa en C y se utilizaron en código C ++.
- Importar / exportar métodos / clases incorrectamente a través de módulos / dll. (Específico de MSVS)
- Dependencia de biblioteca circular
- referencia indefinida a ‘[email protected]’
- Orden de biblioteca interdependiente
- Varios archivos de origen con el mismo nombre
- Escribir incorrectamente o no incluir la extensión .lib al usar el
#pragma
(Microsoft Visual Studio) - Problemas con los amigos de la plantilla
- Inconsistente
UNICODE
definiciones - Falta “extern” en las declaraciones / definiciones de variables constantes (solo C ++)
Miembros de la clase:
Un puro virtual
destructor necesita una implementación.
Declarar un destructor puro aún requiere que lo defina (a diferencia de una función 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 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 avirtual
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 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()
tan puro:
struct X
virtual void foo() = 0;
;
No-virtual
miembros de la 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 estar en línea, en la propia definición de clase:
struct A
~A()
;
o afuera:
A::~A()
Si la implementación está fuera de la definición de la clase, pero en un encabezado, los métodos deben marcarse como inline
para evitar una definición múltiple.
Todos los métodos de miembros usados deben definirse si se usan.
Un error común es olvidar calificar el nombre:
struct A
void foo();
;
void foo()
int main()
A a;
a.foo();
La definición 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 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.
No se puede vincular con bibliotecas / archivos de objeto apropiados o compilar archivos de implementación
Por lo general, 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 vincularlos con esos archivos de objeto.
Debajo gcc debe especificar todos los archivos de objeto que se vincularán juntos en la línea de comando, o compilar los archivos de implementación juntos.
g++ -o test objectFile1.o objectFile2.o -lLibraryName
los libraryName
aquí es solo el nombre desnudo de la biblioteca, sin adiciones específicas de la plataforma. Entonces, por ejemplo, en la biblioteca de Linux, los archivos generalmente se llaman libfoo.so
pero solo escribirías -lfoo
. En Windows, ese mismo archivo podría llamarse foo.lib
, pero usarías el mismo argumento. Es posible que deba agregar el directorio donde se pueden encontrar esos archivos usando -L‹directory›
. Asegúrese de no escribir un espacio después -l
o -L
.
Para XCode: Agregue las rutas de búsqueda del encabezado de usuario -> agregue la ruta de búsqueda de la biblioteca -> arrastre y suelte la referencia de la biblioteca real en la carpeta del proyecto.
Debajo MSVS, los archivos agregados a un proyecto tienen automáticamente sus archivos de objeto vinculados y un lib
se generaría el archivo (en el uso común). Para usar los símbolos en un proyecto separado, debe incluir el 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
. (el camino hacia el lib
el archivo debe agregarse en Linker -> General -> Additional Library Directories
) Cuando se utiliza una biblioteca de terceros que se proporciona con un lib
archivo, si no lo hace, normalmente se produce 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 se compile automáticamente (aunque los archivos pueden, manualmente, ser excluidos individualmente de la compilación).
En la programación de Windows, la señal indicadora 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 al final de cada función en una sección llamada “Biblioteca”.
Recuerda algo, que tienes la capacidad de reseñar .