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.
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.
Yanıtlar:
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] .
- 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]
- 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]
- Kaynak dosya önişleme belirteçlerine (2.5) ve boşluk karakter dizilerine (yorumlar dahil) ayrıştırılır. [SNIR]
- Ö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]
- 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]
- Bitişik dize değişmez belirteçleri birleştirilir.
- 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]
- Çevrilmiş çeviri birimleri ve örnekleme birimleri aşağıdaki gibi birleştirilir: [SNIP]
- 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 a
iç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 .lib
dosya 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 LNK2019
iç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:
#pragma
(Microsoft Visual Studio) kullanırken .lib uzantısını yanlış yazma veya içermemeUNICODE
tanımlarvirtual
yı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 virtual
yö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;
};
virtual
sınıf üyeleriBazı ü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 inline
birden çok tanımlamayı engelleyecek şekilde işaretlenmesi gerekir .
Kullanılıyorsa, kullanılan tüm üye yöntemlerin tanımlanması gerekir.
struct A
{
void foo();
};
void foo() {}
int main()
{
A a;
a.foo();
}
Tanım
void A::foo() {}
static
veri ü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
const
Sı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 const
veri üyeleri için sınıf içinde başlatmaya izin verir .
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
libraryName
Burada 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.so
ancak 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 -l
veya -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 lib
dosya oluşturulur (ortak kullanımda). Sembolleri ayrı bir projede kullanmak için lib
dosyaları proje ayarlarına dahil etmeniz gerekir . Bu, proje özelliklerinin Bağlayıcı bölümünde, içinde yapılır Input -> Additional Dependencies
. ( lib
dosyanın yolu eklenmelidir Linker -> General -> Additional Library Directories
) lib
Dosyayla 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.
gcc main.c
yerine 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 .
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
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.
#includes
değil eklenen kaynak dizinine de kayıp tanımların kategorisinde yer alır.
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 A
bağ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!
"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.
İş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"
}
#ifdef __cplusplus [\n] extern"C" { [\n] #endif
ve #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).
extern "C" { #include <myCppHeader.h> }
.
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.
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
{
};
visibility
ve Windows .def
dosyalarından bahsetmelidir , çünkü bunlar sembol adını ve varlığını da etkiler.
.def
dosya kullanmadım. Bir cevap eklemek veya bunu cevaplamaktan çekinmeyin.
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ı:
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:
Ö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.
Bir kitaplıktaki bazı işlevleri çağırmak istiyorsak, ancak içe aktarma kitaplığı Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
proje 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.
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 .
giriş noktası referansına tanımsız referans WinMain@16
veya benzeri 'olağandışı' main()
başvuru (özelliklegörsel stüdyo).
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 .
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.targets
bu dosyayı bularak ve içindeki tüm v110
bölümleri kopyalayarak düzenledim . Değiştim v110
için v120
de 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ı.
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?
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ı
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
Gözlem
Bu tür bir hatayı nasıl çözebilirim?
Derleyici Zaman Hatası:
Bağlayıcı Zaman Hatası
#pragma once
Derleyiciye, derlenmiş olan geçerli .cpp dosyasına zaten eklenmişse bir başlık eklememesine izin vermek için kullanınGeç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:
Modern bağlayıcıların çoğu, değişen derecelerde yazdırılan ayrıntılı bir seçenek içerir;
Gcc ve clang için; genellikle komut satırına -v -Wl,--verbose
veya -v -Wl,-v
komut satırını eklersiniz . Daha fazla ayrıntıyı burada bulabilirsiniz;
MSVC için /VERBOSE
(özellikle /VERBOSE:LIB
) link komut satırına eklenir.
/VERBOSE
Bağlayıcı seçeneğindeki MSDN sayfası .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.
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.
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 -ltbb
do yerine/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
libfoo
bağlıdır libbar
, sonra bağlantı doğru koyar libfoo
öncelibbar
.undefined reference to
hataları.#include
ve aslında bağladığınız kütüphanelerde tanımlanır.Örnekler C cinsindendir. Aynı şekilde C ++ olabilirler.
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.a
ve 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
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 libz
ve 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'
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-config
varyasyonunu doğru şekilde bağlayın:
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib)
$ ./eg2
1.2.8
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_lib
bağlantı sırasında bulduğunda ve bunun statik kitaplığa ./libmy_lib.a
baş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.a
yani 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.o
yalnızca programınızın başvurduğunu zaten biliyorsa, programa hw
zaten 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.o
kitaplığı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 hw
vardı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_lib
eg1.o
hw
eg1.o
Bu yüzden linker my_lib.o
programa eklenmez ve bunun için başka kullanımı yoktur libmy_lib.a
.
Sonra bulur eg1.o
ve program olarak ekler. Bağlantı dizisindeki bir nesne dosyası her zaman programa eklenir. Şimdi, program bir referans yapar hw
ve bir tanımı içermez hw
; ancak bağlantı sırasında eksik tanım sağlayabilecek hiçbir şey kalmaz. Referans hw
kadar 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 main
ve 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++
, gfortran
sistem bağlayıcı (talimat vs) ld
bir bağlantısını paylaşılan kütüphanelere) olarak ihtiyaç duyulan bazda. Bu dağıtımlardan birine sahipsiniz.
Bu, linker -lz
bağlantı sırasında bulduğunda ve bunun paylaşılan kütüphaneye (örneğin) /usr/lib/x86_64-linux-gnu/libz.so
atı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 libz
ve 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ı libz
her 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
libz
başvurduğunda, bu başvuru libz
aynı 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 zlibVersion
sadece 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 -lz
vardır hiçbir programda hiçbir şeye referanslar: hepsi içindedir eg2.o
henü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.
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 -lfoo
onların bağlantı isteği ve kararı gidiyor içinde /some/where/libfoo.a
veya /some/where/libfoo.so
ve 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ı.
Sadece yaparsam:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
şüphesiz gcc önce derlemek eg1.c
ve 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, gcc
derleme + 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 mı 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':
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.
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 T
kullanılan her tip Foo
iç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ü Foo
tü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)
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.h
ve header2.h
kendi 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.
İki projem var: graphics.lib
ve 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, vec3
derleyici 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).
Visual Studio kullanıyorsanız, DUMPBIN arkadaşınızdır. Eminim diğer derleyiciler benzer araçlara sahiptir.
Süreç şöyle gider:
[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!
UNICODE
tanımlarWindows UNICODE yapı ile inşa edilmiştir TCHAR
vb olarak tanımlanan wchar_t
ile bina değil ne zaman vb UNICODE
ile yapı olarak tanımlanan TCHAR
olarak tanımlanan char
bu vb UNICODE
ve _UNICODE
tanımlar tüm etkileyen " T
" dize türleri ; LPTSTR
, LPCTSTR
Ve bunların elk.
UNICODE
Tanı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
; char
vs wchar_t
.
Hata genellikle bir char
veya wchar_t
türetilmiş tipte bir değer içerir , bunlar da std::basic_string<>
vb. İçerebilir. Koddaki etkilenen işleve göz atarken, genellikle TCHAR
veyastd::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 .
Bu iki yöntemle de yapılabilir;
#define UNICODE
#define _UNICODE
Veya proje ayarlarında;
Proje Özellikleri> Genel> Proje Varsayılanları> Karakter Kümesi
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.
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).
const
Değişken bildirimlerinde / tanımlarında eksik "extern" (yalnızca C ++)C'den gelen insanlar için C ++ 'da global const
değ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 static
anahtar 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 const
olarak file1.cpp dosyasındaki değişkeni açık olarak bildirebilirextern
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 .
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:
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::path
ama file.o için std::experimental::filesystem::path
.
Bunu düzeltmek için sadece file.h dosyasındaki <experimental :: filesystem> öğesini <filesystem> olarak değiştirmem gerekiyordu .
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, nm
küçü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 nm
seç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_PUBLIC
tanı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=hidden
elde 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
nm -CD
veya tuşlarını kullanmalısınız nm -gCD
. Ayrıca bkz . GCC wiki'sinde Görünürlük .
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 lib
yerine 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
/ lib64
klasörle ilgili bir sorun da olabilir .