Mengapa urutan pustaka yang ditautkan terkadang menyebabkan kesalahan dalam GCC?

449

Mengapa urutan pustaka yang ditautkan terkadang menyebabkan kesalahan dalam GCC?

Mendarat di
sumber
Lihat juga sekarang stackoverflow.com/questions/7826448/… - TLDR gccberubah menjadi perilaku yang lebih ketat (relatif) baru-baru ini.
tripleee

Jawaban:

558

(Lihat sejarah pada jawaban ini untuk mendapatkan teks yang lebih rumit, tapi sekarang saya pikir lebih mudah bagi pembaca untuk melihat baris perintah yang sebenarnya).


File umum dibagikan oleh semua perintah di bawah ini

$ cat a.cpp
extern int a;
int main() {
  return a;
}

$ cat b.cpp
extern int b;
int a = b;

$ cat d.cpp
int b;

Menautkan ke perpustakaan statis

$ g++ -c b.cpp -o b.o
$ ar cr libb.a b.o
$ g++ -c d.cpp -o d.o
$ ar cr libd.a d.o

$ g++ -L. -ld -lb a.cpp # wrong order
$ g++ -L. -lb -ld a.cpp # wrong order
$ g++ a.cpp -L. -ld -lb # wrong order
$ g++ a.cpp -L. -lb -ld # right order

Linker mencari dari kiri ke kanan, dan mencatat simbol yang belum terselesaikan. Jika pustaka menyelesaikan simbol, dibutuhkan file objek pustaka itu untuk menyelesaikan simbol (bo dari libb.a dalam kasus ini).

Ketergantungan perpustakaan statis terhadap satu sama lain bekerja sama - perpustakaan yang membutuhkan simbol harus terlebih dahulu, kemudian perpustakaan yang menyelesaikan simbol.

Jika pustaka statis tergantung pada pustaka lain, tetapi pustaka lain lagi tergantung pada pustaka sebelumnya, ada siklus. Anda dapat menyelesaikan ini dengan melampirkan pustaka yang bergantung secara siklis oleh -(dan -), seperti -( -la -lb -)(Anda mungkin perlu keluar dari parens, seperti -\(dan -\)). Linker kemudian mencari lib yang tertutup itu beberapa kali untuk memastikan ketergantungan bersepeda diselesaikan. Atau, Anda dapat menentukan perpustakaan beberapa kali, sehingga masing-masing sebelum satu sama lain: -la -lb -la.

Menautkan ke perpustakaan dinamis

$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -L. -ld -o libb.so # specifies its dependency!

$ g++ -L. -lb a.cpp # wrong order (works on some distributions)
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong order
$ g++ -Wl,--as-needed a.cpp -L. -lb # right order

Ini sama di sini - perpustakaan harus mengikuti file objek program. Perbedaannya di sini dibandingkan dengan perpustakaan statis adalah bahwa Anda tidak perlu peduli tentang dependensi perpustakaan terhadap satu sama lain, karena perpustakaan dinamis memilah dependensi mereka sendiri .

Beberapa distribusi terbaru rupanya default untuk menggunakan --as-neededflag linker, yang memberlakukan bahwa file objek program datang sebelum perpustakaan dinamis. Jika bendera itu dilewati, penghubung tidak akan menautkan ke perpustakaan yang sebenarnya tidak dibutuhkan oleh yang dapat dieksekusi (dan mendeteksi ini dari kiri ke kanan). Distribusi archlinux saya yang terbaru tidak menggunakan flag ini secara default, jadi itu tidak memberikan kesalahan karena tidak mengikuti urutan yang benar.

Tidak benar untuk menghilangkan ketergantungan b.soterhadap d.sosaat membuat yang pertama. Anda akan diminta untuk menentukan pustaka saat menautkan aitu, tetapi atidak benar-benar membutuhkan integer bitu sendiri, jadi itu tidak harus dibuat untuk peduli tentang bdependensi sendiri.

Berikut adalah contoh implikasinya jika Anda lupa menentukan dependensinya libb.so

$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -o libb.so # wrong (but links)

$ g++ -L. -lb a.cpp # wrong, as above
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong, as above
$ g++ a.cpp -L. -lb # wrong, missing libd.so
$ g++ a.cpp -L. -ld -lb # wrong order (works on some distributions)
$ g++ -Wl,--as-needed a.cpp -L. -ld -lb # wrong order (like static libs)
$ g++ -Wl,--as-needed a.cpp -L. -lb -ld # "right"

Jika sekarang Anda melihat dependensi apa yang dimiliki biner, Anda perhatikan biner itu sendiri juga tergantung libd, tidak libbseperti yang seharusnya. Biner perlu ditaut ulang jika libbnanti tergantung pada pustaka lain, jika Anda melakukannya dengan cara ini. Dan jika orang lain memuat libbmenggunakan dlopensaat runtime (pikirkan memuat plugin secara dinamis), panggilan akan gagal juga. Jadi "right"harus benar - benar wrongjuga.

Johannes Schaub - litb
sumber
10
Ulangi sampai semua simbol diselesaikan, eh - Anda akan berpikir mereka bisa mengelola semacam topologi. LLVM memiliki 78 perpustakaan statis sendiri, dengan dependensi siapa-tahu-apa. Benar itu juga memiliki skrip untuk mengetahui opsi kompilasi / tautan - tetapi Anda tidak dapat menggunakannya dalam semua keadaan.
Steve314
6
@Steve itulah yang lorder+ program tsortlakukan. Tetapi kadang-kadang tidak ada pesanan, jika Anda memiliki referensi siklik. Maka Anda hanya perlu menelusuri daftar pustaka sampai semuanya terselesaikan.
Johannes Schaub - litb
10
@Johannes - Tentukan komponen maksimal yang terhubung kuat (misalnya algoritma Tarjans) kemudian secara topologis mengurutkan (secara inheren non-siklik) komponen. Setiap komponen dapat diperlakukan sebagai satu pustaka - jika ada satu pustaka dari komponen yang diperlukan, siklus ketergantungan akan menyebabkan semua pustaka dalam komponen itu diperlukan. Jadi tidak, benar-benar tidak perlu untuk menggilir semua perpustakaan untuk menyelesaikan semuanya, dan tidak perlu untuk opsi baris perintah yang canggung - satu metode menggunakan dua algoritma terkenal dapat menangani semua kasus dengan benar.
Steve314
4
Saya ingin menambahkan satu detail penting ke jawaban yang luar biasa ini: Menggunakan "- (arsip -)" atau "- mulai arsip grup - akhir-kelompok" adalah satu-satunya cara pasti untuk menyelesaikan dependensi melingkar , karena setiap kali tautan mengunjungi arsip, ia menarik (dan mendaftarkan simbol yang belum terselesaikan) hanya file objek yang menyelesaikan simbol yang saat ini belum terselesaikan . Karena itu, algoritma CMake untuk mengulangi komponen yang terhubung dalam grafik dependensi terkadang gagal. (Lihat juga posting blog Ian Lance Taylor yang bagus tentang tautan untuk detail lebih lanjut.)
jorgen
3
Jawaban Anda membantu saya menyelesaikan kesalahan penautan saya dan Anda telah dengan sangat jelas menjelaskan BAGAIMANA untuk menghindari masalah, tetapi apakah Anda tahu MENGAPA dirancang untuk bekerja dengan cara ini?
Anton Daneyko
102

GNU ld linker adalah yang disebut smart linker. Ini akan melacak fungsi yang digunakan oleh perpustakaan statis sebelumnya, secara permanen membuang fungsi-fungsi yang tidak digunakan dari tabel pencariannya. Hasilnya adalah jika Anda menautkan pustaka statis terlalu dini, maka fungsi di pustaka itu tidak lagi tersedia untuk pustaka statis nanti di baris tautan.

Tautan UNIX yang umum berfungsi dari kiri ke kanan, jadi letakkan semua pustaka Anda yang tergantung di sebelah kiri, dan pustaka yang memenuhi dependensi tersebut di sebelah kanan garis tautan. Anda mungkin menemukan bahwa beberapa perpustakaan bergantung pada yang lain sementara pada saat yang sama perpustakaan lain bergantung pada mereka. Di sinilah semakin rumit. Ketika datang ke referensi melingkar, perbaiki kode Anda!

casualcoder
sumber
2
Apakah ini sesuatu dengan hanya gnu ld / gcc? Atau apakah ini sesuatu yang umum dengan tautan?
Mike
2
Tampaknya lebih banyak kompiler Unix yang memiliki masalah serupa. MSVC tidak sepenuhnya bebas dari masalah ini, eiher, tetapi mereka tampaknya tidak seburuk itu.
MSalters
4
Alat MS dev cenderung tidak menunjukkan masalah ini sebanyak karena jika Anda menggunakan rantai alat semua-MS itu akhirnya mengatur urutan linker dengan benar, dan Anda tidak pernah melihat masalah.
Michael Kohne
16
MSVC linker kurang sensitif terhadap masalah ini karena akan mencari semua pustaka untuk simbol yang tidak direferensikan. Urutan perpustakaan masih dapat mempengaruhi simbol mana yang diselesaikan jika lebih dari satu perpustakaan memiliki simbol. Dari MSDN: "Perpustakaan juga dicari dalam urutan baris perintah, dengan peringatan berikut: Simbol yang tidak terselesaikan ketika membawa file objek dari perpustakaan dicari di perpustakaan itu terlebih dahulu, dan kemudian perpustakaan berikut dari baris perintah dan / DEFAULTLIB (Tentukan Perpustakaan Default) arahan, dan kemudian ke perpustakaan mana saja di awal baris perintah "
Michael Burr
4
"... smart linker ..." - Saya percaya itu diklasifikasikan sebagai "single pass" linker, bukan "smart linker".
jww
54

Berikut adalah contoh untuk memperjelas bagaimana hal-hal bekerja dengan GCC ketika perpustakaan statis terlibat. Jadi mari kita asumsikan kita memiliki skenario berikut:

  • myprog.o- mengandung main()fungsi, tergantung padalibmysqlclient
  • libmysqlclient- statis, untuk contoh (Anda lebih suka perpustakaan bersama, tentu saja, karena libmysqlclientsangat besar); di /usr/local/lib; dan tergantung pada hal-hal darilibz
  • libz (dinamis)

Bagaimana kami menautkan ini? (Catatan: contoh dari kompilasi di Cygwin menggunakan gcc 4.3.4)

gcc -L/usr/local/lib -lmysqlclient myprog.o
# undefined reference to `_mysql_init'
# myprog depends on libmysqlclient
# so myprog has to come earlier on the command line

gcc myprog.o -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress'
# we have to link with libz, too

gcc myprog.o -lz -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress'
# libz is needed by libmysqlclient
# so it has to appear *after* it on the command line

gcc myprog.o -L/usr/local/lib -lmysqlclient -lz
# this works
Lumi
sumber
31

Jika Anda menambahkan -Wl,--start-groupke bendera tautan, tidak peduli urutannya atau apakah ada dependensi melingkar.

Pada Qt ini berarti menambahkan:

QMAKE_LFLAGS += -Wl,--start-group

Menghemat banyak waktu untuk bermain-main dan sepertinya tidak memperlambat banyak tautan (yang tentunya memakan waktu jauh lebih sedikit daripada kompilasi).

SvaLopLop
sumber
8

Alternatif lain adalah dengan menentukan daftar perpustakaan dua kali:

gcc prog.o libA.a libB.a libA.a libB.a -o prog.x

Melakukan ini, Anda tidak perlu repot dengan urutan yang benar karena referensi akan diselesaikan di blok kedua.

eek
sumber
5

Anda dapat menggunakan opsi -Xlinker.

g++ -o foobar  -Xlinker -start-group  -Xlinker libA.a -Xlinker libB.a -Xlinker libC.a  -Xlinker -end-group 

hampir sama dengan

g++ -o foobar  -Xlinker -start-group  -Xlinker libC.a -Xlinker libB.a -Xlinker libA.a  -Xlinker -end-group 

Hati-hati!

  1. Urutan dalam suatu kelompok adalah penting! Berikut ini contohnya: perpustakaan debug memiliki rutin debug, tetapi perpustakaan non-debug memiliki versi yang lemah dari yang sama. Anda harus meletakkan perpustakaan debug PERTAMA dalam grup atau Anda akan memutuskan untuk versi non-debug.
  2. Anda harus mendahului setiap perpustakaan dalam daftar grup dengan -Xlinker
yundorri
sumber
5

Kiat cepat yang membuat saya tersandung: jika Anda memanggil tautan sebagai "gcc" atau "g ++", kemudian menggunakan "--start-group" dan "--end-group" tidak akan meneruskan opsi itu ke linker - juga tidak akan menandai kesalahan. Itu hanya akan gagal tautan dengan simbol yang tidak terdefinisi jika Anda salah memesan perpustakaan.

Anda harus menuliskannya sebagai "-Wl, - start-group" dll. Untuk memberi tahu GCC agar meneruskan argumen tersebut ke tautan.

MM
sumber
2

Urutan tautan tentu berpengaruh, setidaknya pada beberapa platform. Saya telah melihat crash untuk aplikasi yang terhubung dengan perpustakaan dalam urutan yang salah (di mana salah berarti A terhubung sebelum B tetapi B tergantung pada A).

David Cournapeau
sumber
2

Saya sudah sering melihat ini, beberapa modul kami terhubung lebih dari 100 pustaka dari kode kami ditambah sistem & lib pihak ke-3.

Bergantung pada tautan yang berbeda, HP / Intel / GCC / SUN / SGI / IBM / dll. Anda bisa mendapatkan fungsi / variabel yang tidak terselesaikan, dll, pada beberapa platform Anda harus membuat daftar pustaka dua kali.

Untuk sebagian besar kami menggunakan hierarki terstruktur dari perpustakaan, inti, platform, berbagai lapisan abstraksi, tetapi untuk beberapa sistem Anda masih harus bermain dengan urutan dalam perintah tautan.

Setelah Anda menemukan dokumen solusi sehingga pengembang berikutnya tidak harus menyelesaikannya lagi.

Dosen lama saya dulu berkata, " kohesi tinggi & kopling rendah ", itu masih berlaku sampai sekarang.

titanae
sumber