Kütüphanelerin bağlanma sırası neden GCC'de hatalara neden oluyor?


Yanıtlar:


558

(Daha ayrıntılı metin almak için bu yanıtın geçmişine bakın, ancak şimdi okuyucunun gerçek komut satırlarını görmesinin daha kolay olduğunu düşünüyorum).


Aşağıdaki komutların tümü tarafından paylaşılan ortak dosyalar

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

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

$ cat d.cpp
int b;

Statik kütüphanelere bağlanma

$ 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

Bağlayıcı soldan sağa doğru arama yapar ve çözülmemiş sembolleri giderken not eder. Bir kütüphane sembolü çözerse, sembolü çözmek için o kütüphanenin nesne dosyalarını alır (bu durumda bo libb.a dışında).

Statik kitaplıkların birbirine karşı bağımlılıkları aynı şekilde çalışır - önce sembollere ihtiyaç duyan kitaplık, daha sonra sembolü çözen kitaplık olmalıdır.

Statik bir kitaplık başka bir kitaplığa bağlıysa, ancak diğer kitaplık yine eski kitaplığa bağlıysa, bir döngü vardır. Sen ederek dönüşümlü olarak bağımlı kitaplıkları içine alarak bu çözebilirsiniz -(ve -)gibi, -( -la -lb -)(siz gibi parens, kaçmak gerekebilir -\(ve -\)). Bağlayıcı daha sonra bisiklet bağımlılıklarının çözüldüğünden emin olmak için ekteki lib'i birkaç kez arar. Her biri diğerinden önce yani Alternatif olarak, kütüphaneler birden fazla kez belirtebilirsiniz: -la -lb -la.

Dinamik kütüphanelere bağlanma

$ 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

Burada da aynı - kütüphaneler programın nesne dosyalarını takip etmelidir. Buradaki statik kütüphanelerle karşılaştırıldığında fark, dinamik kütüphanelerin bağımlılıklarını kendilerinin ayırdığı için kütüphanelerin bağımlılıklarına önem vermemenizdir .

Son zamanlarda yapılan bazı dağıtımlar --as-needed, programın nesne dosyalarının dinamik kitaplıklardan önce gelmesini zorunlu kılan bağlayıcı bayrağını varsayılan olarak kullanıyor . Bu bayrak iletilirse, bağlayıcı yürütülebilir dosya tarafından gerçekten ihtiyaç duyulmayan kitaplıklara bağlanmaz (ve bunu soldan sağa algılar). Son archlinux dağıtımım varsayılan olarak bu bayrağı kullanmıyor, bu nedenle doğru sırayı izlemediği için hata vermedi.

Birincisini oluştururken b.sokarşı bağımlılığı atlamak doğru değildir d.so. aO zaman bağlantı kurarken kütüphaneyi belirtmeniz istenir , ancak atamsayıya gerçekten ihtiyaç duymaz b, bu nedenle bkendi bağımlılıklarına dikkat edilmemelidir .

İşte için bağımlılıkları belirtmeyi kaçırırsanız bunun etkilerine bir örnek 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"

Şimdi ikilinin sahip olduğu bağımlılıklara bakarsanız, ikilinin kendisinin de libdsadece libbolması gerektiği gibi değil de bağlı olduğuna dikkat edin . Daha libbsonra başka bir kütüphaneye bağlıysa, bu şekilde yaparsanız ikili dosyanın yeniden bağlanması gerekir . Ve başka biri çalışma zamanında libbkullanarak dlopenyüklerse (eklentileri dinamik olarak yüklemeyi düşünün), çağrı da başarısız olur. Yani "right"gerçekten de bir olmalı wrong.


10
Tüm semboller çözülene kadar tekrarlayın, ha - topolojik bir tür yönetebileceklerini düşünürdünüz. LLVM'nin kendi başına 78 statik kütüphanesi vardır, kim bilir ne bağımlılıkları vardır. Derleme / bağlantı seçeneklerini anlamak için bir komut dosyasına da sahip olduğu doğru - ancak bunu her koşulda kullanamazsınız.
Steve314

6
@Steve programlar lorder+ tsortbunu yapar. Ancak, döngüsel referanslarınız varsa bazen düzen yoktur. Sonra her şey çözülene kadar kütüphaneler listesinde dolaşmak zorundasınız.
Johannes Schaub - litb

10
@Johannes - Güçlü şekilde bağlı maksimum bileşenleri (örneğin Tarjans algoritması) belirleyin, ardından bileşenlerin (doğal olarak döngüsel olmayan) digrafisini topolojik olarak sıralayın. Her bileşen tek bir kitaplık olarak değerlendirilebilir - bileşenden herhangi bir kitaplık gerekiyorsa, bağımlılık döngü (ler) i bu bileşendeki tüm kitaplıkların gerekli olmasına neden olur. Bu nedenle, hayır, her şeyi çözmek için gerçekten tüm kütüphanelerde dolaşmaya ve garip komut satırı seçeneklerine gerek yoktur - iyi bilinen iki algoritmayı kullanan bir yöntem tüm durumları doğru bir şekilde işleyebilir.
Steve314

4
"--Start-grup arşivleri --end-grubu" "- - (arşivleri)" veya kullanma: Ben bu mükemmel cevaba önemli bir ayrıntı eklemek istiyorum dairesel bağımlılıkları ortadan kalkması için tek kesin yolu olan her zamandan beri, bağlayıcı bir arşivi ziyaret eder, yalnızca o anda çözülmemiş sembolleri çözen nesne dosyalarını çeker (ve çözülmemiş sembollerini kaydeder) . Bu nedenle, CMake'in bağımlılık grafiğindeki bağlı bileşenleri tekrarlama algoritması zaman zaman başarısız olabilir. (Ayrıca ayrıntılar için Ian Lance Taylor'ın
linkler hakkındaki

3
Cevabınız bağlantı hatalarımı çözmeme yardımcı oldu ve başınıza beladan kaçınmak için NASIL açık bir şekilde açıkladınız, ancak neden bu şekilde çalışmak için tasarlandığına dair bir fikrin var mı?
Anton Daneyko

102

GNU ld bağlayıcı, akıllı bağlayıcı olarak adlandırılır. Önceki statik kitaplıkların kullandığı işlevleri takip eder ve arama tablolarından kullanılmayan işlevleri kalıcı olarak atar. Sonuç olarak, statik bir kitaplığı çok erken bağlarsanız, o kitaplıktaki işlevler artık bağlantı satırındaki statik kitaplıklar tarafından kullanılamaz.

Tipik UNIX bağlayıcısı soldan sağa doğru çalışır, bu nedenle tüm bağımlı kitaplıklarınızı solda ve bu bağımlılıkları karşılayanlar da bağlantı satırının sağında bulunur. Bazı kütüphanelerin diğerlerine, diğer kütüphanelerin de onlara bağlı olduğunu görebilirsiniz. Burası karmaşıklaşıyor. Dairesel referanslar söz konusu olduğunda, kodunuzu düzeltin!


2
Bu sadece gnu ld / gcc ile mi? Yoksa bu bağlayıcılarla ortak bir şey mi?
Mike

2
Görünüşe göre daha fazla Unix derleyicisinin benzer sorunları var. MSVC bu sorunlardan tamamen bağımsız değil, ama o kadar da kötü görünmüyor.
MSalters

4
MS dev araçları bu sorunları gösterme eğilimi göstermez, çünkü bir all-MS takım zinciri kullanırsanız, bağlayıcı siparişini doğru şekilde ayarlar ve sorunu asla fark etmezsiniz.
Michael Kohne

16
MSVC bağlayıcısı bu soruna daha az duyarlıdır, çünkü tüm kitaplıklarda başvurulmamış bir sembol arar. Birden fazla kitaplıkta sembolü varsa kitaplık sırası hala hangi sembolün çözüleceğini etkileyebilir . MSDN'den: "Kütüphaneler komut satırında da aşağıdaki uyarı ile aranır: Bir kütüphaneden bir nesne dosyası getirilirken çözülmeyen semboller önce o kütüphanede aranır, sonra komut satırından aşağıdaki kütüphaneler aranır ve / DEFAULTLIB (Varsayılan Kitaplığı Belirt) yönergeleri ve sonra komut satırının başındaki tüm kitaplıklara "
Michael Burr

4
"... akıllı bağlayıcı ..." - Ben bir "akıllı bağlayıcı" olarak değil, "tek geçişli" bağlayıcı olarak sınıflandırıldığını düşünüyorum.
jww

54

Statik kütüphaneler söz konusu olduğunda işlerin GCC ile nasıl çalıştığını netleştirmek için bir örnek . Diyelim ki aşağıdaki senaryoya sahibiz:

  • myprog.o- içeren main()fonksiyon, bağımlılibmysqlclient
  • libmysqlclient- statik, örneğin uğruna (paylaşılan kütüphaneyi, elbette, libmysqlclientçok büyük olduğu için tercih edersiniz ); içinde /usr/local/lib; ve gelen şeylere bağımlılibz
  • libz (dinamik)

Bunu nasıl bağlarız? (Not: gcc 4.3.4 kullanarak Cygwin üzerinde derleme örnekleri)

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

31

-Wl,--start-groupBağlayıcı bayraklarına eklerseniz , hangi sırayla olduklarını veya dairesel bağımlılıklar olup olmadığını umursamazlar.

Qt'de bu şu anlama gelir:

QMAKE_LFLAGS += -Wl,--start-group

Dağınık zaman yükler kaydeder ve çok (yavaşça derleme çok daha az zaman alır) bağlantı yavaşlatmak gibi görünmüyor.


8

Başka bir alternatif, kütüphane listesini iki kez belirtmektir:

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

Bunu yaptığınızda, referans ikinci satırda çözüleceğinden doğru sıra ile uğraşmanıza gerek yoktur.


5

-Xlinker seçeneğini kullanabilirsiniz.

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

ALMOST eşittir

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

Dikkatli ol!

  1. Bir grup içindeki düzen önemlidir! İşte bir örnek: hata ayıklama kitaplığı hata ayıklama yordamına sahiptir, ancak hata ayıklama olmayan kitaplığın aynı zayıf sürümü vardır. Hata ayıklama kitaplığını gruba FIRST koymanız gerekir, aksi takdirde hata ayıklama olmayan sürüme çözümlenir.
  2. Grup listesindeki her kitaplığın önüne -Xlinker koymanız gerekir

5

Beni harekete geçiren hızlı bir ipucu: Eğer bağlayıcıyı "gcc" veya "g ++" olarak çağırıyorsanız, "--start-group" ve "--end-group" komutlarını kullanmak bu seçenekleri linker - bir hata işaretlemez. Kitaplık siparişi yanlışsa, tanımsız sembollerle bağlantı başarısız olur.

GCC'ye argümanı bağlayıcıya geçirmesini bildirmek için bunları "-Wl, - start-group" vb. Olarak yazmanız gerekir.


2

En azından bazı platformlarda bağlantı sırası kesinlikle önemlidir. Yanlış sırada kütüphaneler ile bağlantılı uygulamalar için çökmeler gördüm (burada yanlış anlamına gelir B önce B bağlı ama B A bağlıdır).


2

Bunu çok gördüm, bazı modüllerimiz kodumuzun artı kütüphanesi ve 3. parti kütüphanelerinin 100'den fazla kütüphanesine bağlanıyor.

Farklı bağlayıcılara bağlı olarak HP / Intel / GCC / SUN / SGI / IBM / vb. Çözümlenmemiş işlevler / değişkenler vb. Alabilirsiniz, bazı platformlarda kütüphaneleri iki kez listelemeniz gerekir.

Çoğunlukla kütüphanelerin, çekirdeklerin, platformun, farklı soyutlama katmanlarının yapılandırılmış hiyerarşisini kullanıyoruz, ancak bazı sistemler için hala link komutundaki sıra ile oynamak zorundasınız.

Bir çözüm belgesine dokunduğunuzda, bir sonraki geliştiricinin tekrar çalışması gerekmez.

Eski öğretim görevlim " yüksek uyum ve düşük bağlantı " derdi, bugün hala doğru.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.