Tanımlanmamış referans / çözülmemiş harici sembol hatası nedir ve nasıl düzeltebilirim?


1493

Tanımlanmamış referans / çözülmemiş harici sembol hataları nedir? Sık karşılaşılan nedenler nelerdir ve nasıl düzeltilir / önlenir?

Kendinizi düzenlemek / eklemek için çekinmeyin.


@LuchianGrigore 'bir yanıt eklemekte özgürsünüz ' İzin vermek istiyorsanız, ilgili cevabınızı (IMHO) birincil cevabınızı eklemeyi tercih ettim.
πάντα ῥεῖ

19
Bu soru, yararlı bir cevap kabul etmek için çok geneldir. Bu soruyu yorumlayan 1000'den fazla itibarı olan kişinin sayısına sormadan inanamıyorum. Bu arada mantıklı ve benim için çok yararlı olan birçok soru görüyorum.
Albert van der Horst

10
@AlbertvanderHorst "Bu soru yararlı bir cevap kabul etmek için çok genel" Aşağıdaki cevaplar yararlı değil mi?
LF

4
Çok genel olarak işaretlenen sorulara birçok cevap gibi faydalı olabilirler.
Albert van der Horst

1
Dürüst olmak gerekirse, çoğu yeni kullanıcıdan sorduğumuz bir şey olarak minimal tekrarlanabilir örneği görmek istiyorum. Bununla bir şey demek istemiyorum, sadece - insanların kendimize uyguladığımız kurallara uymalarını bekleyemeyiz.
Danilo

Yanıtlar:


849

Bir C ++ programının derlenmesi 2.2 ile belirtildiği gibi birkaç adımda gerçekleşir (referans için Keith Thompson'a kredi) :

Sözdizimi çeviri kuralları arasındaki öncelik aşağıdaki aşamalarla belirlenir [bkz. Dipnot] .

  1. Fiziksel kaynak dosya karakterleri, gerekirse uygulama tarafından tanımlanmış bir şekilde temel kaynak karakter kümesine (satır sonu göstergeleri için yeni satır karakterleri tanıtarak) eşlenir. [SNIR]
  2. Hemen ters eğik çizgi karakterinin (\) hemen ardından yeni satır karakteri silinir ve fiziksel kaynak satırları mantıksal kaynak satırları oluşturmak üzere eklenir. [SNIR]
  3. Kaynak dosya önişleme belirteçlerine (2.5) ve boşluk karakter dizilerine (yorumlar dahil) ayrıştırılır. [SNIR]
  4. Önişlem direktifleri yürütülür, makro çağrıları genişletilir ve _Pragma tekli operatör ifadeleri yürütülür. [SNIR]
  5. Bir karakter değişmezindeki veya bir dize değişmezindeki her bir kaynak karakter kümesi üyesi, ayrıca bir karakter değişmezindeki veya ham olmayan bir dize değişmezindeki her kaçış dizisi ve evrensel karakter adı, yürütme karakter kümesinin karşılık gelen üyesine dönüştürülür; [SNIR]
  6. Bitişik dize değişmez belirteçleri birleştirilir.
  7. Jetonları ayıran boşluk karakterleri artık önemli değil. Her önişleme belirteci bir simgeye dönüştürülür. (2.7). Ortaya çıkan belirteçler sözdizimsel ve anlamsal olarak analiz edilir ve bir çeviri birimi olarak çevrilir. [SNIR]
  8. Çevrilmiş çeviri birimleri ve örnekleme birimleri aşağıdaki gibi birleştirilir: [SNIP]
  9. Tüm harici varlık referansları çözüldü. Kütüphane bileşenleri, geçerli çeviride tanımlanmayan varlıklara dış referansları karşılamak için bağlanır. Tüm bu çevirmen çıktıları, yürütme ortamında yürütmek için gereken bilgileri içeren bir program görüntüsünde toplanır. (benimkini vurgula)

[dipnot] Uygulamalar, farklı aşamalar birlikte katlanabilse de, bu ayrı aşamalar meydana gelmiş gibi davranmalıdır.

Belirtilen hatalar, derlemenin bu son aşamasında ortaya çıkar ve bunlar genellikle bağlantı olarak adlandırılır. Temel olarak, bir dizi uygulama dosyasını nesne dosyalarına veya kitaplıklarına derlediğiniz anlamına gelir ve şimdi bunların birlikte çalışmasını istiyorsunuz.

Eğer sembolü tanımlı Say aiçinde a.cpp. Şimdi bu sembolü b.cpp ilan etti ve kullandı. Bağlamadan önce, bu sembolün bir yerde tanımlandığını varsayar , ancak henüz nerede olduğu umurumda değildir. Bağlama aşaması, sembolü bulmak ve doğru bir şekilde bağlamaktan sorumludur b.cpp(aslında, onu kullanan nesneye veya kütüphaneye).

Microsoft Visual Studio kullanıyorsanız, projelerin .libdosya oluşturduğunu göreceksiniz . Bunlar, dışa aktarılan sembollerin bir tablosunu ve içe aktarılan sembollerin bir tablosunu içerir. İçe aktarılan semboller, bağlandığınız kitaplıklara göre çözümlenir ve dışa aktarılan semboller, .lib(varsa) kullanan kitaplıklar için sağlanır .

Diğer derleyiciler / platformlar için benzer mekanizmalar mevcuttur.

Yaygın hata mesajları error LNK2001, error LNK1120, error LNK2019için Microsoft Visual Studio ve undefined reference to symbolName için GCC .

Kod:

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;
}

GCC ile aşağıdaki hataları oluşturur :

/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

Visual Studio ile benzer ve benzer hatalar :

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

Yaygın nedenler şunlardır:


16
Şahsen, MS linker hata mesajlarının GCC hataları kadar okunabilir olduğunu düşünüyorum. Ayrıca, çözülemeyen dış için hem karıştırılmış hem de yönetilmeyen adları dahil etme avantajına sahiptirler. Sorunun ne olabileceğini görmek için kitaplıklara veya nesne dosyalarına doğrudan bakmanız gerektiğinde, karıştırılmış adın bulunması yararlı olabilir (örneğin, bir çağrı kuralı uyuşmazlığı). Ayrıca, hangi MSVC sürümünün buradaki hataları ürettiğinden emin değilim, ancak daha yeni sürümler, çözülmemiş harici simgeye atıfta bulunan işlevin adını (hem karıştırılmış hem de yönetilmemiş) içerir.
Michael Burr

5
David Drysdale , bağlayıcıların nasıl çalıştığı hakkında harika bir makale yazdı: Yeni Başlayanlar için Linkers Kılavuzu . Bu sorunun konusu göz önüne alındığında, yararlı olabileceğini düşündüm.
Pressacco

@TankorSmash gcc kullanılsın mı? MinGW daha kesin olmak gerekirse.
doug65536

1
@luchian Yukarıdaki hataları düzelterek doğru eklerseniz iyi olur
Phalgun

178

Sınıf üyeleri:

Saf virtualyıkıcı bir uygulamaya ihtiyaç duyar.

Bir yıkıcı saf ilan etmek hala onu tanımlamanızı gerektirir (normal bir fonksiyonun aksine):

struct X
{
    virtual ~X() = 0;
};
struct Y : X
{
    ~Y() {}
};
int main()
{
    Y y;
}
//X::~X(){} //uncomment this line for successful definition

Bunun nedeni, nesne dolaylı olarak yok edildiğinde temel sınıf yıkıcılarının çağrılmasıdır, bu nedenle bir tanım gereklidir.

virtual yöntemler ya uygulanmalı ya da saf olarak tanımlanmalıdır.

Bu, tanımsız virtualyöntemlere benzer , saf bildirimin bir kukla vtable oluşturduğuna ve işlevi kullanmadan bağlayıcı hatasını alabilirsiniz:

struct X
{
    virtual void foo();
};
struct Y : X
{
   void foo() {}
};
int main()
{
   Y y; //linker error although there was no call to X::foo
}

Bunun çalışması için X::foo()saf olarak beyan edin :

struct X
{
    virtual void foo() = 0;
};

Olmayan virtualsınıf üyeleri

Bazı üyeler, açıkça kullanılmasalar bile tanımlanmalıdır:

struct A
{ 
    ~A();
};

Aşağıdakiler hatayı verir:

A a;      //destructor undefined

Uygulama, sınıf tanımının kendisinde satır içi olabilir:

struct A
{ 
    ~A() {}
};

veya dışarıda:

A::~A() {}

Uygulama sınıf tanımının dışında ancak üstbilgide ise, yöntemlerin inlinebirden çok tanımlamayı engelleyecek şekilde işaretlenmesi gerekir .

Kullanılıyorsa, kullanılan tüm üye yöntemlerin tanımlanması gerekir.

Yaygın bir hata, adı nitelemeyi unutmaktır:

struct A
{
   void foo();
};

void foo() {}

int main()
{
   A a;
   a.foo();
}

Tanım

void A::foo() {}

staticveri üyeleri tek bir çeviri biriminde sınıf dışında tanımlanmalıdır :

struct X
{
    static int x;
};
int main()
{
    int x = X::x;
}
//int X::x; //uncomment this line to define X::x

static constSınıf tanımındaki integral veya numaralandırma tipinde bir veri elemanı için bir başlatıcı sağlanabilir ; bununla birlikte, bu üyenin tek kullanımı yukarıda açıklandığı gibi yine de bir ad alanı kapsamı tanımı gerektirecektir. C ++ 11, tüm static constveri üyeleri için sınıf içinde başlatmaya izin verir .


Her ikisini de yapmanın mümkün olduğunu ve dtor'un aslında bir istisna olmadığını vurgulamak isteyebileceğinizi düşündüm. (ilk bakışta ifadelerinizden belli değil.)
Tekilleştirici

112

Uygun kitaplıklara / nesne dosyalarına bağlanma veya uygulama dosyalarını derleme

Genellikle, her çeviri birimi, o çeviri biriminde tanımlanan sembollerin tanımlarını içeren bir nesne dosyası oluşturur. Bu sembolleri kullanmak için, bu nesne dosyalarına bağlanmanız gerekir.

Gcc altında , komut satırında birbirine bağlanacak tüm nesne dosyalarını belirtir veya uygulama dosyalarını birlikte derlersiniz.

g++ -o test objectFile1.o objectFile2.o -lLibraryName

libraryNameBurada platforma özgü eklemeler olmadan, kütüphanenin sadece çıplak adıdır. Yani örneğin Linux'ta kütüphane dosyaları genellikle çağrılır, libfoo.soancak yalnızca yazarsınız -lfoo. Windows'da aynı dosya çağrılabilir foo.lib, ancak aynı argümanı kullanırsınız. Bu dosyaların bulunabileceği dizini eklemeniz gerekebilir -L‹directory›. Sonra boşluk yazmayın emin olun -lveya -L.

For XCode : -> Kütüphane arama yolu ekleyin - Kullanıcı Başlık Arama Yolları ekle> sürükle ve proje klasörüne fiili kütüphane referansını bırakın.

MSVS altında , bir projeye eklenen dosyalar otomatik olarak nesne dosyalarını birbirine bağlar ve bir libdosya oluşturulur (ortak kullanımda). Sembolleri ayrı bir projede kullanmak için libdosyaları proje ayarlarına dahil etmeniz gerekir . Bu, proje özelliklerinin Bağlayıcı bölümünde, içinde yapılır Input -> Additional Dependencies. ( libdosyanın yolu eklenmelidir Linker -> General -> Additional Library Directories) libDosyayla birlikte sağlanan üçüncü taraf bir kitaplık kullanılırken, bunun yapılmaması genellikle hataya neden olur.

Dosyayı derlemeye eklemeyi de unutursanız, bu durumda nesne dosyası oluşturulmaz. Gelen gcc komut satırından dosya eklemek istiyorum. In MSVS projeye dosyayı ekleyerek bu (dosyalar can olsa elle, tek tek yapı dışında bırakılabilir) otomatik olarak derlemek yapacaktır.

Windows programlamasında, gerekli bir kütüphaneyi bağlamadığınızı belirten işaret, çözülmemiş sembolün adının başlamasıdır __imp_. Belgede işlevin adını arayın ve hangi kütüphaneyi kullanmanız gerektiğini belirtmelidir. Örneğin, MSDN, bilgileri "Kütüphane" adlı bir bölümdeki her işlevin altındaki bir kutuya yerleştirir.


1
Bunun gcc main.cyerine gcc main.c other.c (örneğin yeni başlayanlar genellikle .o dosyaları oluşturmak için çok büyük hale gelmeden önce yaptıkları) ortak hatayı açık bir şekilde ele alabilseydiniz iyi olurdu .
MM

101

Bildirildi, ancak bir değişken veya işlev tanımlanmadı.

Tipik bir değişken bildirimi

extern int x;

Bu sadece bir beyan olduğundan, tek bir tanım gereklidir. Buna karşılık gelen bir tanım:

int x;

Örneğin, aşağıdakiler bir hata oluşturur:

extern int x;
int main()
{
    x = 0;
}
//int x; // uncomment this line for successful definition

Benzer açıklamalar fonksiyonlar için de geçerlidir. Bir işlevi tanımlamadan bildirmek hataya yol açar:

void foo(); // declaration only
int main()
{
   foo();
}
//void foo() {} //uncomment this line for successful definition

Uyguladığınız işlevin, bildirdiğiniz işlevle tam olarak eşleştiğine dikkat edin. Örneğin, eşleşmeyen cv niteleyicileriniz olabilir:

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)

Diğer uyumsuzluk örnekleri arasında

  • Bir ad alanında bildirilen, diğerinde tanımlanan işlev / değişken.
  • Sınıf üyesi olarak bildirilen, genel olarak tanımlanan işlev / değişken (veya tersi).
  • İşlev dönüş türü, parametre numarası ve türleri ve çağrı kuralı tam olarak aynı fikirde değil.

Derleyiciden gelen hata iletisi genellikle bildirilen ancak hiçbir zaman tanımlanmayan değişken veya işlevin tam bildirimini verir. Verdiğiniz tanımla yakından karşılaştırın. Her detayın eşleştiğinden emin olun.


VS yılında başlığında bulunanlarla eşleşen cpp dosyaları #includesdeğil eklenen kaynak dizinine de kayıp tanımların kategorisinde yer alır.
Laurie Stearn

86

Birbirine bağlı bağlantılı kitaplıkların belirtilme sırası yanlış.

Kütüphanelerin birbirine bağlanma sırası, kütüphanelerin birbirine bağlı olması durumunda önemlidir. Genel olarak, kütüphane kütüphaneye Abağlıysa B, daha önce bağlayıcı bayraklarında görünmelidir libA ZORUNLUlibB .

Örneğin:

// 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;
};

Kütüphaneleri oluşturun:

$ 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

Derleme:

$ 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

Tekrar tekrar etmek Yani, sipariş YAPAR olsun!


Merak ediyorum, benim durumumda, paylaşılan bir kütüphaneye bağlı bir nesne dosyam var. Ben Makefile değiştirmek ve Debian üzerinde gcc 4.8.4 ile nesne AFTER kütüphane koymak zorunda kaldı . Centos 6.5'de gcc 4.4 ile Makefile sorunsuz çalıştı.
Marco Sulla

74

"tanımlanmamış referans / çözülmemiş harici sembol" nedir

Ben "tanımsız referans / çözülmemiş dış simge" ne olduğunu açıklamaya çalışacağım.

not: g ++ ve Linux kullanıyorum ve tüm örnekler bunun için

Örneğin, bazı kodlarımız var

// 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;
}

ve

// 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);
}

Nesne dosyaları oluşturma

$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o

Montajcı aşamasından sonra, dışa aktarılacak sembolleri içeren bir nesne dosyasına sahibiz. Sembollere bak

$ 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]

Çıktıdan bazı satırları reddettim, çünkü önemli değil

Dolayısıyla, dışa aktarmak için aşağıdaki sembolleri görüyoruz.

[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable

src2.cpp hiçbir şey dışa aktarmaz ve onun sembollerini görmedik

Nesne dosyalarımızı bağlayın

$ g++ src1.o src2.o -o prog

ve çalıştır

$ ./prog
123

Bağlayıcı dışa aktarılan sembolleri görür ve bağlar. Şimdi burada src2.cpp'deki satırları açmaya çalışıyoruz

// 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);
}

ve bir nesne dosyasını yeniden oluşturma

$ g++ -c src2.cpp -o src2.o

Tamam (hata yok), yalnızca nesne dosyası oluşturduğumuz için bağlantı henüz yapılmadı. Bağlantı oluşturmaya çalışın

$ 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

Bu bizim local_var_name statik olduğu için, yani diğer modüller için görünür olmadığı için oldu. Şimdi daha derinden. Çeviri aşaması çıktısını alın

$ 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

Bu yüzden local_var_name için bir etiket olmadığını gördük, bu yüzden linker onu bulamadı. Ama biz hackerız :) ve düzeltebiliriz. Metin düzenleyicinizde src1.s dosyasını açın ve değiştirin

.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4

için

    .globl  local_var_name
    .data
    .align 4
    .type   local_var_name, @object
    .size   local_var_name, 4
local_var_name:
    .long   456789

yani aşağıdaki gibi olmalısın

    .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:
; ...

local_var_name'ın görünürlüğünü değiştirdik ve değerini 456789 olarak ayarladık. Bir nesne dosyası oluşturmaya çalışın

$ g++ -c src1.s -o src2.o

tamam, kendini oku çıkışına bakın (semboller)

$ readelf --symbols src1.o
8: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 local_var_name

artık local_var_name BIND GLOBAL'a sahip (LOCAL)

bağlantı

$ g++ src1.o src2.o -o prog

ve çalıştır

$ ./prog 
123456789

tamam, kesiyoruz :)

Sonuç olarak, bağlayıcı nesne dosyalarında genel sembolleri bulamadığında bir "tanımlanmamış başvuru / çözülmemiş harici sembol hatası" oluşur.


71

Semboller bir C programında tanımlanmış ve C ++ kodunda kullanılmıştır.

İşlev (veya değişken) void foo()bir C programında tanımlandı ve bir C ++ programında kullanmaya çalıştınız:

void foo();
int main()
{
    foo();
}

C ++ bağlayıcısı isimlerin karıştırılmasını beklediğinden, işlevi şu şekilde bildirmeniz gerekir:

extern "C" void foo();
int main()
{
    foo();
}

Eşdeğer olarak, bir C programında tanımlanmak yerine, fonksiyon (veya değişken) void foo()C ++ 'da tanımlanmıştır, ancak C bağlantısıyla:

extern "C" void foo();

ve bunu C ++ bağlantısına sahip bir C ++ programında kullanmaya çalışırsınız.

Tüm kütüphane başlık dosyasına dahil edilmişse (ve C kodu olarak derlenmişse); içerme aşağıdaki gibi olmalıdır;

extern "C" {
    #include "cheader.h"
}

5
Ya da tersine, bir C kütüphanesi geliştirirseniz, hoş bir kural tüm dışa aktarılan bildirimleri çevreleyen #ifdef __cplusplus [\n] extern"C" { [\n] #endifve #ifdef __cplusplus [\n] } [\n] #endif( [\n]gerçek satır başı) olmakla birlikte başlık dosya (lar) ını korumaktır, ancak bunu yorumda düzgün yazamam).
Bentoy13

Yukarıdaki yorumda olduğu gibi, burada 'Karışık Dil Üstbilgileri Oluşturma' bölümü yardımcı oldu: oracle.com/technetwork/articles/servers-storage-dev/…
zanbri

Gerçekten bana yardım etti! Bu soruyu ararken bu benim durumumdu
knightofhorizon

Bu durum, C : extern ile çevrili bir kazayla normal C ++ başlık dosyanızı eklerseniz de olabilir extern "C" { #include <myCppHeader.h> }.
ComFreek

68

Her şey başarısız olursa, yeniden derleyin.

Son zamanlarda, sadece rahatsız edici dosyayı yeniden derleyerek Visual Studio 2012'de çözülemeyen bir dış hatadan kurtulabildim. Yeniden inşa ettiğimde hata ortadan kalktı.

Bu genellikle iki (veya daha fazla) kitaplığın döngüsel bağımlılığı olduğunda olur. A Kütüphanesi, B.lib'de semboller kullanmaya ve B Kütüphanesi, A.lib'den semboller kullanmaya çalışır. İkisi de başlamak için mevcut değil. A derlemeye çalıştığınızda, bağlantı adımı başarısız olur çünkü B.lib bulamaz. A.lib oluşturulacak, ancak dll olmayacak. Daha sonra B'yi derlersiniz, bu da başarılı olur ve B.lib üretir. A'nın derlenmesi şimdi çalışacaktır çünkü B.lib artık bulunmuştur.


1
Doğru - bu, kütüphanelerin döngüsel bir bağımlılığı olduğunda olur.
Luchian Grigore

1
Cevabınızı genişlettim ve ana cevaba bağlandım. Teşekkürler.
Luchian Grigore

57

Yanlış modülleri / dll (derleyiciye özgü) yöntem / sınıfları alma / verme.

MSVS kullanarak ihracat ve ithalat için hangi sembollerin belirtmenizi gerektirir __declspec(dllexport)ve__declspec(dllimport) .

Bu ikili işlevsellik genellikle bir makro kullanılarak elde edilir:

#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif

Makro THIS_MODULE, yalnızca işlevi veren modülde tanımlanır. Bu şekilde beyan:

DLLIMPEXP void foo();

genişler

__declspec(dllexport) void foo();

ve geçerli modül tanımını içerdiğinden derleyiciye işlevi dışa aktarmasını söyler. Beyanı farklı bir modüle dahil ettiğinizde,

__declspec(dllimport) void foo();

ve derleyiciye, tanımın bağlandığınız kütüphanelerden birinde olduğunu söyler (ayrıca bkz. 1) ).

Benzer şekilde sınıfları içe / dışa aktarabilirsiniz:

class DLLIMPEXP X
{
};

2
Tam olarak söylemek gerekirse, bu yanıt GCC visibilityve Windows .defdosyalarından bahsetmelidir , çünkü bunlar sembol adını ve varlığını da etkiler.
rubenvb

@rubenvb Çağlarda .defdosya kullanmadım. Bir cevap eklemek veya bunu cevaplamaktan çekinmeyin.
Luchian Grigore

57

Bu, her VC ++ programcısının defalarca gördüğü en kafa karıştırıcı hata mesajlarından biridir. Önce şeyleri netleştirelim.

A. Sembol nedir? Kısacası, bir sembol bir isimdir. Değişken adı, işlev adı, sınıf adı, typedef adı veya C ++ diline ait adlar ve işaretler dışında herhangi bir şey olabilir. Kullanıcı tarafından tanımlanmış veya bağımlılık kitaplığı (başka bir kullanıcı tanımlı) tarafından tanıtılmıştır.

Harici nedir? VC ++ 'da, her kaynak dosya (.cpp, .c, vb.) Bir çeviri birimi olarak kabul edilir, derleyici her seferinde bir birim derler ve geçerli çeviri birimi için bir nesne dosyası (.obj) oluşturur. (Bu kaynak dosyanın içerdiği her başlık dosyasının önceden işleneceğini ve bu çeviri biriminin bir parçası olarak kabul edileceğini unutmayın) Bir çeviri birimindeki her şey dahili, diğer her şey harici olarak kabul edilir. C ++ gibi anahtar kelimeleri kullanarak harici bir sembolü referans verebilir extern,__declspec (dllimport) vb.

C. “Çözmek” nedir? Çöz, bir bağlantı zamanı terimidir. Bağlama zamanında, bağlayıcı dosya tanımını dahili olarak bulamayan her sembolün dış tanımını bulmaya çalışır. Bu arama sürecinin kapsamı:

  • Derleme zamanında oluşturulan tüm nesne dosyaları
  • Bu bina uygulamasının ek bağımlılıkları olarak açıkça veya örtük olarak belirtilen tüm kitaplıklar (.lib).

Bu arama işlemine çözüm adı verilir.

D. Son olarak, neden Çözülmemiş Dış Sembol? Bağlayıcı, dahili olarak tanımı olmayan bir sembolün dış tanımını bulamazsa, Çözülmemiş Dış Sembol hatası bildirir.

E. LNK2019'un olası nedenleri : Çözülmemiş Harici Sembol hatası. Bu hatanın, bağlayıcının harici sembollerin tanımını bulamamasından kaynaklandığını zaten biliyoruz, olası nedenler şu şekilde sıralanabilir:

  1. Tanım var

Örneğin, a.cpp'de tanımlanmış foo adında bir fonksiyonumuz varsa:

int foo()
{
    return 0;
}

B.cpp'de foo fonksiyonunu çağırmak istiyoruz, bu yüzden

void foo();

foo () işlevini bildirmek ve başka bir işlev gövdesinde çağırmak için şunu söyleyin bar():

void bar()
{
    foo();
}

Şimdi bu kodu oluşturduğunuzda foo'nun çözülmemiş bir sembol olduğundan şikayet eden bir LNK2019 hatası alacaksınız. Bu durumda, foo () 'nun a.cpp'de tanımına sahip olduğunu biliyoruz, ancak aradığımızdan farklı (farklı dönüş değeri). Bu tanım mevcut.

  1. Tanım mevcut değil

Bir kitaplıktaki bazı işlevleri çağırmak istiyorsak, ancak içe aktarma kitaplığı Project | Properties | Configuration Properties | Linker | Input | Additional Dependencyproje ayarınızın ek bağımlılık listesine (set from:) eklenmez . Tanım, geçerli arama kapsamında bulunmadığından, bağlayıcı bir LNK2019 bildirecektir.


1
Sistematik açıklama için teşekkürler. Bunu okuduktan sonra diğer cevapları anlayabiliyordum.
displayName

55

Şablon uygulamaları görünmüyor.

Uzman olmayan şablonların tanımları, bunları kullanan tüm çeviri birimleri tarafından görülebilir olmalıdır. Bu, şablon tanımını bir uygulama dosyasına ayıramayacağınız anlamına gelir. Uygulamayı ayırmanız gerekiyorsa, olağan geçici çözüm, implşablonu bildiren üstbilginin sonuna eklediğiniz bir dosyaya sahip olmaktır . Ortak bir durum:

template<class T>
struct X
{
    void foo();
};

int main()
{
    X<int> x;
    x.foo();
}

//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}

Bunu düzeltmek için, tanımını X::foo başlık dosyasına veya onu kullanan çeviri biriminin görebileceği bir yere taşımalısınız.

Özel şablonlar bir uygulama dosyasına uygulanabilir ve uygulamanın görünür olması gerekmez, ancak özelliğin önceden bildirilmesi gerekir.

Daha fazla açıklama ve başka bir olası çözüm (açık örnekleme) için bu soruya ve cevaba bakınız .


1
"Şablonlar başlıkta tanımlanmalıdır" ile ilgili daha fazla bilgiyi stackoverflow.com/questions/495021
PlasmaHH

41

giriş noktası referansına tanımsız referans WinMain@16veya benzeri 'olağandışı' main() başvuru (özellikle).

Gerçek IDE'nizle doğru proje türünü seçmeyi kaçırmış olabilirsiniz. IDE, yaygın olarak kullanılan int main(int argc, char** argv);imza yerine, örneğin Windows Uygulaması projelerini bu tür bir giriş noktası işlevine (yukarıdaki eksik başvuruda belirtildiği gibi) bağlamak isteyebilir .

IDE'niz Düz Konsol Projelerini destekliyorsa , bir windows uygulama projesi yerine bu proje türünü seçmek isteyebilirsiniz.


İşte gerçek dünya sorunundan daha ayrıntılı olarak ele alınan case1 ve case2 .


2
Değil yardım ama mesele dışarı Can bu soruya ve bu daha sık olmamasından yerine hiçbir ana işlevi sağlayarak neden olması WinMain. Geçerli C ++ programlarının a main.
chris

36

Ayrıca 3. taraf kitaplıkları kullanıyorsanız, doğru 32/64 bit ikili dosyalara sahip olduğunuzdan emin olun


34

Microsoft #pragma, bağlantı zamanında doğru kitaplığa başvurmak için bir teklif sunar ;

#pragma comment(lib, "libname.lib")

Kitaplığın dizinini içeren kitaplık yoluna ek olarak, bu kitaplığın tam adı olmalıdır.


34

Yeni araç seti sürümü için Visual Studio NuGet paketinin güncellenmesi gerekiyor

Ben sadece libpng Visual Studio 2013 ile bağlantı çalışırken bu sorunu vardı. Sorun paket dosya sadece Visual Studio 2010 ve 2012 için kütüphaneler vardı.

Doğru çözüm, geliştiricinin güncellenmiş bir paket yayınlayıp daha sonra yükseltmesini ummaktır, ancak VS2012 kütüphane dosyalarını işaret ederek VS2013 için ekstra bir ayarda hackleyerek benim için çalıştı.

Paketi ( packagesçözüm dizininin içindeki klasörde) packagename\build\native\packagename.targetsbu dosyayı bularak ve içindeki tüm v110bölümleri kopyalayarak düzenledim . Değiştim v110için v120de durum alanları sadece tüm olarak dosya adı yol bırakmak çok dikkatli olmak v110. Bu, Visual Studio 2013'ün 2012 için kütüphanelere bağlanmasına izin verdi ve bu durumda işe yaradı.


1
Bu çok spesifik görünüyor - belki de yeni bir konu bu cevap için daha iyi bir yer olacaktır.
Luchian Grigore

3
@LuchianGrigore: Bu soruyu tam olarak bu sorun olduğu için buraya göndermek istedim , ancak bu sorunun bir kopyası olarak işaretlendi, bu yüzden orada cevap veremedim. Bunun yerine cevabımı buraya gönderdim.
Malvineous

Bu sorunun zaten kabul edilmiş bir cevabı var. Genel neden yukarıda listelendiği için yinelenen olarak işaretlenmiştir. İçerilmeyen bir kütüphaneyle ilgili her sorun için burada bir cevap verseydik ne olurdu?
Luchian Grigore

6
@LuchianGrigore: Bu sorun bir kitaplığa özgü değildir, Visual Studio'nun paket yönetim sistemini kullanan tüm kitaplıkları etkiler. Diğer soruyu buldum çünkü ikimiz de libpng ile problemlerimiz vardı. Ayrıca libxml2, libiconv ve glew için de aynı problemi yaşadım. Bu soru, Visual Studio'nun paket yönetim sistemindeki bir sorunla ilgilidir ve cevabım nedeni açıklar ve bir geçici çözüm sağlar. Birisi "çözülmemiş harici" gördü ve aslında bir paket yönetim sorunu olduğunda standart bir bağlayıcı sorunu olduğunu varsaydı.
Malvineous

34

Bin .cpp dosyası ve bin .h dosyası olan c ++ 'da yazılmış büyük bir projeniz olduğunu varsayalım ve diyelim ki proje on statik kütüphaneye de bağlı. Diyelim ki Windows'dayız ve projemizi Visual Studio 20xx'de oluşturuyoruz. Tüm çözümü derlemeye başlamak için Ctrl + F7 Visual Studio'ya bastığınızda (çözümde sadece bir projemiz olduğunu varsayalım)

Derlemenin anlamı nedir?

  • Visual Studio .vcxproj dosyasını arayın ve .cpp uzantılı her dosyayı derlemeye başlayın. Derleme sırası undefined olduğundan, önce main.cpp dosyasının derlendiğini varsaymamalısınız.
  • .Cpp dosyaları, .cpp dosyasında tanımlanabilecek veya tanımlanamayacak simgeleri bulmak için ek .h dosyalarına bağımlıysa
  • Derleyicinin bir simge bulamadığı bir .cpp dosyası varsa, derleyici zaman hatası Symbol x bulunamadı iletisini yükseltir
  • .Cpp uzantılı her dosya için bir nesne dosyası .o oluşturulur ve ayrıca Visual Studio çıktıyı , bağlayıcı tarafından işlenmesi gereken tüm nesne dosyalarını içeren ProjectName.Cpp.Clean.txt adlı bir dosyaya yazar .

Derlemenin ikinci adımı Linker tarafından yapılır.Linker tüm nesne dosyasını birleştirmeli ve çıktıyı oluşturmalıdır (yürütülebilir veya kütüphane olabilir)

Proje bağlama adımları

  • Tüm nesne dosyalarını ayrıştırın ve yalnızca başlıklarda bildirilen tanımı bulun (örneğin: Önceki yanıtlarda belirtildiği gibi bir sınıfın bir yönteminin kodu veya bir sınıfın üyesi olan statik değişkenin başlatılması olayı)
  • Bir sembol nesnesi dosyalarında bulunamadı Eğer o da bir proje için yeni bir kütüphane ekleyerek Ek Libraries.For içinde aranır Yapılandırma özellikleri -> VC ++ Dizinler -> Kütüphane Dizinler ve burada kütüphaneleri ve arama için ek klasör belirtilen Yapılandırma özelliklerini > - Bağlayıcı -> Kitaplığın adını belirtmek için giriş . Bağlayıcı Bir .cpp yazma sembolü bulamadık -Eğer o yükseltir bağlayıcı hatası gibi gelebilir error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

Gözlem

  1. Bağlayıcı bir sembol bulduğunda, diğer kütüphanelerde bunun için arama yapmaz
  2. Kütüphaneleri bağlama sırası önemlidir .
  3. Linker bir statik kütüphanede harici bir sembol bulursa, projenin çıktısında sembolü içerir, ancak kütüphane paylaşılırsa (dinamik) çıktıdaki kodu (sembolleri) içermez, ancak Çalışma Zamanı çökebilir meydana

Bu tür bir hatayı nasıl çözebilirim?

Derleyici Zaman Hatası:

  • C ++ projenizi sözdizimsel olarak doğru yazdığınızdan emin olun.

Bağlayıcı Zaman Hatası

  • Başlık dosyalarınızda bildirdiğiniz tüm sembolünüzü tanımlayın
  • #pragma onceDerleyiciye, derlenmiş olan geçerli .cpp dosyasına zaten eklenmişse bir başlık eklememesine izin vermek için kullanın
  • Harici kitaplığınızın başlık dosyalarınızda tanımladığınız diğer sembollerle çakışabilecek simgeler içermediğinden emin olun
  • Şablonu kullandığınızda, derleyicinin herhangi bir örnekleme için uygun kod üretmesine izin vermek için her bir şablon işlevinin tanımını başlık dosyasına eklediğinizden emin olun.

Cevabınız görsel stüdyoya özgü değil mi? Soru herhangi bir IDE / derleyici aracı belirtmediğinden, görsel olmayan stüdyo bölümü için yanıtınızı işe yaramaz hale getirir.
Victor Polevoy

Haklısın . Ama derleme / bağlantı her IDE sürecinde her şey biraz farklı yapılır.Ancak dosyalar tam olarak aynı işlenir (hatta g ++ bayrakları ayrıştırıldığında aynı şeyi yapar ..)

Sorun aslında IDE ile ilgili değil, sorunları birbirine bağlamak için bir cevap. Bağlantı sorunları IDE ile değil derleyici ve derleme işlemiyle ilgilidir.
Victor Polevoy

Ancak derleme / bağlama işlemi g ++ / Visual Studio'da (VS için Microsoft tarafından sağlanan derleyici) / Eclipse / Net Beans'da aynı şekilde yapılıyor

29

Derleyicide / IDE'de bir hata

Geçenlerde bu sorunu yaşadım ve bunun Visual Studio Express 2013'te bir hata olduğu ortaya çıktı . Bir kaynak dosyayı projeden kaldırmak ve hatayı aşmak için yeniden eklemek zorunda kaldı.

Derleyici / IDE'de bir hata olabileceğini düşünüyorsanız denemeniz için adımlar:

  • Projeyi temizleyin (bazı IDE'lerin bunu yapma seçeneği vardır, nesne dosyalarını silerek manuel olarak da yapabilirsiniz)
  • Tüm kaynak kodları orijinalinden kopyalayarak yeni bir proje başlatmayı deneyin.

5
Aletlerinizin kırıldığına inanmak büyük olasılıkla sizi gerçek nedenden uzaklaştıracaktır. Bir derleyicinin soruna neden olduğundan bir hata yapmış olmanız çok daha olasıdır. Çözümünüzü temizlemek veya derleme yapılandırmanızı yeniden oluşturmak derleme hatalarını giderebilir, ancak bu derleyicide bir hata olduğu anlamına gelmez. Bağlantılı "bir hata olduğu ortaya çıktı" Microsoft tarafından onaylanmamıştır ve yeniden üretilemez.
JDiMatteo

4
@JDiMatteo Bu soruda 21 cevap vardır ve bu nedenle önemli miktarda cevap "olası" bir çözüm olmayacaktır. Olabilirlik eşiğinizin altındaki tüm cevapları reddederseniz, yaygın durumların çoğu zaten kolayca tespit edilebildiğinden, bu sayfa etkili bir şekilde işe yaramaz hale gelir.
developerbmw

27

Hatayı teşhis etmeye yardımcı olması için bağlayıcıyı kullanın

Modern bağlayıcıların çoğu, değişen derecelerde yazdırılan ayrıntılı bir seçenek içerir;

  • Bağlantı çağırma (komut satırı),
  • Bağlantı aşamasına hangi kitaplıkların dahil edildiğine ilişkin veriler,
  • Kütüphanelerin yeri,
  • Kullanılan arama yolları.

Gcc ve clang için; genellikle komut satırına -v -Wl,--verboseveya -v -Wl,-vkomut satırını eklersiniz . Daha fazla ayrıntıyı burada bulabilirsiniz;

MSVC için /VERBOSE(özellikle /VERBOSE:LIB) link komut satırına eklenir.


26

Bağlantılı .lib dosyası bir .dll ile ilişkilendirilir

Aynı sorunu yaşadım. Diyelim ki MyProject ve TestProject projelerim var. MyProject için lib dosyasını TestProject'e etkili bir şekilde bağladım. Ancak, bu lib dosyası MyProject için DLL oluşturulduğu gibi üretildi. Ayrıca, MyProject tüm yöntemler için kaynak kodu içermiyordu, ancak yalnızca DLL giriş noktalarına erişim.

Sorunu çözmek için, MyProject'i bir LIB olarak oluşturdum ve TestProject'i bu .lib dosyasına bağladım (oluşturulan .lib dosyasını TestProject klasörüne kopyalayın). Sonra tekrar MyProject DLL olarak oluşturabilirsiniz. TestProject'in bağlandığı lib MyProject sınıflarındaki tüm yöntemler için kod içerdiğinden derleniyor.


25

Bağlayıcı hataları söz konusu olduğunda insanlar bu soruya yönlendirilmiş gibi göründüğünden, bunu buraya ekleyeceğim.

GCC 5.2.0 ile bağlantı hatalarının olası nedenlerinden biri, varsayılan olarak yeni bir libstdc ++ kitaplığı ABI'sının seçilmesidir.

Std :: __ cxx11 ad boşluğunda veya [abi: cxx11] etiketinde türler içeren sembollere tanımlanmamış başvurularla ilgili bağlantı hataları alırsanız, muhtemelen _GLIBCXX_USE_CXX11_ABI için farklı değerlerle derlenmiş nesne dosyalarını birbirine bağlamaya çalıştığınızı gösterir. makro. Bu genellikle GCC'nin eski bir sürümüyle derlenmiş bir üçüncü taraf kütüphanesine bağlanırken olur. Üçüncü taraf kitaplığı yeni ABI ile yeniden oluşturulamazsa, kodunuzu eski ABI ile yeniden derlemeniz gerekir.

Bu yüzden 5.1.0'dan sonra bir GCC'ye geçerken aniden link hataları alırsanız, bu kontrol edilmesi gereken bir şey olacaktır.


20

GNU ld etrafında bağlayıcı komut dosyalarını desteklemeyen bir sarıcı

Bazı .so dosyaları aslında GNU ld linker scriptleridir , örn. Libtbb.so dosyası, bu içeriğe sahip bir ASCII metin dosyasıdır:

INPUT (libtbb.so.2)

Bazı daha karmaşık yapılar bunu desteklemeyebilir. Örneğin, derleyici seçeneklerine -v eklerseniz , mainwin gcc sarmalayıcı mwdip'in , bağlanacak kitaplıkların ayrıntılı çıktı listesindeki bağlayıcı komut dosyası komut dosyalarını attığını görebilirsiniz . Basit bir çözüm, bağlayıcı komut dosyası giriş komutunu değiştirmektir. dosyanın bir kopyasını içeren dosya (veya bir sembolik bağlantı), ör.

cp libtbb.so.2 libtbb.so

Veya -l bağımsız değişkenini .so dosyasının tam yoluyla değiştirebilirsiniz, örneğin -ltbbdo yerine/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2


20

Bağlantınız kitaplıkları, bunlara başvuran nesne dosyalarından önce tüketir

  • Programınızı derlemeye ve GCC araç zincirine bağlamaya çalışıyorsunuz.
  • Bağlantınız tüm gerekli kitaplıkları ve kitaplık arama yollarını belirtir
  • Eğer libfoobağlıdır libbar, sonra bağlantı doğru koyar libfooöncelibbar .
  • Bağlantınız bir şeyle başarısız oluyorundefined reference to hataları.
  • Ancak tüm tanımlanmamış şeyler , sahip olduğunuz başlık dosyalarında bildirilir #includeve aslında bağladığınız kütüphanelerde tanımlanır.

Örnekler C cinsindendir. Aynı şekilde C ++ olabilirler.

Kendiniz inşa ettiğiniz statik kütüphaneyi içeren minimal bir örnek

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;
}

Statik kitaplığınızı oluşturursunuz:

$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o

Programınızı derlersiniz:

$ gcc -I. -c -o eg1.o eg1.c

Bağlantı kurmaya çalışın libmy_lib.ave başarısız olun:

$ 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

Bir adımda derleyip bağlarsanız aynı sonuç, örneğin:

$ 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

Paylaşılan sistem kitaplığı olan sıkıştırma kitaplığını içeren en az örnek libz

eg2.c

#include <zlib.h>
#include <stdio.h>

int main()
{
    printf("%s\n",zlibVersion());
    return 0;
}

Programınızı derleyin:

$ gcc -c -o eg2.o eg2.c

Programınızı bağlamayı deneyin libzve başarısız olun:

$ 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

Bir seferde derleyip bağlarsanız aynı:

$ 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

Ve örnek 2'de aşağıdakileri içeren bir varyasyon pkg-config:

$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'

Ne yapıyorsun?

Programınızı oluşturmak için bağlamak istediğiniz nesne dosyaları ve kitaplıklar sırasında, kitaplıkları bunlara başvuran nesne dosyalarının önüne yerleştirirsiniz. Sen kütüphaneleri yerleştirmeniz gerekir sonra onlara bakın nesne dosyaları.

Örnek 1'i doğru bağlayın:

$ gcc -o eg1 eg1.o -L. -lmy_lib

Başarı:

$ ./eg1 
Hello World

Örnek 2'yi doğru şekilde bağlayın:

$ gcc -o eg2 eg2.o -lz

Başarı:

$ ./eg2 
1.2.8

Örnek 2 pkg-configvaryasyonunu doğru şekilde bağlayın:

$ gcc -o eg2 eg2.o $(pkg-config --libs zlib) 
$ ./eg2
1.2.8

Açıklama

Buradan okuma isteğe bağlıdır .

Varsayılan olarak, dağıtımınızda GCC tarafından oluşturulan bir bağlantı komutu, bağlantıdaki dosyaları komut satırı sırasında soldan sağa tüketir. Bir dosyanın bir şeye atıfta bulunduğunu tespit ettiğinde ve bunun için bir tanım içermediğini tespit ettiğinde, sağdaki dosyalarda bir tanım arayacaktır. Sonunda bir tanım bulursa, başvuru çözülür. Sonunda herhangi bir referans çözülmezse, bağlantı başarısız olur: bağlayıcı geriye doğru arama yapmaz.

İlk olarak, örnek 1 , statik kitaplıklamy_lib.a

Statik kitaplık, nesne dosyalarının dizinlenmiş bir arşividir. Bağlayıcı -lmy_libbağlantı sırasında bulduğunda ve bunun statik kitaplığa ./libmy_lib.abaşvurduğunu anladığında, programınızın nesne dosyalarından herhangi birine ihtiyacı olup olmadığını bilmek ister libmy_lib.a.

Sadece nesne dosyası içinde var libmy_lib.ayani my_lib.o, ve tanımlanan tek bir şey var my_lib.o, yani işlevhw .

Bağlayıcı, programınızın my_lib.oyalnızca programınızın başvurduğunu zaten biliyorsa, programa hwzaten eklediği bir veya daha fazla nesne dosyasında ve zaten eklenmiş nesne dosyalarının hiçbirinin bir tanımı hw,.

Bu doğruysa, bağlayıcı my_lib.okitaplığın bir kopyasını ayıklayıp programınıza ekler. Ardından, programı için bir tanım içeriyor hw, bu yüzden onun referanslar için hwvardır çözüldü .

Programı aşağıdaki gibi bağlamaya çalıştığınızda:

$ gcc -o eg1 -L. -lmy_lib eg1.o

linker gördüğünde programa eklememiştir . Çünkü o noktada, görmedi . Programınız henüz ilişkin referansları yapmaz : henüz herhangi bir referans yapmaz hiç , bütün referanslar bunu yapar içindedir çünkü .eg1.o -lmy_libeg1.ohweg1.o

Bu yüzden linker my_lib.oprograma eklenmez ve bunun için başka kullanımı yoktur libmy_lib.a.

Sonra bulur eg1.ove program olarak ekler. Bağlantı dizisindeki bir nesne dosyası her zaman programa eklenir. Şimdi, program bir referans yapar hwve bir tanımı içermez hw; ancak bağlantı sırasında eksik tanım sağlayabilecek hiçbir şey kalmaz. Referans hwkadar uçları çözülmemiş ve bağlantı başarısız olur.

İkinci olarak, örnek 2 , paylaşılan kitaplıklalibz

Paylaşılan kitaplık, nesne dosyalarının veya bunun gibi bir şeyin arşivi değildir. Daha çok bir işlevi olmayan bir programa benziyor mainve bunun yerine tanımladığı diğer birçok sembolü ortaya çıkarıyor, böylece diğer programlar bunları çalışma zamanında kullanabiliyor.

Birçok Linux dağıtımları bugün GCC araç zinciri böylece onun dil sürücülerini (yapılandırmak gcc, g++, gfortransistem bağlayıcı (talimat vs) ldbir bağlantısını paylaşılan kütüphanelere) olarak ihtiyaç duyulan bazda. Bu dağıtımlardan birine sahipsiniz.

Bu, linker -lzbağlantı sırasında bulduğunda ve bunun paylaşılan kütüphaneye (örneğin) /usr/lib/x86_64-linux-gnu/libz.soatıfta bulunduğunu anladığında, programınıza henüz tanımlanmamış herhangi bir referansın tanımlanmamış olup olmadığını bilmek istediği anlamına gelir. tarafından ihraç edildilibz

Eğer bu doğruysa, o zaman bağlayıcı olacak değil dışına herhangi parçalarını kopyalamak libzve programa ekleyebilirsiniz; bunun yerine, sadece programınızın kodunu doktor olacak, böylece: -

  • Çalışma zamanında, sistem program yükleyicisi, programınızın bir kopyasını libzher yüklediğinde, programınızla aynı sürece bir kopyasını yükler.

  • Çalışma zamanında, programınız tanımlanmış bir şeye libzbaşvurduğunda, bu başvuru libzaynı işlemde kopyasının dışa aktardığı tanımı kullanır .

Programınız dışa aktarılan bir tanıma sahip olan tek bir şeye libz, yani zlibVersionsadece bir kez atıfta bulunulan işleve atıfta bulunmak istiyor eg2.c. Bağlayıcı bu referansı programınıza ekler ve ardından tarafından dışa aktarılan tanımı bulursa libz, başvuru çözülür

Ancak programı aşağıdaki gibi bağlamaya çalıştığınızda:

gcc -o eg2 -lz eg2.o

Bağlayıcı bulgular zaman olayların sırası noktasında örnek 1. olduğu gibi sadece aynı şekilde yanlıştır -lzvardır hiçbir programda hiçbir şeye referanslar: hepsi içindedir eg2.ohenüz görülmedi. Bu yüzden bağlayıcı, işe yaramayacağına karar verir libz. Ulaştığında eg2.o, programa ekler ve daha sonra referansı tanımlanmamışsa zlibVersion, bağlantı dizisi tamamlanır; bu başvuru çözümlenmedi ve bağlantı başarısız oldu.

Son olarak, pkg-configörnek 2'deki varyasyonun şimdi açık bir açıklaması vardır. Kabuk açıldıktan sonra:

gcc -o eg2 $(pkg-config --libs zlib) eg2.o

dönüşür:

gcc -o eg2 -lz eg2.o

bu yine örnek 2'dir.

Örnek 1'deki sorunu yeniden oluşturabilirim, ancak örnek 2'de değil

Bağlantı:

gcc -o eg2 -lz eg2.o

senin için iyi çalışıyor!

(Veya: Bu bağlantı, Fedora 23'te sizin için iyi çalıştı, ancak Ubuntu 16.04'te başarısız oldu)

Bunun nedeni, bağlantının çalıştığı dağıtımın, GCC araç zincirini paylaşılan kütüphaneleri gerektiği gibi bağlamak için yapılandırmayanlardan biri olmasıdır .

O günlerde, unix benzeri sistemlerin statik ve paylaşılan kütüphaneleri farklı kurallara bağlaması normaldi. Bir bağlantı sekansındaki statik kütüphaneler, örnek l'de açıklanan ihtiyaca göre bağlandı, ancak paylaşılan kütüphaneler koşulsuz olarak bağlandı.

Bağlayıcının program tarafından paylaşılan bir kitaplığa gerek olup olmadığını düşünmesi gerekmediğinden, bu davranış bağlantı zamanında ekonomiktir: eğer paylaşılan bir kitaplıksa, onu bağlayın. Ve çoğu bağlantıdaki çoğu kütüphane paylaşılan kütüphanelerdir. Ama dezavantajları da var: -

  • Çalışma zamanında ekonomik değildir , çünkü ihtiyaç duyulmasa bile paylaşılan kitaplıkların bir programla birlikte yüklenmesine neden olabilir.

  • Statik ve paylaşılan kütüphaneleri için farklı bağlantı kuralları olmadığını bilmiyor olabilir uzman olmayan programcılar, kafa karıştırıcı olabilir -lfooonların bağlantı isteği ve kararı gidiyor içinde /some/where/libfoo.aveya /some/where/libfoo.sove yine paylaşılan ve statik kütüphaneleri arasındaki farkı anlamayabilir.

Bu değiş tokuş bugün şizmatik duruma yol açtı. Bazı dağıtımlar, paylaşılan kütüphaneler için GCC bağlantı kurallarını değiştirdi, böylece ihtiyaç ilkesi tüm kütüphaneler için geçerlidir. Bazı dağıtımlar eski yolla sıkıştı.

Aynı anda derleyip bağlasam bile neden bu sorunu alıyorum?

Sadece yaparsam:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c

şüphesiz gcc önce derlemek eg1.cve sonra ortaya çıkan nesne dosyasını bağlamak zorundadır libmy_lib.a. Peki, bağlantı yaparken nesne dosyasının gerekli olduğunu nasıl bilemez?

Çünkü tek bir komutla derleme ve bağlantı, bağlantı dizisinin sırasını değiştirmez.

Yukarıdaki komutu çalıştırdığınızda, gccderleme + bağlantı istediğinizi anlar. Yani perde arkasında, bir derleme komutu oluşturur ve çalıştırır, daha sonra bir bağlantı komutu oluşturur ve sanki onu çalıştırır sen iki komut kaçmıştı:

$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o

Yani bağlantı eğer o yaptığı gibi başarısız bu iki komutları çalıştırın. Hatada fark ettiğiniz tek fark, gcc'nin derleme + bağlantı durumunda geçici bir nesne dosyası oluşturmasıdır, çünkü kullanmasını söylemezsiniz eg1.o. Görürüz:

/tmp/ccQk1tvs.o: In function `main'

onun yerine:

eg1.o: In function `main':

Ayrıca bakınız

Birbirine bağlı bağlantılı kitaplıkların belirtilme sırası yanlış

Birbirine bağımlı kütüphaneleri yanlış sıraya koymak , bağlantıları sağlayan dosyalardan daha sonra bağlantıda daha sonra gelen şeylerin tanımlarına ihtiyaç duyan dosyaları almanın sadece bir yoludur . Kütüphaneleri kendilerine başvuran nesne dosyalarının önüne koymak da aynı hatayı yapmanın başka bir yoludur.


18

Arkadaşlık şablonları ...

Bir arkadaş türüyle (veya işleviyle) şablon türünün kod snippet'i verildiğinde;

template <typename T>
class Foo {
    friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};

operator<<Olmayan bir şablon işlevi olarak ilan ediliyor. Birlikte Tkullanılan her tip Fooiçin şablonlanmamış olmalıdır operator<<. Örneğin, Foo<int>bildirilen bir tür varsa, aşağıdaki gibi bir operatör uygulaması olmalıdır;

std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}

Uygulanmadığından, bağlayıcı onu bulamaz ve hatayla sonuçlanır.

Bunu düzeltmek için, bir şablon operatörünü Footürden önce bildirebilir ve daha sonra uygun örneklemeyle arkadaş olarak bildirebilirsiniz . Sözdizimi biraz garip, ancak aşağıdaki gibi görünüyor;

// 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
}

Yukarıdaki kod, operatörün dostluğunu karşılık gelen örneklemeyle sınırlar Foo, yani operator<< <int>örnekleme, örneklemenin özel üyelerine erişmekle sınırlıdır Foo<int>.

Alternatifler;

  • Arkadaşlığın aşağıdaki gibi şablonların tüm örneklemelerini genişletmesine izin vermek;

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
  • Veya operator<<sınıf uygulaması içinde satır içi uygulama yapılabilir;

    template <typename T>
    class Foo {
        friend std::ostream& operator<<(std::ostream& os, const Foo& a)
        { /*...*/ }
        // ...
    };

Not operatörü (ya da işlevinin) açıklama sadece sınıf göründüğünde, adı için uygun değildir, "normal" arama, yalnızca argüman bağımlı arama için, gelen cppreference ;

Sınıf veya sınıf şablonu X içindeki bir arkadaş bildiriminde ilk kez bildirilen bir ad, X'in en içteki ad alanının bir üyesi olur, ancak ad alanı kapsamındaki eşleşen bir bildirim olmadığı sürece arama için (X'i dikkate alan bağımsız değişken arama dışında) erişilemez. sağlanan...

Cppreference ve C ++ SSS'de şablon arkadaşlar hakkında daha fazla okuma var .

Yukarıdaki teknikleri gösteren kod listesi .


Hatalı kod örneğinin bir yan notu olarak; g ++ bu konuda şu şekilde uyarır:

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)


16

Ekleme yollarınız farklı olduğunda

Bir üstbilgi dosyası ve ilişkili paylaşılan kitaplığı (.lib dosyası) eşitlenmediğinde bağlayıcı hataları oluşabilir. Açıklamama izin ver.

Bağlayıcılar nasıl çalışır? Bağlayıcı, imzalarını karşılaştırarak (başlıkta bildirilen) bir işlev bildirimini tanımıyla (paylaşılan kitaplıkta) eşleştirir. Bağlayıcı mükemmel bir şekilde eşleşen bir işlev tanımı bulamazsa bir bağlayıcı hatası alabilirsiniz.

Beyan ve tanım eşleşiyor gibi görünse de bir linker hatası almak mümkün müdür? Evet! Kaynak kodunda aynı görünebilirler, ancak derleyicinin gördüklerine bağlıdır. Aslında böyle bir durumla sonuçlanabilirsin:

// header1.h
typedef int Number;
void foo(Number);

// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically

Her iki işlev bildiriminin de kaynak kodda nasıl aynı görünse de, derleyiciye göre gerçekten farklı olduklarına dikkat edin.

Böyle bir durumda nasıl sonuçlandığını sorabilirsiniz? Elbette yolları ekleyin ! Paylaşılan kitaplığı derlerken, içerme yolu yol açarsa header1.hve header2.hkendi programınızda kullanmaya başlarsanız, ne olduğunu merak ederek başlığınızı kaşıracaksınız (pun amaçlı).

Bunun gerçek dünyada nasıl olabileceğine bir örnek aşağıda açıklanmıştır.

Bir örnekle daha fazla ayrıntı

İki projem var: graphics.libve main.exe. Her iki proje de buna bağlı common_math.h. Kütüphanenin aşağıdaki işlevi dışa aktardığını varsayın:

// graphics.lib    
#include "common_math.h" 

void draw(vec3 p) { ... } // vec3 comes from common_math.h

Ve sonra devam edip kütüphaneyi kendi projenize dahil edersiniz.

// main.exe
#include "other/common_math.h"
#include "graphics.h"

int main() {
    draw(...);
}

Boom! Bir bağlayıcı hatası alıyorsunuz ve neden başarısız olduğu hakkında hiçbir fikriniz yok. Nedeni ortak kütüphane aynı dahil farklı sürümleri kullanıyor olmasıdır common_math.h(Ben burada farklı bir yol ekleyerek örnekte açık yaptık, ama her zaman çok açık olmayabilir. Belki derleme ayarları dahil yolu farklıdır) .

Bu örnekte, bağlayıcının size bulamadığını söyleyeceğini draw(), gerçekte bunun kütüphane tarafından dışa aktarıldığını bildiğiniz zaman. Neyin yanlış gittiğini merak ederek kafanızı çizerek saatler geçirebilirsiniz. Mesele şu ki, bağlayıcı türleri farklı bir imza görüyor çünkü parametre türleri biraz farklı. Örnekte, vec3derleyici açısından her iki projede de farklı bir tür bulunmaktadır. Bunun nedeni, biraz farklı iki içerme dosyasından gelmeleridir (belki içerme dosyaları kütüphanenin iki farklı sürümünden gelir).

Bağlayıcıda hata ayıklama

Visual Studio kullanıyorsanız, DUMPBIN arkadaşınızdır. Eminim diğer derleyiciler benzer araçlara sahiptir.

Süreç şöyle gider:

  1. Bağlayıcı hatası sırasında verilen garip karışık adı not edin. (örn. XYZ @ grafik çizin).
  2. Dışa aktarılan sembolleri kitaplıktan bir metin dosyasına dökün.
  3. Dışa aktarılan ilgilenilen sembolü arayın ve karıştırılan adın farklı olduğuna dikkat edin.
  4. Karışık isimlerin neden farklı olduğuna dikkat edin. Kaynak kodunda aynı görünseler bile parametre türlerinin farklı olduğunu görebilirsiniz.
  5. Farklı olmalarının nedeni. Yukarıda verilen örnekte, farklı içerme dosyaları nedeniyle farklıdırlar.

[1] Proje ile, bir kütüphane ya da bir yürütülebilir dosya üretmek için birbirine bağlı bir dizi kaynak dosya kastediyorum.

DÜZENLEME 1: Daha kolay anlaşılması için ilk bölümü yeniden yazınız. Başka bir şeyin düzeltilmesi gerekip gerekmediğini bana bildirmek için lütfen aşağıya yorum yapın. Teşekkürler!


15

Tutarsız UNICODEtanımlar

Windows UNICODE yapı ile inşa edilmiştir TCHARvb olarak tanımlanan wchar_tile bina değil ne zaman vb UNICODEile yapı olarak tanımlanan TCHARolarak tanımlanan charbu vb UNICODEve _UNICODEtanımlar tüm etkileyen " T" dize türleri ; LPTSTR, LPCTSTRVe bunların elk.

UNICODETanımlı bir kütüphane oluşturmak ve onu tanımlanmayan bir projeye bağlamaya çalışmak UNICODE, tanımlayıcısında bir uyumsuzluk olacağından bağlayıcı hatalarına neden olacaktır TCHAR; charvs wchar_t.

Hata genellikle bir charveya wchar_ttüretilmiş tipte bir değer içerir , bunlar da std::basic_string<>vb. İçerebilir. Koddaki etkilenen işleve göz atarken, genellikle TCHARveyastd::basic_string<TCHAR> vs. Bu kod başlangıçta ( "dar" veya) yapı bir UNICODE ve bir Multi-Byte Karakter ikisi için düşünülmüştü bir Gammaz işareti olduğunu .

Bunu düzeltmek için, UNICODE(ve _UNICODE) tutarlı bir tanımıyla gerekli tüm kütüphaneleri ve projeleri oluşturun .

  1. Bu iki yöntemle de yapılabilir;

    #define UNICODE
    #define _UNICODE
  2. Veya proje ayarlarında;

    Proje Özellikleri> Genel> Proje Varsayılanları> Karakter Kümesi

  3. Veya komut satırında;

    /DUNICODE /D_UNICODE

Alternatif de uygulanabilir, eğer UNICODE kullanılması amaçlanmamışsa, tanımların belirlenmediğinden ve / veya çok karakterli ayarın projelerde kullanıldığından ve tutarlı bir şekilde uygulandığından emin olun.

"Release" ve "Debug" sürümleri arasında da tutarlı olmayı unutmayın.


14

Temizleyin ve yeniden oluşturun

Yapının "temizliği", önceki yapılardan, başarısız yapılardan, eksik yapılardan ve diğer yapı sistemiyle ilgili yapı sorunlarından etrafta bırakılabilecek "ölü odunu" kaldırabilir.

Genel olarak IDE veya derleme bir tür "temiz" işlev içerecektir, ancak bu doğru bir şekilde yapılandırılmamış olabilir (örn. El ile oluşturulmuş bir dosyada) veya başarısız olabilir (örn. Ara veya sonuçtaki ikili dosyalar salt okunurdur).

"Temizleme" işlemi tamamlandıktan sonra, "temizleme" işleminin başarılı olduğunu ve oluşturulan tüm ara dosyanın (örn. Otomatik bir makefile) başarıyla kaldırıldığını doğrulayın.

Bu süreç son çare olarak görülebilir, ancak genellikle iyi bir ilk adımdır ; özellikle hatayla ilgili kod yakın zamanda eklenmişse (yerel olarak veya kaynak havuzdan).


10

constDeğişken bildirimlerinde / tanımlarında eksik "extern" (yalnızca C ++)

C'den gelen insanlar için C ++ 'da global constdeğişkenlerin dahili (veya statik) bağlantıya sahip olması şaşırtıcı olabilir . C'de durum böyle değildi, çünkü tüm global değişkenler dolaylı olarak extern(yani staticanahtar kelime eksik olduğunda).

Misal:

// 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
}

bir başlık dosyası kullanmak ve dosyayı file2.cpp ve file1.cpp dosyalarına eklemek doğru olur

extern const int test;
extern int test2;

Alternatif constolarak file1.cpp dosyasındaki değişkeni açık olarak bildirebilirextern


8

Bu, birden fazla kabul edilmiş yanıtı olan oldukça eski bir soru olsa da, belirsiz bir "tanımsız referans" hatasını nasıl çözeceğinizi paylaşmak istiyorum .

Kütüphanelerin farklı versiyonları

Başvurmak için bir takma ad kullanıyordum std::filesystem::path: dosya sistemi C ++ 17'den beri standart kütüphanede ama programımın C ++ 14'te derlenmesi gerekiyordu, bu yüzden değişken takma ad kullanmaya karar verdim:

#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

Diyelim ki üç dosyam var: main.cpp, file.h, file.cpp:

  • file.h # include's < experimental :: dosya sistemi > ve yukarıdaki kodu içerir
  • file.cpp , file.h uygulaması, # include'ın " file.h " dosyası
  • main.cpp # include'ın < dosya sistemi > ve " dosya.h "

Not farklı kütüphaneler main.cpp ve file.h. kullanılan Main.cpp # , < dosya sistemi > sonrasında "dosya.h" içerdiğinden , kullanılan dosya sistemi sürümü C ++ 17 olanıydı . Programı aşağıdaki komutlarla derlerdim:

$ g++ -g -std=c++17 -c main.cpp-> main.cpp dosyasını main.o dosyasına derler.
$ g++ -g -std=c++17 -c file.cpp-> file.cpp dosyasını ve file.h dosyasını file.o dosyasına derler.
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs-> main.o ve file.o dosyalarını bağlar

Bu şekilde herhangi bir fonksiyon file.o içerdiği ve bu main.o kullanılan gereklipath_t çünkü "tanımsız referansı" hataları verdi main.o sevk std::filesystem::pathama file.o için std::experimental::filesystem::path.

çözüm

Bunu düzeltmek için sadece file.h dosyasındaki <experimental :: filesystem> öğesini <filesystem> olarak değiştirmem gerekiyordu .


5

Paylaşılan kitaplıklara bağlanırken, kullanılan sembollerin gizli olmadığından emin olun.

Gcc'nin varsayılan davranışı, tüm sembollerin görünür olmasıdır. Ancak, çeviri birimleri seçenekle oluşturulduğunda -fvisibility=hidden, __attribute__ ((visibility ("default")))sonuçta paylaşılan nesnede yalnızca ile işaretlenmiş işlevler / simgeler harici olur.

Aradığınız sembollerin harici olup olmadığını kontrol ederek şunları yapabilirsiniz:

# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL 

gizli / yerel semboller, nmküçük harfli sembol türüyle gösterilir ; örneğin t, kod bölümü için T yerine:

nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL

Ayrıca adları demangle nmseçeneği ile de kullanabilirsiniz -C(C ++ kullanıldıysa).

Windows-dll'lere benzer şekilde, genel işlevler bir tanımla işaretlenir, örneğin şu şekilde DLL_PUBLICtanımlanır:

#define DLL_PUBLIC __attribute__ ((visibility ("default")))

DLL_PUBLIC int my_public_function(){
  ...
}

Bu kabaca Windows / MSVC sürümüne karşılık gelir:

#ifdef BUILDING_DLL
    #define DLL_PUBLIC __declspec(dllexport) 
#else
    #define DLL_PUBLIC __declspec(dllimport) 
#endif

Görünürlük hakkında daha fazla bilgi gcc wiki'de bulunabilir.


Bir çeviri birimi -fvisibility=hiddenelde edilen sembollerle derlendiğinde hala harici bağlantıya sahiptir (büyük harf sembol tipiyle gösterilir nm) ve nesne dosyaları statik kitaplıkların bir parçası haline gelirse sorunsuz bir şekilde harici bağlantı için kullanılabilir. Bağlantı yalnızca nesne dosyaları paylaşılan bir kitaplığa bağlandığında yerel olur.

Bir nesne dosyasındaki hangi sembollerin gizli olduğunu bulmak için:

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2

Harici sembolleri görüntülemek için nm -CDveya tuşlarını kullanmalısınız nm -gCD. Ayrıca bkz . GCC wiki'sinde Görünürlük .
jww

2

Farklı mimariler

Şuna benzer bir mesaj görebilirsiniz:

library machine type 'x64' conflicts with target machine type 'X86'

Bu durumda, kullanılabilir semboller, derlemekte olduğunuz mimariden farklı bir mimari içindir.

Visual Studio'da bunun nedeni yanlış "Platform "dur ve uygun olanı seçmeniz veya kitaplığın doğru sürümünü yüklemeniz gerekir.

Linux'ta bunun nedeni yanlış kitaplık klasörü olabilir ( örneğin libyerine lib64).

MacOS'ta, her iki mimariyi de aynı dosyaya gönderme seçeneği vardır. Bağlantının her iki sürümün de orada olmasını beklemesi olabilir, ancak yalnızca biri var. Ayrıca , kütüphanenin alındığı yanlış lib/ lib64klasörle ilgili bir sorun da olabilir .

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.