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.
c++
linker-errors
undefined-reference
c++-faq
unresolved-external
Luchian Grigore
sumber
sumber
Jawaban:
Mengkompilasi program C ++ berlangsung dalam beberapa langkah, seperti yang ditentukan oleh 2.2 (kredit ke Keith Thompson untuk referensi) :
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
dalama.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 benarb.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 danundefined reference to
symbolName untuk GCC .Kode:
akan menghasilkan kesalahan berikut dengan GCC :
dan kesalahan serupa dengan Microsoft Visual Studio :
Penyebab umum meliputi:
#pragma
(Microsoft Visual Studio)UNICODE
Definisi yang tidak konsistensumber
Anggota kelas:
Destroyer murni
virtual
membutuhkan implementasi.Mendeklarasikan destruktor murni masih mengharuskan Anda untuk mendefinisikannya (tidak seperti fungsi biasa):
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:Agar ini berfungsi, nyatakan
X::foo()
sebagai murni:virtual
Anggota non- kelasBeberapa anggota perlu didefinisikan bahkan jika tidak digunakan secara eksplisit:
Berikut ini akan menghasilkan kesalahan:
Implementasi dapat inline, dalam definisi kelas itu sendiri:
atau di luar:
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.
Kesalahan umum adalah lupa untuk memenuhi syarat nama:
Definisi seharusnya
static
anggota data harus didefinisikan di luar kelas dalam satu unit terjemahan :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 semuastatic const
anggota data.sumber
Gagal menautkan file pustaka / objek yang sesuai atau mengkompilasi file implementasi
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.
Di
libraryName
sini hanya nama telanjang perpustakaan, tanpa tambahan khusus platform. Jadi misal di Linux library file biasanya dipanggillibfoo.so
tetapi Anda hanya akan menulis-lfoo
. Di Windows file yang sama mungkin dipanggilfoo.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 a
lib
file akan dihasilkan (dalam penggunaan umum). Untuk menggunakan simbol dalam proyek terpisah, Anda harus memasukkanlib
file dalam pengaturan proyek. Ini dilakukan di bagian Linker dari properti proyek, diInput -> Additional Dependencies
. (path kelib
file harus ditambahkanLinker -> General -> Additional Library Directories
) Ketika menggunakan perpustakaan pihak ketiga yang disediakan denganlib
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".sumber
gcc main.c
alih-alihgcc main.c other.c
(yang sering dilakukan pemula sebelum proyek mereka menjadi begitu besar untuk membangun file .o).Dideklarasikan tetapi tidak mendefinisikan variabel atau fungsi.
Deklarasi variabel tipikal adalah
Karena ini hanya deklarasi, diperlukan satu definisi . Definisi yang sesuai adalah:
Misalnya, berikut ini akan menghasilkan kesalahan:
Komentar serupa berlaku untuk fungsi. Mendeklarasikan fungsi tanpa mendefinisikannya menyebabkan kesalahan:
Hati-hati bahwa fungsi yang Anda implementasikan sama persis dengan yang Anda nyatakan. Misalnya, Anda mungkin memiliki cv-kualifikasi yang tidak cocok:
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.
sumber
#includes
tidak ditambahkan ke direktori sumber juga termasuk dalam kategori definisi yang hilang.Urutan di mana pustaka terkait yang saling tergantung ditentukan salah.
Urutan pustaka yang ditautkan tidak masalah jika pustaka bergantung satu sama lain. Secara umum, jika pustaka
A
tergantung pada pustakaB
, makalibA
HARUS muncul sebelumnyalibB
di bendera tautan.Sebagai contoh:
Buat perpustakaan:
Menyusun:
Jadi untuk mengulangi lagi, order TIDAK peduli!
sumber
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".
Misalnya kita punya beberapa kode
dan
Buat file objek
Setelah fase assembler, kami memiliki file objek, yang berisi simbol apa pun untuk diekspor. Lihatlah simbolnya
Saya telah menolak beberapa baris dari keluaran, karena itu tidak masalah
Jadi, kita lihat ikuti simbol untuk diekspor.
src2.cpp tidak mengekspor apa pun dan kami belum melihat simbolnya
Tautkan file objek kami
dan jalankan
Linker melihat simbol yang diekspor dan menautkannya. Sekarang kami mencoba untuk menghapus komentar pada baris di src2.cpp seperti di sini
dan membangun kembali file objek
OK (tidak ada kesalahan), karena kami hanya membangun file objek, menghubungkan belum dilakukan. Coba tautkan
Itu terjadi karena local_var_name kami statis, yaitu tidak terlihat untuk modul lain. Sekarang lebih dalam. Dapatkan output fase terjemahan
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
untuk
yaitu Anda harus memiliki seperti di bawah ini
kami telah mengubah visibilitas local_var_name dan menetapkan nilainya menjadi 456789. Cobalah untuk membangun file objek darinya
ok, lihat output readelf (simbol)
sekarang local_var_name memiliki Bind GLOBAL (tadinya LOCAL)
tautan
dan jalankan
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.
sumber
Simbol didefinisikan dalam program C dan digunakan dalam kode C ++.
Fungsi (atau variabel)
void foo()
didefinisikan dalam program C dan Anda mencoba menggunakannya dalam program C ++:Linker C ++ mengharapkan nama-nama akan di-mangble, jadi Anda harus mendeklarasikan fungsinya sebagai:
Secara ekuivalen, alih-alih didefinisikan dalam program C, fungsi (atau variabel)
void foo()
didefinisikan dalam C ++ tetapi dengan tautan C: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;
sumber
#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.
sumber
Mengimpor / mengekspor metode / kelas secara salah di seluruh modul / dll (khusus kompiler).
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:
Makro
THIS_MODULE
hanya akan didefinisikan dalam modul yang mengekspor fungsi. Dengan begitu, deklarasi:meluas ke
dan memberitahu kompiler untuk mengekspor fungsi, karena modul saat ini berisi definisinya. Ketika menyertakan deklarasi dalam modul yang berbeda, itu akan diperluas ke
dan memberi tahu kompiler bahwa definisi ada di salah satu perpustakaan yang Anda tautkan (juga lihat 1) ).
Anda dapat similary kelas impor / ekspor:
sumber
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:
Di b.cpp kami ingin memanggil fungsi foo, jadi kami menambahkan
untuk mendeklarasikan function foo (), dan memanggilnya di badan fungsi lain, katakan
bar()
: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.sumber
Implementasi template tidak terlihat.
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: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 .
sumber
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 .
sumber
WinMain
. Program C ++ yang valid perlu amain
.Juga jika Anda menggunakan perpustakaan pihak ke-3 pastikan Anda memiliki binari 32/64 bit yang benar
sumber
Microsoft menawarkan
#pragma
referensi perpustakaan yang benar pada waktu tautan;Selain jalur perpustakaan termasuk direktori perpustakaan, ini harus menjadi nama lengkap perpustakaan.
sumber
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 menemukanpackagename\build\native\packagename.targets
dan di dalam file itu, menyalin semuav110
bagian. Saya mengubahv110
kev120
dalam bidang kondisi hanya sangat berhati-hati untuk meninggalkan semua path nama filev110
. Ini hanya memungkinkan Visual Studio 2013 untuk menautkan ke perpustakaan untuk 2012, dan dalam hal ini, berhasil.sumber
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 dikompilasisumber
Bug dalam kompiler / IDE
Saya 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:
sumber
Gunakan tautan untuk membantu mendiagnosis kesalahan
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 .sumber
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.
sumber
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.
Jadi jika Anda tiba-tiba mendapatkan kesalahan linker ketika beralih ke GCC setelah 5.1.0 ini akan menjadi hal yang patut untuk dilihat.
sumber
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:
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
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
sumber
Tautan Anda menggunakan pustaka sebelum file objek yang merujuknya
libfoo
tergantunglibbar
, maka keterkaitan Anda dengan benar diletakkanlibfoo
sebelumnyalibbar
.undefined reference to
sesuatu .#include
dan sebenarnya didefinisikan di perpustakaan yang Anda tautkan.Contohnya dalam C. Mereka juga bisa menjadi C ++
Contoh minimal yang melibatkan perpustakaan statis yang Anda buat sendiri
my_lib.c
my_lib.h
eg1.c
Anda membangun perpustakaan statis Anda:
Anda mengkompilasi program Anda:
Anda mencoba menautkannya dengan
libmy_lib.a
dan gagal:Hasil yang sama jika Anda mengkompilasi dan menautkan dalam satu langkah, seperti:
Contoh minimal yang melibatkan pustaka sistem bersama, pustaka kompresi
libz
eg2.c
Kompilasi program Anda:
Cobalah menautkan program Anda dengan
libz
dan gagal:Sama jika Anda mengompilasi dan menautkan dalam sekali jalan:
Dan variasi pada contoh 2 yang melibatkan
pkg-config
:Apa yang kamu lakukan salah
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:
Keberhasilan:
Tautkan contoh 2 dengan benar:
Keberhasilan:
Tautkan
pkg-config
variasi 2 contoh dengan benar:Penjelasan
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 statis
my_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 dalamnyalibmy_lib.a
.Hanya ada file objek di
libmy_lib.a
, yaitumy_lib.o
, dan hanya ada satu hal yang didefinisikanmy_lib.o
, yaitu fungsihw
.Linker akan memutuskan bahwa program Anda perlu
my_lib.o
jika dan hanya jika sudah tahu bahwa program Anda mengacu padahw
, dalam satu atau lebih file objek yang telah ditambahkan ke program, dan bahwa tidak ada file objek yang telah ditambahkan mengandung definisi untukhw
.Jika itu benar, maka tautan akan mengekstrak salinan dari
my_lib.o
perpustakaan dan menambahkannya ke program Anda. Kemudian, program anda berisi definisi untukhw
, jadi referensi untukhw
yang diselesaikan .Ketika Anda mencoba menautkan program seperti:
tautan belum ditambahkan
eg1.o
ke program saat dilihat-lmy_lib
. Karena pada saat itu, belum terlihateg1.o
. Program Anda belum membuat referensi apa punhw
: ia belum membuat referensi sama sekali , karena semua referensi yang dibuatnya ada di dalamnyaeg1.o
.Jadi linker tidak menambah
my_lib.o
program dan tidak digunakan untuk selanjutnyalibmy_lib.a
.Selanjutnya, ia menemukan
eg1.o
, dan menambahkannya menjadi program. File objek dalam urutan tautan selalu ditambahkan ke program. Sekarang, program membuat referensi kehw
, dan tidak mengandung definisihw
; tetapi tidak ada yang tersisa dalam urutan tautan yang dapat memberikan definisi yang hilang. Referensi untukhw
berakhir tidak terselesaikan , dan hubungan gagal.Kedua, contoh 2 , dengan perpustakaan bersama
libz
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 salinanlibz
dalam proses yang sama.Program Anda ingin merujuk hanya satu hal yang memiliki definisi yang diekspor oleh
libz
, yaitu fungsizlibVersion
, yang disebut hanya sekali, dieg2.c
. Jika tautan menambahkan referensi itu ke program Anda, dan kemudian menemukan definisi yang diekspor olehlibz
, referensi diselesaikanTetapi ketika Anda mencoba menautkan program seperti:
urutan kejadian salah dengan cara yang sama seperti contoh 1. Pada saat tautan ditemukan
-lz
, tidak ada referensi apa pun dalam program: semuanya adaeg2.o
, yang belum terlihat. Jadi linker memutuskan tidak ada gunanyalibz
. Ketika mencapaieg2.o
, menambahkannya ke program, dan kemudian memiliki referensi yang tidak ditentukanzlibVersion
, 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:menjadi:
yang hanya contoh 2 lagi.
Saya dapat mereproduksi masalah dalam contoh 1, tetapi tidak dalam contoh 2
Tautan:
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.
Mengapa saya masih mendapatkan masalah ini meskipun saya mengkompilasi-dan-tautan pada saat yang sama?
Jika saya lakukan saja:
tentunya gcc harus melakukan kompilasi
eg1.c
terlebih dahulu, dan kemudian menautkan file objek yang dihasilkan dengannyalibmy_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: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:dari pada:
Lihat juga
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.
sumber
Berteman dengan templat ...
Diberikan potongan kode jenis templat dengan operator teman (atau fungsi);
The
operator<<
sedang dinyatakan sebagai fungsi non-template. Untuk setiap jenis yangT
digunakanFoo
, perlu ada non-templatedoperator<<
. Misalnya, jika ada tipe yangFoo<int>
dideklarasikan, maka harus ada implementasi operator sebagai berikut;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;Kode di atas membatasi persahabatan operator dengan instantiasi yang sesuai
Foo
, yaituoperator<< <int>
instantiasi terbatas untuk mengakses anggota pribadi dari instantiasiFoo<int>
.Alternatif termasuk;
Mengizinkan pertemanan meluas ke semua instansiasi templat, sebagai berikut;
Atau, implementasi untuk
operator<<
dapat dilakukan inline di dalam definisi kelas;Catatan , ketika pernyataan operator (atau fungsi) hanya muncul di kelas, nama tidak tersedia untuk pencarian "normal", hanya untuk pencarian bergantung argumen, dari cppreference ;
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
sumber
Ketika jalur sertakan Anda berbeda
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:
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 menggunakanheader2.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.
Elaborasi lebih lanjut dengan sebuah contoh
Saya punya dua proyek:
graphics.lib
danmain.exe
. Kedua proyek bergantungcommon_math.h
. Misalkan perpustakaan mengekspor fungsi berikut:Dan kemudian Anda pergi ke depan dan memasukkan perpustakaan ke dalam proyek Anda sendiri.
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).Mendebug tautan
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!
sumber
UNICODE
Definisi yang tidak konsistenWindows UNICODE build dibuat dengan
TCHAR
dll. Didefinisikan sebagaiwchar_t
dll. Ketika tidak membangun denganUNICODE
didefinisikan sebagai build denganTCHAR
didefinisikan sebagaichar
dll. IniUNICODE
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 manaUNICODE
tidak didefinisikan akan menghasilkan kesalahan penghubung karena akan ada ketidakcocokan dalam definisiTCHAR
;char
vs.wchar_t
.Kesalahan biasanya mencakup fungsi nilai dengan tipe
char
atauwchar_t
turunan, ini bisa termasukstd::basic_string<>
dll juga. Saat menelusuri fungsi yang terpengaruh dalam kode, sering kali akan ada referensi keTCHAR
ataustd::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;
Atau dalam pengaturan proyek;
Atau di baris perintah;
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".
sumber
Bersihkan dan bangun kembali
"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).
sumber
"Extern" tidak ada dalam
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 implisitextern
(yaitu ketikastatic
kata kunci hilang).Contoh:
benar adalah dengan menggunakan file header dan memasukkannya ke file2.cpp dan file1.cpp
Atau orang dapat mendeklarasikan
const
variabel dalam file1.cpp dengan eksplisitextern
sumber
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 .
Versi perpustakaan yang berbeda
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: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.oDengan cara ini setiap fungsi yang terkandung dalam file.o dan digunakan dalam main.o yang diperlukan
path_t
memberikan kesalahan "undefined reference" karena main.o merujukstd::filesystem::path
tetapi file.o kestd::experimental::filesystem::path
.Resolusi
Untuk memperbaikinya, saya hanya perlu mengubah <eksperimental :: filesystem> di file.h ke <filesystem> .
sumber
Saat menautkan ke pustaka bersama, pastikan simbol yang digunakan tidak disembunyikan.
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:
simbol tersembunyi / lokal ditunjukkan oleh
nm
dengan tipe simbol huruf kecil, misalnyat
alih-alih `T untuk bagian kode: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:Yang kira-kira sesuai dengan versi Windows / MSVC:
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 olehnm
) 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:
sumber
nm -CD
ataunm -gCD
untuk melihat simbol eksternal. Lihat juga Visibilitas di wiki GCC.Arsitektur yang berbeda
Anda mungkin melihat pesan seperti:
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
bukanlib64
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 dilib64
mana perpustakaan diambil.sumber