Apa yang dimaksud dengan kesalahan simbol eksternal / referensi yang tidak ditentukan? Apa penyebab umum dan bagaimana cara memperbaikinya?
Jangan ragu untuk mengedit / menambahkan milik Anda.
Apa yang dimaksud dengan kesalahan simbol eksternal / referensi yang tidak ditentukan? Apa penyebab umum dan bagaimana cara memperbaikinya?
Jangan ragu untuk mengedit / menambahkan milik Anda.
Jawaban:
Mengkompilasi program C ++ berlangsung dalam beberapa langkah, seperti yang ditentukan oleh 2.2 (kredit ke Keith Thompson untuk referensi) :
Diutamakan di antara aturan penerjemahan sintaks ditentukan oleh fase berikut [lihat catatan kaki] .
- Karakter file sumber fisik dipetakan, dengan cara yang ditentukan implementasi, ke set karakter sumber dasar (memperkenalkan karakter baris baru untuk indikator end-of-line) jika perlu. [MENGGUNTING]
- Setiap instance karakter backslash (\) segera diikuti oleh karakter baris baru dihapus, menyambungkan baris sumber fisik untuk membentuk garis sumber logis. [MENGGUNTING]
- File sumber didekomposisi menjadi token preprocessing (2.5) dan urutan karakter spasi putih (termasuk komentar). [MENGGUNTING]
- Arahan preprocessing dieksekusi, invokasi makro diperluas, dan ekspresi operator unilateral dieksekusi. [MENGGUNTING]
- Setiap set karakter sumber anggota dalam literal karakter atau string literal, serta setiap urutan pelarian dan nama karakter universal dalam literal karakter atau string string non-mentah, dikonversi ke anggota yang sesuai dari set karakter eksekusi; [MENGGUNTING]
- Token string string yang berdekatan disatukan.
- Karakter spasi putih yang memisahkan token tidak lagi signifikan. Setiap token preprocessing dikonversi menjadi token. (2.7). Token yang dihasilkan dianalisis secara sintaksis dan semantik serta diterjemahkan sebagai unit terjemahan. [MENGGUNTING]
- Unit terjemahan yang diterjemahkan dan unit instantiation digabungkan sebagai berikut: [SNIP]
- Semua referensi entitas eksternal diselesaikan. Komponen perpustakaan ditautkan untuk memenuhi referensi eksternal ke entitas yang tidak didefinisikan dalam terjemahan saat ini. Semua output penerjemah tersebut dikumpulkan ke dalam gambar program yang berisi informasi yang diperlukan untuk eksekusi dalam lingkungan pelaksanaannya. (penekanan milikku)
[catatan kaki] Implementasi harus berperilaku seolah-olah fase terpisah ini terjadi, meskipun dalam praktiknya fase yang berbeda mungkin dilipat bersama.
Kesalahan yang ditentukan terjadi selama tahap kompilasi terakhir ini, paling sering disebut sebagai penautan. Ini pada dasarnya berarti bahwa Anda mengkompilasi sekelompok file implementasi ke file objek atau pustaka dan sekarang Anda ingin membuatnya bekerja bersama.
Katakanlah Anda mendefinisikan simbol a
dalam a.cpp
. Sekarang, b.cpp
mendeklarasikan simbol itu dan menggunakannya. Sebelum menghubungkan, itu hanya mengasumsikan bahwa simbol itu didefinisikan di suatu tempat , tetapi belum peduli di mana. Fase penautan bertanggung jawab untuk menemukan simbol dan menautkannya dengan benar b.cpp
(yah, sebenarnya ke objek atau pustaka yang menggunakannya).
Jika Anda menggunakan Microsoft Visual Studio, Anda akan melihat bahwa proyek menghasilkan .lib
file. Ini berisi tabel simbol yang diekspor, dan tabel simbol yang diimpor. Simbol yang diimpor diselesaikan terhadap perpustakaan yang Anda tautkan, dan simbol yang diekspor disediakan untuk perpustakaan yang menggunakannya .lib
(jika ada).
Mekanisme serupa ada untuk kompiler / platform lainnya.
Pesan kesalahan umum adalah error LNK2001
, error LNK1120
, error LNK2019
untuk Microsoft Visual Studio dan undefined reference to
symbolName untuk GCC .
Kode:
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;
}
akan menghasilkan kesalahan berikut dengan 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
dan kesalahan serupa dengan 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
Penyebab umum meliputi:
#pragma
(Microsoft Visual Studio)UNICODE
Definisi yang tidak konsistenvirtual
membutuhkan implementasi.Mendeklarasikan destruktor murni masih mengharuskan Anda untuk mendefinisikannya (tidak seperti fungsi biasa):
struct X
{
virtual ~X() = 0;
};
struct Y : X
{
~Y() {}
};
int main()
{
Y y;
}
//X::~X(){} //uncomment this line for successful definition
Ini terjadi karena destruktor kelas dasar dipanggil ketika objek dihancurkan secara implisit, sehingga diperlukan definisi.
virtual
metode harus diimplementasikan atau didefinisikan sebagai murni.Ini mirip dengan non- virtual
metode tanpa definisi, dengan alasan tambahan bahwa deklarasi murni menghasilkan dummy vtable dan Anda mungkin mendapatkan kesalahan tautan tanpa menggunakan fungsi:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
int main()
{
Y y; //linker error although there was no call to X::foo
}
Agar ini berfungsi, nyatakan X::foo()
sebagai murni:
struct X
{
virtual void foo() = 0;
};
virtual
Anggota non- kelasBeberapa anggota perlu didefinisikan bahkan jika tidak digunakan secara eksplisit:
struct A
{
~A();
};
Berikut ini akan menghasilkan kesalahan:
A a; //destructor undefined
Implementasi dapat inline, dalam definisi kelas itu sendiri:
struct A
{
~A() {}
};
atau di luar:
A::~A() {}
Jika implementasi di luar definisi kelas, tetapi di header, metode harus ditandai inline
untuk mencegah definisi berganda.
Semua metode anggota yang digunakan perlu didefinisikan jika digunakan.
struct A
{
void foo();
};
void foo() {}
int main()
{
A a;
a.foo();
}
Definisi seharusnya
void A::foo() {}
static
anggota data harus didefinisikan di luar kelas dalam satu unit terjemahan :struct X
{
static int x;
};
int main()
{
int x = X::x;
}
//int X::x; //uncomment this line to define X::x
Inisialisasi dapat disediakan untuk anggota static
const
data tipe integral atau enumerasi dalam definisi kelas; Namun, penggunaan odr anggota ini masih akan memerlukan definisi lingkup namespace seperti dijelaskan di atas. C ++ 11 memungkinkan inisialisasi di dalam kelas untuk semua static const
anggota data.
Secara umum, setiap unit terjemahan akan menghasilkan file objek yang berisi definisi simbol yang didefinisikan dalam unit terjemahan itu. Untuk menggunakan simbol-simbol itu, Anda harus menautkan file-file objek tersebut.
Di bawah gcc Anda akan menentukan semua file objek yang akan dihubungkan bersama di baris perintah, atau mengkompilasi file implementasi bersama.
g++ -o test objectFile1.o objectFile2.o -lLibraryName
Di libraryName
sini hanya nama telanjang perpustakaan, tanpa tambahan khusus platform. Jadi misal di Linux library file biasanya dipanggil libfoo.so
tetapi Anda hanya akan menulis -lfoo
. Di Windows file yang sama mungkin dipanggil foo.lib
, tetapi Anda akan menggunakan argumen yang sama. Anda mungkin harus menambahkan direktori tempat file-file itu dapat ditemukan menggunakan -L‹directory›
. Pastikan untuk tidak menulis spasi setelah -l
atau -L
.
Untuk XCode : Tambahkan Jalur Pencarian Header Pengguna -> tambahkan Path Pencarian Perpustakaan -> drag dan drop referensi perpustakaan yang sebenarnya ke dalam folder proyek.
Di bawah MSVS , file yang ditambahkan ke proyek secara otomatis akan menghubungkan file objeknya bersama dan alib
file akan dihasilkan (dalam penggunaan umum). Untuk menggunakan simbol dalam proyek terpisah, Anda harus memasukkan lib
file dalam pengaturan proyek. Ini dilakukan di bagian Linker dari properti proyek, di Input -> Additional Dependencies
. (path ke lib
file harus ditambahkan Linker -> General -> Additional Library Directories
) Ketika menggunakan perpustakaan pihak ketiga yang disediakan dengan lib
file, kegagalan untuk melakukannya biasanya menghasilkan kesalahan.
Bisa juga terjadi bahwa Anda lupa untuk menambahkan file ke kompilasi, dalam hal ini file objek tidak akan dihasilkan. Di gcc Anda akan menambahkan file ke baris perintah. Dalam MSVS, menambahkan file ke proyek akan membuatnya mengkompilasinya secara otomatis (walaupun file dapat secara manual dikecualikan secara individual dari build).
Dalam pemrograman Windows, tanda yang menunjukkan bahwa Anda tidak menautkan pustaka yang diperlukan adalah bahwa nama simbol yang belum terselesaikan dimulai dengan __imp_
. Cari nama fungsi dalam dokumentasi, dan itu harus mengatakan perpustakaan mana yang perlu Anda gunakan. Misalnya, MSDN meletakkan informasi dalam kotak di bagian bawah setiap fungsi di bagian yang disebut "Perpustakaan".
gcc main.c
alih-alih gcc main.c other.c
(yang sering dilakukan pemula sebelum proyek mereka menjadi begitu besar untuk membangun file .o).
Deklarasi variabel tipikal adalah
extern int x;
Karena ini hanya deklarasi, diperlukan satu definisi . Definisi yang sesuai adalah:
int x;
Misalnya, berikut ini akan menghasilkan kesalahan:
extern int x;
int main()
{
x = 0;
}
//int x; // uncomment this line for successful definition
Komentar serupa berlaku untuk fungsi. Mendeklarasikan fungsi tanpa mendefinisikannya menyebabkan kesalahan:
void foo(); // declaration only
int main()
{
foo();
}
//void foo() {} //uncomment this line for successful definition
Hati-hati bahwa fungsi yang Anda implementasikan sama persis dengan yang Anda nyatakan. Misalnya, Anda mungkin memiliki cv-kualifikasi yang tidak cocok:
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)
Contoh ketidakcocokan lainnya termasuk
Pesan kesalahan dari kompiler akan sering memberi Anda deklarasi penuh variabel atau fungsi yang dideklarasikan tetapi tidak pernah didefinisikan. Bandingkan dengan erat dengan definisi yang Anda berikan. Pastikan setiap detail cocok.
#includes
tidak ditambahkan ke direktori sumber juga termasuk dalam kategori definisi yang hilang.
Urutan pustaka yang ditautkan tidak masalah jika pustaka bergantung satu sama lain. Secara umum, jika pustaka A
tergantung pada pustaka B
, maka libA
HARUS muncul sebelumnya libB
di bendera tautan.
Sebagai contoh:
// 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;
};
Buat perpustakaan:
$ 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
Menyusun:
$ 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
Jadi untuk mengulangi lagi, order TIDAK peduli!
apa yang dimaksud dengan "referensi eksternal / simbol eksternal yang tidak ditentukan"
Saya akan mencoba menjelaskan apa yang dimaksud dengan "referensi tidak ditentukan / simbol eksternal yang tidak terselesaikan".
Catatan: saya menggunakan g ++ dan Linux dan semua contoh untuk itu
Misalnya kita punya beberapa kode
// 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;
}
dan
// 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);
}
Buat file objek
$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o
Setelah fase assembler, kami memiliki file objek, yang berisi simbol apa pun untuk diekspor. Lihatlah simbolnya
$ 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]
Saya telah menolak beberapa baris dari keluaran, karena itu tidak masalah
Jadi, kita lihat ikuti simbol untuk diekspor.
[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable
src2.cpp tidak mengekspor apa pun dan kami belum melihat simbolnya
Tautkan file objek kami
$ g++ src1.o src2.o -o prog
dan jalankan
$ ./prog
123
Linker melihat simbol yang diekspor dan menautkannya. Sekarang kami mencoba untuk menghapus komentar pada baris di src2.cpp seperti di sini
// 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);
}
dan membangun kembali file objek
$ g++ -c src2.cpp -o src2.o
OK (tidak ada kesalahan), karena kami hanya membangun file objek, menghubungkan belum dilakukan. Coba tautkan
$ 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
Itu terjadi karena local_var_name kami statis, yaitu tidak terlihat untuk modul lain. Sekarang lebih dalam. Dapatkan output fase terjemahan
$ 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
Jadi, kami telah melihat tidak ada label untuk local_var_name, itu sebabnya linker belum menemukannya. Tapi kami adalah peretas :) dan kami bisa memperbaikinya. Buka src1.s di editor teks Anda dan ubah
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
untuk
.globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
yaitu Anda harus memiliki seperti di bawah ini
.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:
; ...
kami telah mengubah visibilitas local_var_name dan menetapkan nilainya menjadi 456789. Cobalah untuk membangun file objek darinya
$ g++ -c src1.s -o src2.o
ok, lihat output readelf (simbol)
$ readelf --symbols src1.o
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 local_var_name
sekarang local_var_name memiliki Bind GLOBAL (tadinya LOCAL)
tautan
$ g++ src1.o src2.o -o prog
dan jalankan
$ ./prog
123456789
ok, kita hack :)
Jadi, sebagai hasilnya - "referensi yang tidak terdefinisi / kesalahan simbol eksternal yang tidak terselesaikan" terjadi ketika linker tidak dapat menemukan simbol global dalam file objek.
Fungsi (atau variabel) void foo()
didefinisikan dalam program C dan Anda mencoba menggunakannya dalam program C ++:
void foo();
int main()
{
foo();
}
Linker C ++ mengharapkan nama-nama akan di-mangble, jadi Anda harus mendeklarasikan fungsinya sebagai:
extern "C" void foo();
int main()
{
foo();
}
Secara ekuivalen, alih-alih didefinisikan dalam program C, fungsi (atau variabel) void foo()
didefinisikan dalam C ++ tetapi dengan tautan C:
extern "C" void foo();
dan Anda mencoba menggunakannya dalam program C ++ dengan tautan C ++.
Jika seluruh perpustakaan disertakan dalam file header (dan dikompilasi sebagai kode C); termasuk harus sebagai berikut;
extern "C" {
#include "cheader.h"
}
#ifdef __cplusplus [\n] extern"C" { [\n] #endif
dan #ifdef __cplusplus [\n] } [\n] #endif
( [\n]
menjadi carriage return yang asli tetapi saya tidak dapat menulis ini dengan benar dalam komentar).
extern "C" { #include <myCppHeader.h> }
.
Jika semuanya gagal, kompilasi ulang.
Saya baru-baru ini bisa menyingkirkan kesalahan eksternal yang tidak terselesaikan di Visual Studio 2012 hanya dengan mengkompilasi ulang file yang menyinggung. Ketika saya membangun kembali, kesalahan hilang.
Ini biasanya terjadi ketika dua (atau lebih) perpustakaan memiliki ketergantungan siklik. Library A berupaya menggunakan simbol dalam B.lib dan library B mencoba menggunakan simbol dari A.lib. Tidak ada untuk memulai. Ketika Anda mencoba mengompilasi A, langkah tautan akan gagal karena tidak dapat menemukan B.lib. A.lib akan dihasilkan, tetapi tidak ada dll. Anda kemudian mengkompilasi B, yang akan berhasil dan menghasilkan B.lib. Mengkompilasi ulang A sekarang akan berfungsi karena B.lib sekarang ditemukan.
MSVS mengharuskan Anda untuk menentukan simbol mana yang akan diekspor dan diimpor menggunakan __declspec(dllexport)
dan __declspec(dllimport)
.
Fungsi ganda ini biasanya diperoleh melalui penggunaan makro:
#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif
Makro THIS_MODULE
hanya akan didefinisikan dalam modul yang mengekspor fungsi. Dengan begitu, deklarasi:
DLLIMPEXP void foo();
meluas ke
__declspec(dllexport) void foo();
dan memberitahu kompiler untuk mengekspor fungsi, karena modul saat ini berisi definisinya. Ketika menyertakan deklarasi dalam modul yang berbeda, itu akan diperluas ke
__declspec(dllimport) void foo();
dan memberi tahu kompiler bahwa definisi ada di salah satu perpustakaan yang Anda tautkan (juga lihat 1) ).
Anda dapat similary kelas impor / ekspor:
class DLLIMPEXP X
{
};
visibility
dan Windows .def
, karena ini juga mempengaruhi nama simbol dan keberadaannya.
.def
file. Jangan ragu untuk menambahkan jawaban atau mengedit yang ini.
Ini adalah salah satu pesan kesalahan yang paling membingungkan yang setiap programmer VC ++ telah melihat berulang kali. Mari kita perjelas dulu.
A. Apa itu simbol? Singkatnya, simbol adalah nama. Itu bisa berupa nama variabel, nama fungsi, nama kelas, nama typedef, atau apa pun kecuali nama dan tanda yang termasuk dalam bahasa C ++. Ini didefinisikan pengguna atau diperkenalkan oleh perpustakaan dependensi (yang ditentukan pengguna lain).
B. Apa itu eksternal?
Dalam VC ++, setiap file sumber (.cpp, .c, dll.) Dianggap sebagai unit terjemahan, kompiler mengkompilasi satu unit pada suatu waktu, dan menghasilkan satu file objek (.obj) untuk unit terjemahan saat ini. (Perhatikan bahwa setiap file header yang menyertakan file sumber ini akan diproses sebelumnya dan akan dianggap sebagai bagian dari unit terjemahan ini) Segala sesuatu di dalam unit terjemahan dianggap sebagai internal, yang lainnya dianggap sebagai eksternal. Di C ++, Anda dapat mereferensikan simbol eksternal dengan menggunakan kata kunci like extern
, __declspec (dllimport)
dan sebagainya.
C. Apa itu "tekad"? Resolve adalah istilah yang menghubungkan waktu. Dalam menghubungkan waktu, linker mencoba menemukan definisi eksternal untuk setiap simbol dalam file objek yang tidak dapat menemukan definisi secara internal. Ruang lingkup proses pencarian ini termasuk:
Proses pencarian ini disebut tekad.
D. Akhirnya, mengapa Simbol Eksternal yang Tidak Terselesaikan? Jika tautan tidak dapat menemukan definisi eksternal untuk simbol yang tidak memiliki definisi secara internal, itu akan melaporkan kesalahan Simbol Eksternal yang Tidak Terselesaikan.
E. Kemungkinan penyebab LNK2019 : Kesalahan Simbol Eksternal yang Tidak Terselesaikan. Kita sudah tahu bahwa kesalahan ini disebabkan karena linker gagal menemukan definisi simbol eksternal, kemungkinan penyebabnya dapat diurutkan sebagai:
Misalnya, jika kita memiliki fungsi yang disebut foo didefinisikan dalam a.cpp:
int foo()
{
return 0;
}
Di b.cpp kami ingin memanggil fungsi foo, jadi kami menambahkan
void foo();
untuk mendeklarasikan function foo (), dan memanggilnya di badan fungsi lain, katakan bar()
:
void bar()
{
foo();
}
Sekarang ketika Anda membangun kode ini, Anda akan mendapatkan kesalahan LNK2019 dengan mengeluh bahwa foo adalah simbol yang belum terselesaikan. Dalam hal ini, kita tahu bahwa foo () memiliki definisi dalam a.cpp, tetapi berbeda dari yang kita panggil (nilai pengembalian berbeda). Ini adalah kasus yang ada definisi.
Jika kami ingin memanggil beberapa fungsi di pustaka, tetapi pustaka impor tidak ditambahkan ke daftar ketergantungan tambahan (ditetapkan dari Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
:) pengaturan proyek Anda. Sekarang tautan akan melaporkan LNK2019 karena definisi tersebut tidak ada dalam lingkup pencarian saat ini.
Templat yang tidak dikhususkan harus memiliki definisi yang dapat dilihat oleh semua unit terjemahan yang menggunakannya. Itu berarti Anda tidak dapat memisahkan definisi templat ke file implementasi. Jika Anda harus memisahkan implementasi, solusi yang biasa adalah memiliki impl
file yang Anda sertakan di akhir header yang menyatakan templat. Situasi umum adalah:
template<class T>
struct X
{
void foo();
};
int main()
{
X<int> x;
x.foo();
}
//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}
Untuk memperbaiki ini, Anda harus memindahkan definisi X::foo
ke file header atau tempat yang terlihat oleh unit terjemahan yang menggunakannya.
Templat khusus dapat diimplementasikan dalam file implementasi dan implementasinya tidak harus terlihat, tetapi spesialisasi harus dinyatakan sebelumnya.
Untuk penjelasan lebih lanjut dan solusi lain yang mungkin (instantiasi eksplisit) lihat pertanyaan dan jawaban ini .
referensi tidak terdefinisi ke WinMain@16
atau referensi titik masuk 'tidak biasa' yang serupamain()
(terutama untukStudio visual).
Anda mungkin telah kehilangan untuk memilih jenis proyek yang tepat dengan IDE Anda yang sebenarnya. IDE mungkin ingin mengikat misalnya proyek Aplikasi Windows ke fungsi titik masuk tersebut (seperti yang ditentukan dalam referensi yang hilang di atas), daripada int main(int argc, char** argv);
tanda tangan yang umum digunakan .
Jika IDE Anda mendukung Proyek Konsol Biasa, Anda mungkin ingin memilih jenis proyek ini, alih-alih proyek aplikasi windows.
Berikut adalah case1 dan case2 ditangani secara lebih rinci dari masalah dunia nyata .
WinMain
. Program C ++ yang valid perlu a main
.
Paket Visual Studio NuGet perlu diperbarui untuk versi toolset baru
Saya baru saja mengalami masalah ini mencoba menautkan libpng dengan Visual Studio 2013. Masalahnya adalah bahwa file paket hanya memiliki perpustakaan untuk Visual Studio 2010 dan 2012.
Solusi yang benar adalah dengan harapan pengembang merilis paket yang diperbarui dan kemudian memutakhirkan, tetapi itu berhasil bagi saya dengan meretas dalam pengaturan ekstra untuk VS2013, menunjuk pada file perpustakaan VS2012.
Saya mengedit paket (dalam packages
folder di dalam direktori solusi) dengan menemukan packagename\build\native\packagename.targets
dan di dalam file itu, menyalin semua v110
bagian. Saya mengubah v110
ke v120
dalam bidang kondisi hanya sangat berhati-hati untuk meninggalkan semua path nama file v110
. Ini hanya memungkinkan Visual Studio 2013 untuk menautkan ke perpustakaan untuk 2012, dan dalam hal ini, berhasil.
Misalkan Anda memiliki proyek besar yang ditulis dalam c ++ yang memiliki ribuan file .cpp dan ribuan file .h. Dan katakanlah proyek ini juga tergantung pada sepuluh perpustakaan statis. Katakanlah kita berada di Windows dan kami membangun proyek kami di Visual Studio 20xx. Ketika Anda menekan Ctrl + F7 Visual Studio untuk mulai mengkompilasi seluruh solusi (misalkan kita hanya memiliki satu proyek dalam solusi)
Apa arti kompilasi?
Langkah kedua kompilasi dilakukan oleh Linker.Linker harus menggabungkan semua file objek dan akhirnya membangun output (yang dapat dieksekusi atau perpustakaan)
Langkah-langkah dalam Menghubungkan proyek
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
Pengamatan
Bagaimana Mengatasi kesalahan semacam ini
Kesalahan Waktu Kompilator:
Kesalahan Waktu Linker
#pragma once
untuk memungkinkan kompiler untuk tidak memasukkan satu header jika sudah termasuk dalam .cpp saat ini yang dikompilasiSaya baru-baru ini memiliki masalah ini, dan ternyata itu adalah bug di Visual Studio Express 2013 . Saya harus menghapus file sumber dari proyek dan menambahkannya kembali untuk mengatasi bug.
Langkah-langkah untuk dicoba jika Anda yakin itu adalah bug di kompiler / IDE:
Sebagian besar penghubung modern menyertakan opsi verbose yang dicetak ke berbagai tingkat;
Untuk gcc dan dentang; Anda biasanya akan menambah -v -Wl,--verbose
atau -v -Wl,-v
ke baris perintah. Rincian lebih lanjut dapat ditemukan di sini;
Untuk MSVC, /VERBOSE
(khususnya /VERBOSE:LIB
) ditambahkan ke baris perintah tautan.
/VERBOSE
opsi tautan .File .lib tertaut dikaitkan dengan .dll
Saya memiliki masalah yang sama. Katakanlah saya punya proyek MyProject dan TestProject. Saya telah secara efektif menautkan file lib untuk MyProject ke TestProject. Namun, file lib ini diproduksi sebagai DLL untuk MyProject dibangun. Juga, saya tidak mengandung kode sumber untuk semua metode di MyProject, tetapi hanya akses ke titik masuk DLL.
Untuk mengatasi masalah ini, saya membuat MyProject sebagai LIB, dan menautkan TestProject ke file .lib ini (saya salin tempelkan file .lib yang dihasilkan ke dalam folder TestProject). Saya kemudian dapat membangun lagi MyProject sebagai DLL. Itu dikompilasi karena lib yang terhubung dengan TestProject tidak mengandung kode untuk semua metode di kelas di MyProject.
Karena orang-orang sepertinya diarahkan ke pertanyaan ini ketika datang ke kesalahan linker saya akan menambahkan ini di sini.
Salah satu alasan yang mungkin untuk kesalahan penghubung dengan GCC 5.2.0 adalah bahwa libstdc ++ library ABI baru sekarang dipilih secara default.
Jika Anda mendapatkan kesalahan linker tentang referensi yang tidak terdefinisi ke simbol yang melibatkan jenis di std :: __ cxx11 namespace atau tag [abi: cxx11] maka itu mungkin menunjukkan bahwa Anda mencoba untuk menautkan bersama file objek yang dikompilasi dengan nilai yang berbeda untuk _GLIBCXX_USE_CXX11_ABI makro. Ini biasanya terjadi ketika menautkan ke perpustakaan pihak ketiga yang dikompilasi dengan versi GCC yang lebih lama. Jika perpustakaan pihak ketiga tidak dapat dibangun kembali dengan ABI baru maka Anda perlu mengkompilasi ulang kode Anda dengan ABI lama.
Jadi jika Anda tiba-tiba mendapatkan kesalahan linker ketika beralih ke GCC setelah 5.1.0 ini akan menjadi hal yang patut untuk dilihat.
Wrapper di sekitar GNU ld yang tidak mendukung skrip tautan
Beberapa file .so sebenarnya adalah skrip linker GNU ld , misalnya file libtbb.so adalah file teks ASCII dengan konten ini:
INPUT (libtbb.so.2)
Beberapa bangunan yang lebih kompleks mungkin tidak mendukung ini. Sebagai contoh, jika Anda memasukkan -v dalam opsi kompiler, Anda dapat melihat bahwa mainwin gcc wrapper mwdip membuang file perintah skrip linker di daftar output verbose dari perpustakaan untuk dihubungkan. Pekerjaan sederhana di sekitar adalah untuk mengganti perintah input skrip linker file dengan salinan file sebagai gantinya (atau symlink), misalnya
cp libtbb.so.2 libtbb.so
Atau Anda bisa mengganti argumen -l dengan path lengkap dari .so, misal alih-alih -ltbb
lakukan/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
libfoo
tergantung libbar
, maka keterkaitan Anda dengan benar diletakkan libfoo
sebelumnya libbar
.undefined reference to
sesuatu .#include
dan sebenarnya didefinisikan di perpustakaan yang Anda tautkan.Contohnya dalam C. Mereka juga bisa menjadi 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;
}
Anda membangun perpustakaan statis Anda:
$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o
Anda mengkompilasi program Anda:
$ gcc -I. -c -o eg1.o eg1.c
Anda mencoba menautkannya dengan libmy_lib.a
dan gagal:
$ 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
Hasil yang sama jika Anda mengkompilasi dan menautkan dalam satu langkah, seperti:
$ 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;
}
Kompilasi program Anda:
$ gcc -c -o eg2.o eg2.c
Cobalah menautkan program Anda dengan libz
dan gagal:
$ 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
Sama jika Anda mengompilasi dan menautkan dalam sekali jalan:
$ 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
Dan variasi pada contoh 2 yang melibatkan pkg-config
:
$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
Dalam urutan file objek dan pustaka yang ingin Anda tautkan untuk membuat program Anda, Anda menempatkan pustaka sebelum file objek yang merujuknya. Anda perlu menempatkan perpustakaan setelah file objek yang merujuknya.
Tautkan contoh 1 dengan benar:
$ gcc -o eg1 eg1.o -L. -lmy_lib
Keberhasilan:
$ ./eg1
Hello World
Tautkan contoh 2 dengan benar:
$ gcc -o eg2 eg2.o -lz
Keberhasilan:
$ ./eg2
1.2.8
Tautkan pkg-config
variasi 2 contoh dengan benar:
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib)
$ ./eg2
1.2.8
Membaca adalah opsional mulai dari sini .
Secara default, perintah tautan yang dibuat oleh GCC, di distro Anda, menggunakan file di tautan dari kiri ke kanan dalam urutan baris perintah. Ketika menemukan bahwa suatu file merujuk pada sesuatu dan tidak mengandung definisi untuk itu, untuk akan mencari definisi dalam file lebih jauh ke kanan. Jika akhirnya menemukan definisi, referensi diselesaikan. Jika referensi tetap tidak terselesaikan di akhir, tautan gagal: tautan tidak mencari mundur.
Pertama, contoh 1 , dengan perpustakaan statismy_lib.a
Perpustakaan statis adalah arsip file objek yang diindeks. Ketika tautan menemukan -lmy_lib
dalam urutan tautan dan mengetahui bahwa ini merujuk ke pustaka statis ./libmy_lib.a
, ia ingin mengetahui apakah program Anda membutuhkan salah satu file objek di dalamnya libmy_lib.a
.
Hanya ada file objek di libmy_lib.a
, yaitu my_lib.o
, dan hanya ada satu hal yang didefinisikan my_lib.o
, yaitu fungsi hw
.
Linker akan memutuskan bahwa program Anda perlu my_lib.o
jika dan hanya jika sudah tahu bahwa program Anda mengacu pada hw
, dalam satu atau lebih file objek yang telah ditambahkan ke program, dan bahwa tidak ada file objek yang telah ditambahkan mengandung definisi untuk hw
.
Jika itu benar, maka tautan akan mengekstrak salinan dari my_lib.o
perpustakaan dan menambahkannya ke program Anda. Kemudian, program anda berisi definisi untuk hw
, jadi referensi untuk hw
yang diselesaikan .
Ketika Anda mencoba menautkan program seperti:
$ gcc -o eg1 -L. -lmy_lib eg1.o
tautan belum ditambahkan eg1.o
ke program saat dilihat
-lmy_lib
. Karena pada saat itu, belum terlihat eg1.o
. Program Anda belum membuat referensi apa pun hw
: ia belum membuat referensi sama sekali , karena semua referensi yang dibuatnya ada di dalamnya eg1.o
.
Jadi linker tidak menambah my_lib.o
program dan tidak digunakan untuk selanjutnya libmy_lib.a
.
Selanjutnya, ia menemukan eg1.o
, dan menambahkannya menjadi program. File objek dalam urutan tautan selalu ditambahkan ke program. Sekarang, program membuat referensi ke hw
, dan tidak mengandung definisi hw
; tetapi tidak ada yang tersisa dalam urutan tautan yang dapat memberikan definisi yang hilang. Referensi untuk hw
berakhir tidak terselesaikan , dan hubungan gagal.
Kedua, contoh 2 , dengan perpustakaan bersamalibz
Pustaka bersama bukanlah arsip dari file objek atau semacamnya. Ini jauh lebih seperti program yang tidak memiliki main
fungsi dan sebagai gantinya memaparkan beberapa simbol lain yang didefinisikannya, sehingga program lain dapat menggunakannya saat runtime.
Banyak Linux distro hari mengkonfigurasi GCC mereka toolchain sehingga driver bahasanya ( gcc
, g++
, gfortran
dll) menginstruksikan linker sistem ( ld
) untuk nge-link shared library pada saat dibutuhkan dasar. Anda punya salah satu distro itu.
Ini berarti bahwa ketika penghubung menemukan -lz
dalam urutan tautan, dan mengetahui bahwa ini merujuk ke perpustakaan bersama (katakanlah) /usr/lib/x86_64-linux-gnu/libz.so
, ia ingin tahu apakah ada referensi yang telah ditambahkan ke program Anda yang belum didefinisikan memiliki definisi yang diekspor olehlibz
Jika itu benar, maka tautan tidak akan menyalin potongan apa pun libz
dan menambahkannya ke program Anda; sebagai gantinya, itu hanya akan dokter kode program Anda sehingga: -
Saat runtime, program loader sistem akan memuat salinan libz
ke dalam proses yang sama seperti program Anda setiap kali memuat salinan program Anda, untuk menjalankannya.
Saat runtime, setiap kali program Anda merujuk ke sesuatu yang didefinisikan
libz
, referensi itu menggunakan definisi yang diekspor oleh salinan libz
dalam proses yang sama.
Program Anda ingin merujuk hanya satu hal yang memiliki definisi yang diekspor oleh libz
, yaitu fungsi zlibVersion
, yang disebut hanya sekali, di eg2.c
. Jika tautan menambahkan referensi itu ke program Anda, dan kemudian menemukan definisi yang diekspor oleh libz
, referensi diselesaikan
Tetapi ketika Anda mencoba menautkan program seperti:
gcc -o eg2 -lz eg2.o
urutan kejadian salah dengan cara yang sama seperti contoh 1. Pada saat tautan ditemukan -lz
, tidak ada referensi apa pun dalam program: semuanya ada eg2.o
, yang belum terlihat. Jadi linker memutuskan tidak ada gunanya libz
. Ketika mencapai eg2.o
, menambahkannya ke program, dan kemudian memiliki referensi yang tidak ditentukan zlibVersion
, urutan tautan selesai; referensi itu tidak terselesaikan, dan hubungan gagal.
Terakhir, pkg-config
variasi dari contoh 2 sekarang memiliki penjelasan yang jelas. Setelah ekspansi shell:
gcc -o eg2 $(pkg-config --libs zlib) eg2.o
menjadi:
gcc -o eg2 -lz eg2.o
yang hanya contoh 2 lagi.
Tautan:
gcc -o eg2 -lz eg2.o
bekerja dengan baik untuk Anda!
(Atau: Tautan itu berfungsi baik untuk Anda pada, katakanlah, Fedora 23, tetapi gagal di Ubuntu 16.04)
Itu karena distro tempat linkage berfungsi adalah salah satu yang tidak mengonfigurasi rantai alat GCC untuk menautkan pustaka bersama sesuai kebutuhan .
Kembali pada hari itu, itu normal untuk sistem seperti unix untuk menghubungkan perpustakaan statis dan berbagi dengan aturan yang berbeda. Pustaka statis dalam urutan hubungan dikaitkan pada basis yang diperlukan seperti dijelaskan pada contoh 1, tetapi pustaka bersama dihubungkan tanpa syarat.
Perilaku ini ekonomis pada waktu tautan karena penghubung tidak perlu merenungkan apakah perpustakaan bersama diperlukan oleh program: jika itu adalah perpustakaan bersama, tautkan. Dan sebagian besar perpustakaan di sebagian besar tautan adalah perpustakaan bersama. Tetapi ada juga kerugiannya: -
Itu tidak ekonomis saat runtime , karena dapat menyebabkan pustaka bersama untuk dimuat bersama dengan sebuah program bahkan jika tidak membutuhkannya.
Aturan tautan yang berbeda untuk pustaka statis dan terbagi dapat membingungkan bagi programmer yang tidak mahir, yang mungkin tidak tahu apakah -lfoo
dalam tautan mereka akan menyelesaikan /some/where/libfoo.a
atau tidak /some/where/libfoo.so
, dan mungkin tidak memahami perbedaan antara pustaka bersama dan statis.
Pertukaran ini telah menyebabkan situasi skismatik hari ini. Beberapa distro telah mengubah aturan tautan GCC untuk perpustakaan bersama sehingga prinsip yang diperlukan berlaku untuk semua perpustakaan. Beberapa distro terjebak dengan cara lama.
Jika saya lakukan saja:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
tentunya gcc harus melakukan kompilasi eg1.c
terlebih dahulu, dan kemudian menautkan file objek yang dihasilkan dengannya libmy_lib.a
. Jadi bagaimana mungkin ia tidak tahu bahwa file objek diperlukan ketika melakukan penautan?
Karena mengkompilasi dan menghubungkan dengan satu perintah tidak mengubah urutan urutan tautan.
Ketika Anda menjalankan perintah di atas, gcc
cari tahu bahwa Anda ingin kompilasi + tautan. Jadi di belakang layar, itu menghasilkan perintah kompilasi, dan menjalankannya, kemudian menghasilkan perintah tautan, dan menjalankannya, seolah-olah Anda telah menjalankan dua perintah:
$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o
Jadi linkage gagal seperti halnya jika Anda tidak menjalankan dua perintah. Satu-satunya perbedaan yang Anda perhatikan dalam kegagalan adalah bahwa gcc telah menghasilkan file objek sementara dalam kompilasi + tautan case, karena Anda tidak menyuruhnya menggunakannya eg1.o
. Kami melihat:
/tmp/ccQk1tvs.o: In function `main'
dari pada:
eg1.o: In function `main':
Urutan di mana pustaka terkait yang saling tergantung ditentukan salah
Menempatkan pustaka yang saling bergantung dalam urutan yang salah hanyalah salah satu cara di mana Anda bisa mendapatkan file yang membutuhkan definisi hal-hal yang datang kemudian dalam tautan daripada file yang memberikan definisi. Menempatkan pustaka di depan file objek yang merujuknya adalah cara lain untuk membuat kesalahan yang sama.
Diberikan potongan kode jenis templat dengan operator teman (atau fungsi);
template <typename T>
class Foo {
friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};
The operator<<
sedang dinyatakan sebagai fungsi non-template. Untuk setiap jenis yang T
digunakan Foo
, perlu ada non-templated operator<<
. Misalnya, jika ada tipe yang Foo<int>
dideklarasikan, maka harus ada implementasi operator sebagai berikut;
std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}
Karena tidak diterapkan, tautan gagal menemukannya dan mengakibatkan kesalahan.
Untuk memperbaikinya, Anda dapat mendeklarasikan operator template sebelum Foo
jenis dan kemudian menyatakan sebagai teman, instantiasi yang sesuai. Sintaksnya sedikit canggung, tetapi terlihat sebagai berikut;
// 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
}
Kode di atas membatasi persahabatan operator dengan instantiasi yang sesuai Foo
, yaitu operator<< <int>
instantiasi terbatas untuk mengakses anggota pribadi dari instantiasi Foo<int>
.
Alternatif termasuk;
Mengizinkan pertemanan meluas ke semua instansiasi templat, sebagai berikut;
template <typename T>
class Foo {
template <typename T1>
friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
// ...
};
Atau, implementasi untuk operator<<
dapat dilakukan inline di dalam definisi kelas;
template <typename T>
class Foo {
friend std::ostream& operator<<(std::ostream& os, const Foo& a)
{ /*...*/ }
// ...
};
Catatan , ketika pernyataan operator (atau fungsi) hanya muncul di kelas, nama tidak tersedia untuk pencarian "normal", hanya untuk pencarian bergantung argumen, dari cppreference ;
Sebuah nama yang pertama kali dideklarasikan dalam deklarasi teman di dalam kelas atau templat kelas X menjadi anggota dari namespace X yang terlampir, tetapi tidak dapat diakses untuk pencarian (kecuali pencarian yang bergantung pada argumen yang menganggap X) kecuali deklarasi yang sesuai pada lingkup namespace adalah disediakan ...
Ada bacaan lebih lanjut tentang teman templat di cppreference dan C ++ FAQ .
Daftar kode yang menunjukkan teknik-teknik di atas .
Sebagai catatan untuk contoh kode yang gagal; g ++ memperingatkan tentang ini sebagai berikut
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)
Kesalahan tautan dapat terjadi ketika file header dan pustaka bersama yang terkait (file .lib) tidak sinkron. Biarkan saya jelaskan.
Bagaimana cara kerja tautan? Linker cocok dengan deklarasi fungsi (dideklarasikan di header) dengan definisinya (di perpustakaan bersama) dengan membandingkan tanda tangan mereka. Anda bisa mendapatkan kesalahan linker jika linker tidak menemukan definisi fungsi yang cocok dengan sempurna.
Apakah mungkin untuk tetap mendapatkan kesalahan linker meskipun deklarasi dan definisi tampaknya cocok? Iya! Mereka mungkin terlihat sama dalam kode sumber, tetapi itu benar-benar tergantung pada apa yang dilihat kompiler. Pada dasarnya Anda bisa berakhir dengan situasi seperti ini:
// header1.h
typedef int Number;
void foo(Number);
// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically
Perhatikan bagaimana meskipun kedua deklarasi fungsi terlihat identik dalam kode sumber, tetapi keduanya benar-benar berbeda menurut kompiler.
Anda mungkin bertanya bagaimana seseorang berakhir dalam situasi seperti itu? Sertakan jalur tentu saja! Jika saat mengkompilasi pustaka bersama, jalur sertakan mengarah ke header1.h
dan Anda akhirnya menggunakan header2.h
di program Anda sendiri, Anda akan dibiarkan menggaruk tajuk Anda bertanya-tanya apa yang terjadi (pun intended).
Contoh bagaimana ini bisa terjadi di dunia nyata dijelaskan di bawah ini.
Saya punya dua proyek: graphics.lib
dan main.exe
. Kedua proyek bergantung common_math.h
. Misalkan perpustakaan mengekspor fungsi berikut:
// graphics.lib
#include "common_math.h"
void draw(vec3 p) { ... } // vec3 comes from common_math.h
Dan kemudian Anda pergi ke depan dan memasukkan perpustakaan ke dalam proyek Anda sendiri.
// main.exe
#include "other/common_math.h"
#include "graphics.h"
int main() {
draw(...);
}
Ledakan! Anda mendapatkan kesalahan linker dan Anda tidak tahu mengapa itu gagal. Alasannya adalah bahwa perpustakaan umum menggunakan versi yang berbeda dari yang sama termasuk common_math.h
(saya telah membuatnya jelas di sini dalam contoh dengan memasukkan jalur yang berbeda, tetapi mungkin tidak selalu begitu jelas. Mungkin jalur sertakan berbeda dalam pengaturan kompiler) .
Perhatikan dalam contoh ini, penghubung akan memberi tahu Anda bahwa itu tidak dapat ditemukan draw()
, padahal kenyataannya Anda tahu itu sedang diekspor oleh perpustakaan. Anda bisa menghabiskan waktu berjam-jam menggaruk-garuk kepala bertanya-tanya apa yang salah. Masalahnya, linker melihat tanda tangan yang berbeda karena tipe parameternya sedikit berbeda. Dalam contoh ini, vec3
ada jenis yang berbeda di kedua proyek sejauh menyangkut kompiler. Ini bisa terjadi karena mereka berasal dari dua file sertakan yang sedikit berbeda (mungkin file sertakan berasal dari dua versi perpustakaan yang berbeda).
DUMPBIN adalah teman Anda, jika Anda menggunakan Visual Studio. Saya yakin kompiler lain memiliki alat serupa lainnya.
Prosesnya seperti ini:
[1] Yang saya maksud dengan proyek adalah seperangkat file sumber yang dihubungkan bersama untuk menghasilkan perpustakaan atau yang dapat dieksekusi.
EDIT 1: Tulis ulang bagian pertama agar lebih mudah dimengerti. Berikan komentar di bawah ini untuk memberi tahu saya jika ada sesuatu yang perlu diperbaiki. Terima kasih!
UNICODE
Definisi yang tidak konsistenWindows UNICODE build dibuat dengan TCHAR
dll. Didefinisikan sebagai wchar_t
dll. Ketika tidak membangun dengan UNICODE
didefinisikan sebagai build dengan TCHAR
didefinisikan sebagai char
dll. Ini UNICODE
dan _UNICODE
mendefinisikan mempengaruhi semua tipe string " T
" ; LPTSTR
, LPCTSTR
dan rusa mereka.
Membangun satu perpustakaan dengan UNICODE
didefinisikan dan berusaha untuk menautkannya dalam proyek di mana UNICODE
tidak didefinisikan akan menghasilkan kesalahan penghubung karena akan ada ketidakcocokan dalam definisi TCHAR
; char
vs. wchar_t
.
Kesalahan biasanya mencakup fungsi nilai dengan tipe char
atau wchar_t
turunan, ini bisa termasuk std::basic_string<>
dll juga. Saat menelusuri fungsi yang terpengaruh dalam kode, sering kali akan ada referensi ke TCHAR
atau std::basic_string<TCHAR>
lain - lain. Ini adalah tanda bahwa kode awalnya ditujukan untuk UNICODE dan karakter Multi-Byte (atau "sempit") build .
Untuk memperbaikinya, buat semua perpustakaan dan proyek yang diperlukan dengan definisi UNICODE
(dan _UNICODE
) yang konsisten .
Ini bisa dilakukan dengan;
#define UNICODE
#define _UNICODE
Atau dalam pengaturan proyek;
Properti Proyek> Umum> Default Proyek> Set Karakter
Atau di baris perintah;
/DUNICODE /D_UNICODE
Alternatif ini berlaku juga, jika UNICODE tidak dimaksudkan untuk digunakan, pastikan definisi tidak ditetapkan, dan / atau pengaturan multi-karakter digunakan dalam proyek dan diterapkan secara konsisten.
Jangan lupa untuk konsisten antara build "Release" dan "Debug".
"Bersih" bangunan dapat menghilangkan "kayu mati" yang mungkin dibiarkan tergeletak di sekitar bangunan sebelumnya, bangunan gagal, bangunan tidak lengkap, dan masalah pembangunan sistem terkait lainnya.
Secara umum IDE atau build akan menyertakan beberapa bentuk fungsi "bersih", tetapi ini mungkin tidak dikonfigurasi dengan benar (misalnya dalam makefile manual) atau mungkin gagal (misalnya, binari perantara atau yang dihasilkan hanya baca-saja).
Setelah "clean" selesai, verifikasi bahwa "clean" telah berhasil dan semua file perantara yang dihasilkan (mis. Makefile otomatis) telah berhasil dihapus.
Proses ini dapat dilihat sebagai upaya terakhir, tetapi seringkali merupakan langkah pertama yang baik ; terutama jika kode yang terkait dengan kesalahan baru-baru ini ditambahkan (baik secara lokal atau dari repositori sumber).
const
deklarasi / definisi variabel (khusus C ++)Bagi orang-orang yang datang dari C mungkin mengejutkan bahwa dalam C ++ const
variabel global memiliki hubungan internal (atau statis). Dalam C ini tidak terjadi, karena semua variabel global secara implisit extern
(yaitu ketika static
kata kunci hilang).
Contoh:
// 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
}
benar adalah dengan menggunakan file header dan memasukkannya ke file2.cpp dan file1.cpp
extern const int test;
extern int test2;
Atau orang dapat mendeklarasikan const
variabel dalam file1.cpp dengan eksplisitextern
Meskipun ini adalah pertanyaan yang cukup lama dengan beberapa jawaban yang diterima, saya ingin membagikan cara mengatasi kesalahan "referensi tidak jelas ke " yang tidak jelas .
Saya menggunakan alias untuk merujuk ke std::filesystem::path
: filesystem di perpustakaan standar sejak C ++ 17 tetapi program saya juga perlu dikompilasi di C ++ 14 jadi saya memutuskan untuk menggunakan alias variabel:
#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
Katakanlah saya memiliki tiga file: main.cpp, file.h, file.cpp:
Perhatikan berbagai pustaka yang digunakan dalam main.cpp dan file.h. Karena main.cpp # include'd " file.h " setelah < filesystem >, versi filesystem yang digunakan adalah C ++ 17 . Saya biasa mengkompilasi program dengan perintah berikut:
$ g++ -g -std=c++17 -c main.cpp
-> mengkompilasi main.cpp ke main.o
$ g++ -g -std=c++17 -c file.cpp
-> mengkompilasi file.cpp dan file.h ke file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs
-> tautan main.o dan file.o
Dengan cara ini setiap fungsi yang terkandung dalam file.o dan digunakan dalam main.o yang diperlukanpath_t
memberikan kesalahan "undefined reference" karena main.o merujuk std::filesystem::path
tetapi file.o ke std::experimental::filesystem::path
.
Untuk memperbaikinya, saya hanya perlu mengubah <eksperimental :: filesystem> di file.h ke <filesystem> .
Perilaku default gcc adalah bahwa semua simbol terlihat. Namun, ketika unit terjemahan dibangun dengan opsi -fvisibility=hidden
, hanya fungsi / simbol yang ditandai dengan __attribute__ ((visibility ("default")))
eksternal dalam objek bersama yang dihasilkan.
Anda dapat memeriksa apakah simbol yang Anda cari adalah eksternal dengan memohon:
# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL
simbol tersembunyi / lokal ditunjukkan oleh nm
dengan tipe simbol huruf kecil, misalnya t
alih-alih `T untuk bagian kode:
nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL
Anda juga dapat menggunakan nm
opsi -C
untuk menghapus nama (jika C ++ digunakan).
Mirip dengan Windows-dll, orang akan menandai fungsi publik dengan define, misalnya DLL_PUBLIC
didefinisikan sebagai:
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
DLL_PUBLIC int my_public_function(){
...
}
Yang kira-kira sesuai dengan versi Windows / MSVC:
#ifdef BUILDING_DLL
#define DLL_PUBLIC __declspec(dllexport)
#else
#define DLL_PUBLIC __declspec(dllimport)
#endif
Informasi lebih lanjut tentang visibilitas dapat ditemukan di wiki gcc.
Ketika unit terjemahan dikompilasi dengan -fvisibility=hidden
simbol yang dihasilkan masih memiliki hubungan eksternal (ditunjukkan dengan tipe simbol huruf besar oleh nm
) dan dapat digunakan untuk hubungan eksternal tanpa masalah jika file objek menjadi bagian dari perpustakaan statis. Tautan menjadi lokal hanya ketika file objek ditautkan ke perpustakaan bersama.
Untuk menemukan simbol mana dalam file objek yang tersembunyi jalankan:
>>> objdump -t XXXX.o | grep hidden
0000000000000000 g F .text 000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g F .text 000000000000000b .hidden HIDDEN_SYMBOL2
nm -CD
atau nm -gCD
untuk melihat simbol eksternal. Lihat juga Visibilitas di wiki GCC.
Arsitektur yang berbeda
Anda mungkin melihat pesan seperti:
library machine type 'x64' conflicts with target machine type 'X86'
Dalam hal ini, itu berarti bahwa simbol yang tersedia adalah untuk arsitektur yang berbeda dari yang Anda kompilasi.
Pada Visual Studio, ini disebabkan oleh "Platform" yang salah, dan Anda harus memilih yang tepat atau menginstal versi perpustakaan yang tepat.
Di Linux, ini mungkin karena folder pustaka yang salah (menggunakan lib
bukan lib64
sebagai contoh).
Di MacOS, ada opsi pengiriman kedua arsitektur dalam file yang sama. Mungkin saja tautannya mengharapkan kedua versi ada di sana, tetapi hanya satu yang ada. Ini juga bisa menjadi masalah dengan folder lib
/ salah di lib64
mana perpustakaan diambil.