Apa yang dimaksud dengan referensi simbol eksternal / kesalahan yang belum ditentukan dan bagaimana cara memperbaikinya?

1493

Apa yang dimaksud dengan kesalahan simbol eksternal / referensi yang tidak ditentukan? Apa penyebab umum dan bagaimana cara memperbaikinya?

Jangan ragu untuk mengedit / menambahkan milik Anda.

Luchian Grigore
sumber
@LuchianGrigore 'jangan ragu untuk menambahkan jawaban' Saya lebih suka menambahkan tautan yang relevan (IMHO) jawaban utama Anda, jika Anda ingin mengizinkan.
πάντα ῥεῖ
19
Pertanyaan ini terlalu umum untuk mengakui jawaban yang bermanfaat. Saya tidak percaya dengan 1000+ reputasi orang yang mengomentari ini, tanpa menjawab pertanyaan itu. Sementara itu saya melihat banyak pertanyaan yang masuk akal dan sangat berguna bagi saya yang tertutup.
Albert van der Horst
10
@AlbertvanderHorst "Pertanyaan ini terlalu umum untuk mengakui jawaban yang bermanfaat" Apakah jawaban di bawah ini tidak berguna?
LF
4
Mereka mungkin berguna, sama seperti banyak jawaban untuk pertanyaan yang ditandai terlalu umum.
Albert van der Horst
1
Saya ingin melihat contoh minimal yang dapat direproduksi sebagai sesuatu yang kami minta dari sebagian besar pengguna baru, jujur. Saya tidak bermaksud apa-apa dengan itu, itu hanya - kita tidak bisa mengharapkan orang untuk mengikuti aturan yang tidak kita lakukan pada diri kita sendiri.
Danilo

Jawaban:

850

Mengkompilasi program C ++ berlangsung dalam beberapa langkah, seperti yang ditentukan oleh 2.2 (kredit ke Keith Thompson untuk referensi) :

Diutamakan di antara aturan penerjemahan sintaks ditentukan oleh fase berikut [lihat catatan kaki] .

  1. Karakter file sumber fisik dipetakan, dengan cara yang ditentukan implementasi, ke set karakter sumber dasar (memperkenalkan karakter baris baru untuk indikator end-of-line) jika perlu. [MENGGUNTING]
  2. Setiap instance karakter backslash (\) segera diikuti oleh karakter baris baru dihapus, menyambungkan baris sumber fisik untuk membentuk garis sumber logis. [MENGGUNTING]
  3. File sumber didekomposisi menjadi token preprocessing (2.5) dan urutan karakter spasi putih (termasuk komentar). [MENGGUNTING]
  4. Arahan preprocessing dieksekusi, invokasi makro diperluas, dan ekspresi operator unilateral dieksekusi. [MENGGUNTING]
  5. Setiap set karakter sumber anggota dalam literal karakter atau string literal, serta setiap urutan pelarian dan nama karakter universal dalam literal karakter atau string string non-mentah, dikonversi ke anggota yang sesuai dari set karakter eksekusi; [MENGGUNTING]
  6. Token string string yang berdekatan disatukan.
  7. Karakter spasi putih yang memisahkan token tidak lagi signifikan. Setiap token preprocessing dikonversi menjadi token. (2.7). Token yang dihasilkan dianalisis secara sintaksis dan semantik serta diterjemahkan sebagai unit terjemahan. [MENGGUNTING]
  8. Unit terjemahan yang diterjemahkan dan unit instantiation digabungkan sebagai berikut: [SNIP]
  9. Semua referensi entitas eksternal diselesaikan. Komponen perpustakaan ditautkan untuk memenuhi referensi eksternal ke entitas yang tidak didefinisikan dalam terjemahan saat ini. Semua output penerjemah tersebut dikumpulkan ke dalam gambar program yang berisi informasi yang diperlukan untuk eksekusi dalam lingkungan pelaksanaannya. (penekanan milikku)

[catatan kaki] Implementasi harus berperilaku seolah-olah fase terpisah ini terjadi, meskipun dalam praktiknya fase yang berbeda mungkin dilipat bersama.

Kesalahan yang ditentukan terjadi selama tahap kompilasi terakhir ini, paling sering disebut sebagai penautan. Ini pada dasarnya berarti bahwa Anda mengkompilasi sekelompok file implementasi ke file objek atau pustaka dan sekarang Anda ingin membuatnya bekerja bersama.

Katakanlah Anda mendefinisikan simbol adalam a.cpp. Sekarang, b.cpp mendeklarasikan simbol itu dan menggunakannya. Sebelum menghubungkan, itu hanya mengasumsikan bahwa simbol itu didefinisikan di suatu tempat , tetapi belum peduli di mana. Fase penautan bertanggung jawab untuk menemukan simbol dan menautkannya dengan benar b.cpp(yah, sebenarnya ke objek atau pustaka yang menggunakannya).

Jika Anda menggunakan Microsoft Visual Studio, Anda akan melihat bahwa proyek menghasilkan .libfile. Ini berisi tabel simbol yang diekspor, dan tabel simbol yang diimpor. Simbol yang diimpor diselesaikan terhadap perpustakaan yang Anda tautkan, dan simbol yang diekspor disediakan untuk perpustakaan yang menggunakannya .lib(jika ada).

Mekanisme serupa ada untuk kompiler / platform lainnya.

Pesan kesalahan umum adalah error LNK2001, error LNK1120, error LNK2019untuk Microsoft Visual Studio dan undefined reference to symbolName untuk GCC .

Kode:

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

akan menghasilkan kesalahan berikut dengan GCC :

/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

dan kesalahan serupa dengan Microsoft Visual Studio :

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

Penyebab umum meliputi:

Luchian Grigore
sumber
16
Secara pribadi, saya pikir pesan kesalahan MS linker sama mudah dibaca seperti kesalahan GCC. Mereka juga memiliki keuntungan dengan memasukkan nama yang rusak dan tidak bercampur untuk eksternal yang belum terselesaikan. Memiliki nama yang rusak dapat membantu ketika Anda perlu melihat perpustakaan atau file objek secara langsung untuk melihat apa masalahnya (misalnya, ketidakcocokan konvensi pemanggilan). Juga, saya tidak yakin versi MSVC apa yang menghasilkan kesalahan di sini, tetapi versi yang lebih baru menyertakan nama (baik yang rusak maupun yang tidak berubah) dari fungsi yang mengacu pada simbol eksternal yang belum terselesaikan.
Michael Burr
5
David Drysdale menulis artikel yang bagus tentang bagaimana linker bekerja: Beginner's Guide to Linkers . Mengingat topik pertanyaan ini, saya pikir itu mungkin terbukti bermanfaat.
Pressacco
@TankorSmash Gunakan gcc? MinGW lebih tepatnya.
doug65536
1
@luchian alangkah baiknya jika Anda menambahkan yang benar, memperbaiki kesalahan di atas
Phalgun
178

Anggota kelas:

Destroyer murni virtualmembutuhkan implementasi.

Mendeklarasikan destruktor murni masih mengharuskan Anda untuk mendefinisikannya (tidak seperti fungsi biasa):

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

Ini terjadi karena destruktor kelas dasar dipanggil ketika objek dihancurkan secara implisit, sehingga diperlukan definisi.

virtual metode harus diimplementasikan atau didefinisikan sebagai murni.

Ini mirip dengan non- virtualmetode tanpa definisi, dengan alasan tambahan bahwa deklarasi murni menghasilkan dummy vtable dan Anda mungkin mendapatkan kesalahan tautan tanpa menggunakan fungsi:

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

Agar ini berfungsi, nyatakan X::foo()sebagai murni:

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

virtualAnggota non- kelas

Beberapa anggota perlu didefinisikan bahkan jika tidak digunakan secara eksplisit:

struct A
{ 
    ~A();
};

Berikut ini akan menghasilkan kesalahan:

A a;      //destructor undefined

Implementasi dapat inline, dalam definisi kelas itu sendiri:

struct A
{ 
    ~A() {}
};

atau di luar:

A::~A() {}

Jika implementasi di luar definisi kelas, tetapi di header, metode harus ditandai inlineuntuk mencegah definisi berganda.

Semua metode anggota yang digunakan perlu didefinisikan jika digunakan.

Kesalahan umum adalah lupa untuk memenuhi syarat nama:

struct A
{
   void foo();
};

void foo() {}

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

Definisi seharusnya

void A::foo() {}

staticanggota data harus didefinisikan di luar kelas dalam satu unit terjemahan :

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

Inisialisasi dapat disediakan untuk anggota static constdata tipe integral atau enumerasi dalam definisi kelas; Namun, penggunaan odr anggota ini masih akan memerlukan definisi lingkup namespace seperti dijelaskan di atas. C ++ 11 memungkinkan inisialisasi di dalam kelas untuk semua static constanggota data.

Luchian Grigore
sumber
Hanya berpikir Anda mungkin ingin menekankan bahwa melakukan keduanya adalah mungkin, dan dtor sebenarnya bukan pengecualian. (Tidak jelas dari kata-kata Anda pada pandangan pertama.)
Deduplicator
112

Gagal menautkan file pustaka / objek yang sesuai atau mengkompilasi file implementasi

Secara umum, setiap unit terjemahan akan menghasilkan file objek yang berisi definisi simbol yang didefinisikan dalam unit terjemahan itu. Untuk menggunakan simbol-simbol itu, Anda harus menautkan file-file objek tersebut.

Di bawah gcc Anda akan menentukan semua file objek yang akan dihubungkan bersama di baris perintah, atau mengkompilasi file implementasi bersama.

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

Di libraryNamesini hanya nama telanjang perpustakaan, tanpa tambahan khusus platform. Jadi misal di Linux library file biasanya dipanggil libfoo.sotetapi Anda hanya akan menulis -lfoo. Di Windows file yang sama mungkin dipanggil foo.lib, tetapi Anda akan menggunakan argumen yang sama. Anda mungkin harus menambahkan direktori tempat file-file itu dapat ditemukan menggunakan -L‹directory›. Pastikan untuk tidak menulis spasi setelah -latau -L.

Untuk XCode : Tambahkan Jalur Pencarian Header Pengguna -> tambahkan Path Pencarian Perpustakaan -> drag dan drop referensi perpustakaan yang sebenarnya ke dalam folder proyek.

Di bawah MSVS , file yang ditambahkan ke proyek secara otomatis akan menghubungkan file objeknya bersama dan alib file akan dihasilkan (dalam penggunaan umum). Untuk menggunakan simbol dalam proyek terpisah, Anda harus memasukkan libfile dalam pengaturan proyek. Ini dilakukan di bagian Linker dari properti proyek, di Input -> Additional Dependencies. (path ke libfile harus ditambahkan Linker -> General -> Additional Library Directories) Ketika menggunakan perpustakaan pihak ketiga yang disediakan dengan libfile, kegagalan untuk melakukannya biasanya menghasilkan kesalahan.

Bisa juga terjadi bahwa Anda lupa untuk menambahkan file ke kompilasi, dalam hal ini file objek tidak akan dihasilkan. Di gcc Anda akan menambahkan file ke baris perintah. Dalam MSVS, menambahkan file ke proyek akan membuatnya mengkompilasinya secara otomatis (walaupun file dapat secara manual dikecualikan secara individual dari build).

Dalam pemrograman Windows, tanda yang menunjukkan bahwa Anda tidak menautkan pustaka yang diperlukan adalah bahwa nama simbol yang belum terselesaikan dimulai dengan __imp_. Cari nama fungsi dalam dokumentasi, dan itu harus mengatakan perpustakaan mana yang perlu Anda gunakan. Misalnya, MSDN meletakkan informasi dalam kotak di bagian bawah setiap fungsi di bagian yang disebut "Perpustakaan".

Luchian Grigore
sumber
1
Akan lebih baik jika Anda bisa secara eksplisit menutupi kesalahan umum gcc main.calih-alih gcc main.c other.c (yang sering dilakukan pemula sebelum proyek mereka menjadi begitu besar untuk membangun file .o).
MM
101

Dideklarasikan tetapi tidak mendefinisikan variabel atau fungsi.

Deklarasi variabel tipikal adalah

extern int x;

Karena ini hanya deklarasi, diperlukan satu definisi . Definisi yang sesuai adalah:

int x;

Misalnya, berikut ini akan menghasilkan kesalahan:

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

Komentar serupa berlaku untuk fungsi. Mendeklarasikan fungsi tanpa mendefinisikannya menyebabkan kesalahan:

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

Hati-hati bahwa fungsi yang Anda implementasikan sama persis dengan yang Anda nyatakan. Misalnya, Anda mungkin memiliki cv-kualifikasi yang tidak cocok:

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)

Contoh ketidakcocokan lainnya termasuk

  • Fungsi / variabel dideklarasikan dalam satu namespace, didefinisikan dalam yang lain.
  • Fungsi / variabel dinyatakan sebagai anggota kelas, didefinisikan sebagai global (atau sebaliknya).
  • Jenis fungsi pengembalian, nomor parameter dan jenis, dan konvensi pemanggilan tidak semua persis setuju.

Pesan kesalahan dari kompiler akan sering memberi Anda deklarasi penuh variabel atau fungsi yang dideklarasikan tetapi tidak pernah didefinisikan. Bandingkan dengan erat dengan definisi yang Anda berikan. Pastikan setiap detail cocok.

Luchian Grigore
sumber
Dalam VS, file cpp yang cocok dengan header yang #includestidak ditambahkan ke direktori sumber juga termasuk dalam kategori definisi yang hilang.
Laurie Stearn
86

Urutan di mana pustaka terkait yang saling tergantung ditentukan salah.

Urutan pustaka yang ditautkan tidak masalah jika pustaka bergantung satu sama lain. Secara umum, jika pustaka Atergantung pada pustaka B, maka libA HARUS muncul sebelumnya libBdi bendera tautan.

Sebagai contoh:

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

Buat perpustakaan:

$ 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

Menyusun:

$ 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

Jadi untuk mengulangi lagi, order TIDAK peduli!

Svalorzen
sumber
Saya ingin tahu fakta bahwa dalam kasus saya, saya punya satu file objek yang tergantung pada perpustakaan bersama. Saya harus memodifikasi Makefile dan meletakkan perpustakaan SETELAH objek dengan gcc 4.8.4 di Debian. Pada Centos 6.5 dengan gcc 4.4 Makefile bekerja tanpa masalah.
Marco Sulla
74

apa yang dimaksud dengan "referensi eksternal / simbol eksternal yang tidak ditentukan"

Saya akan mencoba menjelaskan apa yang dimaksud dengan "referensi tidak ditentukan / simbol eksternal yang tidak terselesaikan".

Catatan: saya menggunakan g ++ dan Linux dan semua contoh untuk itu

Misalnya kita punya beberapa kode

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

dan

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

Buat file objek

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

Setelah fase assembler, kami memiliki file objek, yang berisi simbol apa pun untuk diekspor. Lihatlah simbolnya

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

Saya telah menolak beberapa baris dari keluaran, karena itu tidak masalah

Jadi, kita lihat ikuti simbol untuk diekspor.

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

src2.cpp tidak mengekspor apa pun dan kami belum melihat simbolnya

Tautkan file objek kami

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

dan jalankan

$ ./prog
123

Linker melihat simbol yang diekspor dan menautkannya. Sekarang kami mencoba untuk menghapus komentar pada baris di src2.cpp seperti di sini

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

dan membangun kembali file objek

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

OK (tidak ada kesalahan), karena kami hanya membangun file objek, menghubungkan belum dilakukan. Coba tautkan

$ 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

Itu terjadi karena local_var_name kami statis, yaitu tidak terlihat untuk modul lain. Sekarang lebih dalam. Dapatkan output fase terjemahan

$ 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

Jadi, kami telah melihat tidak ada label untuk local_var_name, itu sebabnya linker belum menemukannya. Tapi kami adalah peretas :) dan kami bisa memperbaikinya. Buka src1.s di editor teks Anda dan ubah

.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4

untuk

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

yaitu Anda harus memiliki seperti di bawah ini

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

kami telah mengubah visibilitas local_var_name dan menetapkan nilainya menjadi 456789. Cobalah untuk membangun file objek darinya

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

ok, lihat output readelf (simbol)

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

sekarang local_var_name memiliki Bind GLOBAL (tadinya LOCAL)

tautan

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

dan jalankan

$ ./prog 
123456789

ok, kita hack :)

Jadi, sebagai hasilnya - "referensi yang tidak terdefinisi / kesalahan simbol eksternal yang tidak terselesaikan" terjadi ketika linker tidak dapat menemukan simbol global dalam file objek.

Kastaneda
sumber
71

Simbol didefinisikan dalam program C dan digunakan dalam kode C ++.

Fungsi (atau variabel) void foo()didefinisikan dalam program C dan Anda mencoba menggunakannya dalam program C ++:

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

Linker C ++ mengharapkan nama-nama akan di-mangble, jadi Anda harus mendeklarasikan fungsinya sebagai:

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

Secara ekuivalen, alih-alih didefinisikan dalam program C, fungsi (atau variabel) void foo()didefinisikan dalam C ++ tetapi dengan tautan C:

extern "C" void foo();

dan Anda mencoba menggunakannya dalam program C ++ dengan tautan C ++.

Jika seluruh perpustakaan disertakan dalam file header (dan dikompilasi sebagai kode C); termasuk harus sebagai berikut;

extern "C" {
    #include "cheader.h"
}
Luchian Grigore
sumber
5
Atau sebaliknya, Jika Anda mengembangkan perpustakaan C, aturan yang bagus adalah untuk melindungi file header dengan mengelilingi semua deklarasi yang diekspor dengan #ifdef __cplusplus [\n] extern"C" { [\n] #endifdan #ifdef __cplusplus [\n] } [\n] #endif( [\n]menjadi carriage return yang asli tetapi saya tidak dapat menulis ini dengan benar dalam komentar).
Bentoy13
Seperti dalam komentar di atas, bagian 'Membuat
Tajuk
Sangat membantu saya! Ini adalah kasus saya ketika saya mencari pertanyaan ini
knightofhorizon
Ini juga dapat terjadi jika Anda termasuk C ++ file header yang biasa Anda dengan kecelakaan dikelilingi oleh extern C : extern "C" { #include <myCppHeader.h> }.
ComFreek
68

Jika semuanya gagal, kompilasi ulang.

Saya baru-baru ini bisa menyingkirkan kesalahan eksternal yang tidak terselesaikan di Visual Studio 2012 hanya dengan mengkompilasi ulang file yang menyinggung. Ketika saya membangun kembali, kesalahan hilang.

Ini biasanya terjadi ketika dua (atau lebih) perpustakaan memiliki ketergantungan siklik. Library A berupaya menggunakan simbol dalam B.lib dan library B mencoba menggunakan simbol dari A.lib. Tidak ada untuk memulai. Ketika Anda mencoba mengompilasi A, langkah tautan akan gagal karena tidak dapat menemukan B.lib. A.lib akan dihasilkan, tetapi tidak ada dll. Anda kemudian mengkompilasi B, yang akan berhasil dan menghasilkan B.lib. Mengkompilasi ulang A sekarang akan berfungsi karena B.lib sekarang ditemukan.

sgryzko
sumber
1
Benar - ini terjadi ketika perpustakaan memiliki ketergantungan siklik.
Luchian Grigore
1
Saya memperluas jawaban Anda dan menghubungkannya di jawaban utama. Terima kasih.
Luchian Grigore
57

Mengimpor / mengekspor metode / kelas secara salah di seluruh modul / dll (khusus kompiler).

MSVS mengharuskan Anda untuk menentukan simbol mana yang akan diekspor dan diimpor menggunakan __declspec(dllexport)dan __declspec(dllimport).

Fungsi ganda ini biasanya diperoleh melalui penggunaan makro:

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

Makro THIS_MODULEhanya akan didefinisikan dalam modul yang mengekspor fungsi. Dengan begitu, deklarasi:

DLLIMPEXP void foo();

meluas ke

__declspec(dllexport) void foo();

dan memberitahu kompiler untuk mengekspor fungsi, karena modul saat ini berisi definisinya. Ketika menyertakan deklarasi dalam modul yang berbeda, itu akan diperluas ke

__declspec(dllimport) void foo();

dan memberi tahu kompiler bahwa definisi ada di salah satu perpustakaan yang Anda tautkan (juga lihat 1) ).

Anda dapat similary kelas impor / ekspor:

class DLLIMPEXP X
{
};
Luchian Grigore
sumber
2
Agar lengkap, jawaban ini harus menyebutkan file GCC visibilitydan Windows .def, karena ini juga mempengaruhi nama simbol dan keberadaannya.
rubenvb
@rubenvb Saya sudah lama tidak menggunakan .deffile. Jangan ragu untuk menambahkan jawaban atau mengedit yang ini.
Luchian Grigore
57

Ini adalah salah satu pesan kesalahan yang paling membingungkan yang setiap programmer VC ++ telah melihat berulang kali. Mari kita perjelas dulu.

A. Apa itu simbol? Singkatnya, simbol adalah nama. Itu bisa berupa nama variabel, nama fungsi, nama kelas, nama typedef, atau apa pun kecuali nama dan tanda yang termasuk dalam bahasa C ++. Ini didefinisikan pengguna atau diperkenalkan oleh perpustakaan dependensi (yang ditentukan pengguna lain).

B. Apa itu eksternal? Dalam VC ++, setiap file sumber (.cpp, .c, dll.) Dianggap sebagai unit terjemahan, kompiler mengkompilasi satu unit pada suatu waktu, dan menghasilkan satu file objek (.obj) untuk unit terjemahan saat ini. (Perhatikan bahwa setiap file header yang menyertakan file sumber ini akan diproses sebelumnya dan akan dianggap sebagai bagian dari unit terjemahan ini) Segala sesuatu di dalam unit terjemahan dianggap sebagai internal, yang lainnya dianggap sebagai eksternal. Di C ++, Anda dapat mereferensikan simbol eksternal dengan menggunakan kata kunci like extern, __declspec (dllimport)dan sebagainya.

C. Apa itu "tekad"? Resolve adalah istilah yang menghubungkan waktu. Dalam menghubungkan waktu, linker mencoba menemukan definisi eksternal untuk setiap simbol dalam file objek yang tidak dapat menemukan definisi secara internal. Ruang lingkup proses pencarian ini termasuk:

  • Semua file objek yang dihasilkan dalam waktu kompilasi
  • Semua perpustakaan (.lib) yang secara eksplisit atau implisit ditentukan sebagai dependensi tambahan dari aplikasi bangunan ini.

Proses pencarian ini disebut tekad.

D. Akhirnya, mengapa Simbol Eksternal yang Tidak Terselesaikan? Jika tautan tidak dapat menemukan definisi eksternal untuk simbol yang tidak memiliki definisi secara internal, itu akan melaporkan kesalahan Simbol Eksternal yang Tidak Terselesaikan.

E. Kemungkinan penyebab LNK2019 : Kesalahan Simbol Eksternal yang Tidak Terselesaikan. Kita sudah tahu bahwa kesalahan ini disebabkan karena linker gagal menemukan definisi simbol eksternal, kemungkinan penyebabnya dapat diurutkan sebagai:

  1. Definisi ada

Misalnya, jika kita memiliki fungsi yang disebut foo didefinisikan dalam a.cpp:

int foo()
{
    return 0;
}

Di b.cpp kami ingin memanggil fungsi foo, jadi kami menambahkan

void foo();

untuk mendeklarasikan function foo (), dan memanggilnya di badan fungsi lain, katakan bar():

void bar()
{
    foo();
}

Sekarang ketika Anda membangun kode ini, Anda akan mendapatkan kesalahan LNK2019 dengan mengeluh bahwa foo adalah simbol yang belum terselesaikan. Dalam hal ini, kita tahu bahwa foo () memiliki definisi dalam a.cpp, tetapi berbeda dari yang kita panggil (nilai pengembalian berbeda). Ini adalah kasus yang ada definisi.

  1. Definisi tidak ada

Jika kami ingin memanggil beberapa fungsi di pustaka, tetapi pustaka impor tidak ditambahkan ke daftar ketergantungan tambahan (ditetapkan dari Project | Properties | Configuration Properties | Linker | Input | Additional Dependency:) pengaturan proyek Anda. Sekarang tautan akan melaporkan LNK2019 karena definisi tersebut tidak ada dalam lingkup pencarian saat ini.

Nima Soroush
sumber
1
Terima kasih atas penjelasan sistematisnya. Saya bisa mengerti jawaban lain di sini setelah membaca yang ini.
displayName
56

Implementasi template tidak terlihat.

Templat yang tidak dikhususkan harus memiliki definisi yang dapat dilihat oleh semua unit terjemahan yang menggunakannya. Itu berarti Anda tidak dapat memisahkan definisi templat ke file implementasi. Jika Anda harus memisahkan implementasi, solusi yang biasa adalah memiliki implfile yang Anda sertakan di akhir header yang menyatakan templat. Situasi umum adalah:

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

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

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

Untuk memperbaiki ini, Anda harus memindahkan definisi X::foo ke file header atau tempat yang terlihat oleh unit terjemahan yang menggunakannya.

Templat khusus dapat diimplementasikan dalam file implementasi dan implementasinya tidak harus terlihat, tetapi spesialisasi harus dinyatakan sebelumnya.

Untuk penjelasan lebih lanjut dan solusi lain yang mungkin (instantiasi eksplisit) lihat pertanyaan dan jawaban ini .

Luchian Grigore
sumber
1
Informasi lebih lanjut untuk "templat harus ditentukan di header" dapat ditemukan di stackoverflow.com/questions/495021
PlasmaHH
41

referensi tidak terdefinisi ke WinMain@16atau referensi titik masuk 'tidak biasa' yang serupamain() (terutama untuk).

Anda mungkin telah kehilangan untuk memilih jenis proyek yang tepat dengan IDE Anda yang sebenarnya. IDE mungkin ingin mengikat misalnya proyek Aplikasi Windows ke fungsi titik masuk tersebut (seperti yang ditentukan dalam referensi yang hilang di atas), daripada int main(int argc, char** argv);tanda tangan yang umum digunakan .

Jika IDE Anda mendukung Proyek Konsol Biasa, Anda mungkin ingin memilih jenis proyek ini, alih-alih proyek aplikasi windows.


Berikut adalah case1 dan case2 ditangani secara lebih rinci dari masalah dunia nyata .

πάντα ῥεῖ
sumber
2
Tidak bisa tidak menunjukkan pertanyaan ini dan fakta bahwa ini lebih sering disebabkan oleh tidak memiliki fungsi utama sama sekali daripada tidak memiliki WinMain. Program C ++ yang valid perlu a main.
chris
36

Juga jika Anda menggunakan perpustakaan pihak ke-3 pastikan Anda memiliki binari 32/64 bit yang benar

Dula
sumber
34

Microsoft menawarkan #pragmareferensi perpustakaan yang benar pada waktu tautan;

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

Selain jalur perpustakaan termasuk direktori perpustakaan, ini harus menjadi nama lengkap perpustakaan.

Niall
sumber
34

Paket Visual Studio NuGet perlu diperbarui untuk versi toolset baru

Saya baru saja mengalami masalah ini mencoba menautkan libpng dengan Visual Studio 2013. Masalahnya adalah bahwa file paket hanya memiliki perpustakaan untuk Visual Studio 2010 dan 2012.

Solusi yang benar adalah dengan harapan pengembang merilis paket yang diperbarui dan kemudian memutakhirkan, tetapi itu berhasil bagi saya dengan meretas dalam pengaturan ekstra untuk VS2013, menunjuk pada file perpustakaan VS2012.

Saya mengedit paket (dalam packagesfolder di dalam direktori solusi) dengan menemukan packagename\build\native\packagename.targetsdan di dalam file itu, menyalin semua v110bagian. Saya mengubah v110ke v120dalam bidang kondisi hanya sangat berhati-hati untuk meninggalkan semua path nama file v110. Ini hanya memungkinkan Visual Studio 2013 untuk menautkan ke perpustakaan untuk 2012, dan dalam hal ini, berhasil.

Malvine
sumber
1
Ini sepertinya terlalu spesifik - mungkin utas baru akan menjadi tempat yang lebih baik untuk jawaban ini.
Luchian Grigore
3
@LuchianGrigore: Saya memang ingin memposting di sini karena pertanyaan itu adalah masalah ini, tapi itu ditandai sebagai duplikat dari pertanyaan ini jadi saya tidak bisa menjawabnya di sana. Jadi saya mengirim jawaban saya di sini.
Malvineous
Pertanyaan itu sudah memiliki jawaban yang diterima. Itu ditandai sebagai duplikat karena penyebab umum tercantum di atas. Apa yang akan terjadi jika kami memiliki jawaban di sini untuk setiap masalah dengan perpustakaan yang tidak termasuk?
Luchian Grigore
6
@LuchianGrigore: Masalah ini tidak khusus untuk perpustakaan, ini memengaruhi semua perpustakaan menggunakan sistem manajemen paket Visual Studio. Saya kebetulan menemukan pertanyaan lain karena kami berdua memiliki masalah dengan libpng. Saya juga punya masalah yang sama (dengan solusi yang sama) untuk libxml2, libiconv dan glew. Pertanyaan itu adalah tentang masalah dengan sistem manajemen paket Visual Studio, dan jawaban saya menjelaskan alasannya dan memberikan solusi. Seseorang baru saja melihat "eksternal yang tidak terselesaikan" dan menganggap itu adalah masalah tautan standar ketika sebenarnya itu adalah masalah manajemen paket.
Malvineous
34

Misalkan Anda memiliki proyek besar yang ditulis dalam c ++ yang memiliki ribuan file .cpp dan ribuan file .h. Dan katakanlah proyek ini juga tergantung pada sepuluh perpustakaan statis. Katakanlah kita berada di Windows dan kami membangun proyek kami di Visual Studio 20xx. Ketika Anda menekan Ctrl + F7 Visual Studio untuk mulai mengkompilasi seluruh solusi (misalkan kita hanya memiliki satu proyek dalam solusi)

Apa arti kompilasi?

  • Visual Studio mencari ke dalam file .vcxproj dan mulai mengkompilasi setiap file yang memiliki ekstensi .cpp. Urutan kompilasi tidak ditentukan. Jadi Anda tidak boleh berasumsi bahwa file main.cpp dikompilasi terlebih dahulu
  • Jika file .cpp tergantung pada file .h tambahan untuk menemukan simbol yang mungkin atau mungkin tidak didefinisikan dalam file .cpp
  • Jika ada satu file .cpp di mana kompiler tidak dapat menemukan satu simbol, kesalahan waktu kompilator memunculkan pesan Simbol x tidak dapat ditemukan
  • Untuk setiap file dengan ekstensi .cpp dihasilkan file objek .o dan juga Visual Studio menulis output dalam file bernama ProjectName.Cpp.Clean.txt yang berisi semua file objek yang harus diproses oleh linker.

Langkah kedua kompilasi dilakukan oleh Linker.Linker harus menggabungkan semua file objek dan akhirnya membangun output (yang dapat dieksekusi atau perpustakaan)

Langkah-langkah dalam Menghubungkan proyek

  • Parsir semua file objek dan temukan definisi yang hanya dideklarasikan dalam header (mis .: Kode satu metode kelas seperti yang disebutkan dalam jawaban sebelumnya, atau acara inisialisasi variabel statis yang menjadi anggota di dalam kelas)
  • Jika satu simbol tidak dapat ditemukan dalam file objek, ia juga dicari di Perpustakaan Tambahan. Untuk menambahkan perpustakaan baru ke proyek Properti konfigurasi -> Direktori VC ++ -> Direktori Direktori dan di sini Anda menentukan folder tambahan untuk mencari perpustakaan dan properti Konfigurasi -> Linker -> Input untuk menentukan nama perpustakaan. -Jika Linker tidak dapat menemukan simbol yang Anda tulis dalam satu .cpp ia menimbulkan kesalahan waktu linker yang mungkin terdengar seperti error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

Pengamatan

  1. Setelah Linker menemukan satu simbol, dia tidak mencari di perpustakaan lain untuk itu
  2. Urutan menghubungkan perpustakaan tidak masalah .
  3. Jika Linker menemukan simbol eksternal dalam satu perpustakaan statis, ia menyertakan simbol dalam output proyek. Namun, jika perpustakaan dibagikan (dinamis) ia tidak menyertakan kode (simbol) dalam output, tetapi Run-Time crash mungkin terjadi

Bagaimana Mengatasi kesalahan semacam ini

Kesalahan Waktu Kompilator:

  • Pastikan Anda menulis sintaksis proyek c ++ Anda dengan benar.

Kesalahan Waktu Linker

  • Tentukan semua simbol Anda yang Anda nyatakan dalam file header Anda
  • Gunakan #pragma onceuntuk memungkinkan kompiler untuk tidak memasukkan satu header jika sudah termasuk dalam .cpp saat ini yang dikompilasi
  • Pastikan bahwa perpustakaan eksternal Anda tidak mengandung simbol yang dapat bertentangan dengan simbol lain yang Anda tetapkan dalam file header Anda
  • Saat Anda menggunakan templat untuk memastikan Anda menyertakan definisi setiap fungsi templat dalam file header untuk memungkinkan kompiler menghasilkan kode yang sesuai untuk setiap instantiations.
eFarzad
sumber
Bukankah jawaban Anda khusus untuk studio visual? Pertanyaannya tidak menentukan alat IDE / compiler sehingga membuat jawaban Anda tidak berguna untuk bagian non-visual-studio.
Victor Polevoy
Kamu benar . Tetapi setiap dalam setiap proses IDE kompilasi / penautan sedang dilakukan sedikit berbeda. Tapi file diproses persis sama (bahkan g ++ melakukan hal yang sama ketika mengurai bendera ..)
Masalahnya sebenarnya bukan tentang IDE tetapi tentang jawaban untuk menghubungkan masalah. Menautkan masalah tidak terkait dengan IDE tetapi dengan proses kompiler dan pembuatan.
Victor Polevoy
Ya.Tapi proses build / tautan sedang dilakukan di g ++ / Visual Studio (kompiler yang disediakan oleh Microsoft untuk VS) / Eclipse / Net Beans dengan cara yang sama
29

Bug dalam kompiler / IDE

Saya baru-baru ini memiliki masalah ini, dan ternyata itu adalah bug di Visual Studio Express 2013 . Saya harus menghapus file sumber dari proyek dan menambahkannya kembali untuk mengatasi bug.

Langkah-langkah untuk dicoba jika Anda yakin itu adalah bug di kompiler / IDE:

  • Bersihkan proyek (beberapa IDE memiliki opsi untuk melakukan ini, Anda juga dapat melakukannya secara manual dengan menghapus file objek)
  • Coba mulai proyek baru, salin semua kode sumber dari yang asli.
pengembang
sumber
5
Percaya bahwa alat Anda rusak kemungkinan besar akan menjauhkan Anda dari penyebab sebenarnya. Itu jauh lebih mungkin bahwa Anda membuat kesalahan daripada kompiler yang menyebabkan masalah Anda. Membersihkan solusi Anda atau membuat ulang konfigurasi build Anda dapat memperbaiki kesalahan build, tetapi itu tidak berarti ada bug di kompiler. Tertaut "ternyata itu bug" tidak dikonfirmasi oleh Microsoft dan tidak dapat diproduksi ulang.
JDiMatteo
4
@ JDiMatteo Ada 21 jawaban untuk pertanyaan ini dan karenanya sejumlah besar jawaban tidak akan menjadi solusi "kemungkinan". Jika Anda mengabaikan semua jawaban yang berada di bawah ambang batas kemungkinan Anda maka halaman ini secara efektif menjadi tidak berguna karena sebagian besar kasus umum dengan mudah terlihat.
developerbmw
27

Gunakan tautan untuk membantu mendiagnosis kesalahan

Sebagian besar penghubung modern menyertakan opsi verbose yang dicetak ke berbagai tingkat;

  • Permintaan tautan (baris perintah),
  • Data tentang perpustakaan apa yang termasuk dalam tahap tautan,
  • Lokasi perpustakaan,
  • Jalur pencarian digunakan.

Untuk gcc dan dentang; Anda biasanya akan menambah -v -Wl,--verboseatau -v -Wl,-vke baris perintah. Rincian lebih lanjut dapat ditemukan di sini;

Untuk MSVC, /VERBOSE(khususnya /VERBOSE:LIB) ditambahkan ke baris perintah tautan.

Niall
sumber
26

File .lib tertaut dikaitkan dengan .dll

Saya memiliki masalah yang sama. Katakanlah saya punya proyek MyProject dan TestProject. Saya telah secara efektif menautkan file lib untuk MyProject ke TestProject. Namun, file lib ini diproduksi sebagai DLL untuk MyProject dibangun. Juga, saya tidak mengandung kode sumber untuk semua metode di MyProject, tetapi hanya akses ke titik masuk DLL.

Untuk mengatasi masalah ini, saya membuat MyProject sebagai LIB, dan menautkan TestProject ke file .lib ini (saya salin tempelkan file .lib yang dihasilkan ke dalam folder TestProject). Saya kemudian dapat membangun lagi MyProject sebagai DLL. Itu dikompilasi karena lib yang terhubung dengan TestProject tidak mengandung kode untuk semua metode di kelas di MyProject.

Kiriloff
sumber
25

Karena orang-orang sepertinya diarahkan ke pertanyaan ini ketika datang ke kesalahan linker saya akan menambahkan ini di sini.

Salah satu alasan yang mungkin untuk kesalahan penghubung dengan GCC 5.2.0 adalah bahwa libstdc ++ library ABI baru sekarang dipilih secara default.

Jika Anda mendapatkan kesalahan linker tentang referensi yang tidak terdefinisi ke simbol yang melibatkan jenis di std :: __ cxx11 namespace atau tag [abi: cxx11] maka itu mungkin menunjukkan bahwa Anda mencoba untuk menautkan bersama file objek yang dikompilasi dengan nilai yang berbeda untuk _GLIBCXX_USE_CXX11_ABI makro. Ini biasanya terjadi ketika menautkan ke perpustakaan pihak ketiga yang dikompilasi dengan versi GCC yang lebih lama. Jika perpustakaan pihak ketiga tidak dapat dibangun kembali dengan ABI baru maka Anda perlu mengkompilasi ulang kode Anda dengan ABI lama.

Jadi jika Anda tiba-tiba mendapatkan kesalahan linker ketika beralih ke GCC setelah 5.1.0 ini akan menjadi hal yang patut untuk dilihat.

Plankalkül
sumber
20

Wrapper di sekitar GNU ld yang tidak mendukung skrip tautan

Beberapa file .so sebenarnya adalah skrip linker GNU ld , misalnya file libtbb.so adalah file teks ASCII dengan konten ini:

INPUT (libtbb.so.2)

Beberapa bangunan yang lebih kompleks mungkin tidak mendukung ini. Sebagai contoh, jika Anda memasukkan -v dalam opsi kompiler, Anda dapat melihat bahwa mainwin gcc wrapper mwdip membuang file perintah skrip linker di daftar output verbose dari perpustakaan untuk dihubungkan. Pekerjaan sederhana di sekitar adalah untuk mengganti perintah input skrip linker file dengan salinan file sebagai gantinya (atau symlink), misalnya

cp libtbb.so.2 libtbb.so

Atau Anda bisa mengganti argumen -l dengan path lengkap dari .so, misal alih-alih -ltbblakukan/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2

JDiMatteo
sumber
20

Tautan Anda menggunakan pustaka sebelum file objek yang merujuknya

  • Anda mencoba mengkompilasi dan menautkan program Anda dengan GCC toolchain.
  • Tautan Anda menentukan semua perpustakaan dan jalur pencarian perpustakaan yang diperlukan
  • Jika libfootergantung libbar, maka keterkaitan Anda dengan benar diletakkan libfoosebelumnya libbar.
  • Tautan Anda gagal dengan kesalahan undefined reference to sesuatu .
  • Tetapi semua sesuatu yang tidak terdefinisi dinyatakan dalam file header yang Anda miliki #includedan sebenarnya didefinisikan di perpustakaan yang Anda tautkan.

Contohnya dalam C. Mereka juga bisa menjadi C ++

Contoh minimal yang melibatkan perpustakaan statis yang Anda buat sendiri

my_lib.c

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

Anda membangun perpustakaan statis Anda:

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

Anda mengkompilasi program Anda:

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

Anda mencoba menautkannya dengan libmy_lib.adan gagal:

$ 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

Hasil yang sama jika Anda mengkompilasi dan menautkan dalam satu langkah, seperti:

$ 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

Contoh minimal yang melibatkan pustaka sistem bersama, pustaka kompresi libz

eg2.c

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

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

Kompilasi program Anda:

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

Cobalah menautkan program Anda dengan libzdan gagal:

$ 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

Sama jika Anda mengompilasi dan menautkan dalam sekali jalan:

$ 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

Dan variasi pada contoh 2 yang melibatkan pkg-config:

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

Apa yang kamu lakukan salah

Dalam urutan file objek dan pustaka yang ingin Anda tautkan untuk membuat program Anda, Anda menempatkan pustaka sebelum file objek yang merujuknya. Anda perlu menempatkan perpustakaan setelah file objek yang merujuknya.

Tautkan contoh 1 dengan benar:

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

Keberhasilan:

$ ./eg1 
Hello World

Tautkan contoh 2 dengan benar:

$ gcc -o eg2 eg2.o -lz

Keberhasilan:

$ ./eg2 
1.2.8

Tautkan pkg-configvariasi 2 contoh dengan benar:

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

Penjelasan

Membaca adalah opsional mulai dari sini .

Secara default, perintah tautan yang dibuat oleh GCC, di distro Anda, menggunakan file di tautan dari kiri ke kanan dalam urutan baris perintah. Ketika menemukan bahwa suatu file merujuk pada sesuatu dan tidak mengandung definisi untuk itu, untuk akan mencari definisi dalam file lebih jauh ke kanan. Jika akhirnya menemukan definisi, referensi diselesaikan. Jika referensi tetap tidak terselesaikan di akhir, tautan gagal: tautan tidak mencari mundur.

Pertama, contoh 1 , dengan perpustakaan statismy_lib.a

Perpustakaan statis adalah arsip file objek yang diindeks. Ketika tautan menemukan -lmy_libdalam urutan tautan dan mengetahui bahwa ini merujuk ke pustaka statis ./libmy_lib.a, ia ingin mengetahui apakah program Anda membutuhkan salah satu file objek di dalamnya libmy_lib.a.

Hanya ada file objek di libmy_lib.a, yaitu my_lib.o, dan hanya ada satu hal yang didefinisikan my_lib.o, yaitu fungsi hw.

Linker akan memutuskan bahwa program Anda perlu my_lib.ojika dan hanya jika sudah tahu bahwa program Anda mengacu pada hw, dalam satu atau lebih file objek yang telah ditambahkan ke program, dan bahwa tidak ada file objek yang telah ditambahkan mengandung definisi untuk hw.

Jika itu benar, maka tautan akan mengekstrak salinan dari my_lib.operpustakaan dan menambahkannya ke program Anda. Kemudian, program anda berisi definisi untuk hw, jadi referensi untuk hwyang diselesaikan .

Ketika Anda mencoba menautkan program seperti:

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

tautan belum ditambahkan eg1.o ke program saat dilihat -lmy_lib. Karena pada saat itu, belum terlihat eg1.o. Program Anda belum membuat referensi apa pun hw: ia belum membuat referensi sama sekali , karena semua referensi yang dibuatnya ada di dalamnya eg1.o.

Jadi linker tidak menambah my_lib.oprogram dan tidak digunakan untuk selanjutnya libmy_lib.a.

Selanjutnya, ia menemukan eg1.o, dan menambahkannya menjadi program. File objek dalam urutan tautan selalu ditambahkan ke program. Sekarang, program membuat referensi ke hw, dan tidak mengandung definisi hw; tetapi tidak ada yang tersisa dalam urutan tautan yang dapat memberikan definisi yang hilang. Referensi untuk hwberakhir tidak terselesaikan , dan hubungan gagal.

Kedua, contoh 2 , dengan perpustakaan bersamalibz

Pustaka bersama bukanlah arsip dari file objek atau semacamnya. Ini jauh lebih seperti program yang tidak memiliki mainfungsi dan sebagai gantinya memaparkan beberapa simbol lain yang didefinisikannya, sehingga program lain dapat menggunakannya saat runtime.

Banyak Linux distro hari mengkonfigurasi GCC mereka toolchain sehingga driver bahasanya ( gcc, g++, gfortrandll) menginstruksikan linker sistem ( ld) untuk nge-link shared library pada saat dibutuhkan dasar. Anda punya salah satu distro itu.

Ini berarti bahwa ketika penghubung menemukan -lzdalam urutan tautan, dan mengetahui bahwa ini merujuk ke perpustakaan bersama (katakanlah) /usr/lib/x86_64-linux-gnu/libz.so, ia ingin tahu apakah ada referensi yang telah ditambahkan ke program Anda yang belum didefinisikan memiliki definisi yang diekspor olehlibz

Jika itu benar, maka tautan tidak akan menyalin potongan apa pun libzdan menambahkannya ke program Anda; sebagai gantinya, itu hanya akan dokter kode program Anda sehingga: -

  • Saat runtime, program loader sistem akan memuat salinan libzke dalam proses yang sama seperti program Anda setiap kali memuat salinan program Anda, untuk menjalankannya.

  • Saat runtime, setiap kali program Anda merujuk ke sesuatu yang didefinisikan libz, referensi itu menggunakan definisi yang diekspor oleh salinan libzdalam proses yang sama.

Program Anda ingin merujuk hanya satu hal yang memiliki definisi yang diekspor oleh libz, yaitu fungsi zlibVersion, yang disebut hanya sekali, di eg2.c. Jika tautan menambahkan referensi itu ke program Anda, dan kemudian menemukan definisi yang diekspor oleh libz, referensi diselesaikan

Tetapi ketika Anda mencoba menautkan program seperti:

gcc -o eg2 -lz eg2.o

urutan kejadian salah dengan cara yang sama seperti contoh 1. Pada saat tautan ditemukan -lz, tidak ada referensi apa pun dalam program: semuanya ada eg2.o, yang belum terlihat. Jadi linker memutuskan tidak ada gunanya libz. Ketika mencapai eg2.o, menambahkannya ke program, dan kemudian memiliki referensi yang tidak ditentukan zlibVersion, urutan tautan selesai; referensi itu tidak terselesaikan, dan hubungan gagal.

Terakhir, pkg-configvariasi dari contoh 2 sekarang memiliki penjelasan yang jelas. Setelah ekspansi shell:

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

menjadi:

gcc -o eg2 -lz eg2.o

yang hanya contoh 2 lagi.

Saya dapat mereproduksi masalah dalam contoh 1, tetapi tidak dalam contoh 2

Tautan:

gcc -o eg2 -lz eg2.o

bekerja dengan baik untuk Anda!

(Atau: Tautan itu berfungsi baik untuk Anda pada, katakanlah, Fedora 23, tetapi gagal di Ubuntu 16.04)

Itu karena distro tempat linkage berfungsi adalah salah satu yang tidak mengonfigurasi rantai alat GCC untuk menautkan pustaka bersama sesuai kebutuhan .

Kembali pada hari itu, itu normal untuk sistem seperti unix untuk menghubungkan perpustakaan statis dan berbagi dengan aturan yang berbeda. Pustaka statis dalam urutan hubungan dikaitkan pada basis yang diperlukan seperti dijelaskan pada contoh 1, tetapi pustaka bersama dihubungkan tanpa syarat.

Perilaku ini ekonomis pada waktu tautan karena penghubung tidak perlu merenungkan apakah perpustakaan bersama diperlukan oleh program: jika itu adalah perpustakaan bersama, tautkan. Dan sebagian besar perpustakaan di sebagian besar tautan adalah perpustakaan bersama. Tetapi ada juga kerugiannya: -

  • Itu tidak ekonomis saat runtime , karena dapat menyebabkan pustaka bersama untuk dimuat bersama dengan sebuah program bahkan jika tidak membutuhkannya.

  • Aturan tautan yang berbeda untuk pustaka statis dan terbagi dapat membingungkan bagi programmer yang tidak mahir, yang mungkin tidak tahu apakah -lfoodalam tautan mereka akan menyelesaikan /some/where/libfoo.aatau tidak /some/where/libfoo.so, dan mungkin tidak memahami perbedaan antara pustaka bersama dan statis.

Pertukaran ini telah menyebabkan situasi skismatik hari ini. Beberapa distro telah mengubah aturan tautan GCC untuk perpustakaan bersama sehingga prinsip yang diperlukan berlaku untuk semua perpustakaan. Beberapa distro terjebak dengan cara lama.

Mengapa saya masih mendapatkan masalah ini meskipun saya mengkompilasi-dan-tautan pada saat yang sama?

Jika saya lakukan saja:

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

tentunya gcc harus melakukan kompilasi eg1.cterlebih dahulu, dan kemudian menautkan file objek yang dihasilkan dengannya libmy_lib.a. Jadi bagaimana mungkin ia tidak tahu bahwa file objek diperlukan ketika melakukan penautan?

Karena mengkompilasi dan menghubungkan dengan satu perintah tidak mengubah urutan urutan tautan.

Ketika Anda menjalankan perintah di atas, gcccari tahu bahwa Anda ingin kompilasi + tautan. Jadi di belakang layar, itu menghasilkan perintah kompilasi, dan menjalankannya, kemudian menghasilkan perintah tautan, dan menjalankannya, seolah-olah Anda telah menjalankan dua perintah:

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

Jadi linkage gagal seperti halnya jika Anda tidak menjalankan dua perintah. Satu-satunya perbedaan yang Anda perhatikan dalam kegagalan adalah bahwa gcc telah menghasilkan file objek sementara dalam kompilasi + tautan case, karena Anda tidak menyuruhnya menggunakannya eg1.o. Kami melihat:

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

dari pada:

eg1.o: In function `main':

Lihat juga

Urutan di mana pustaka terkait yang saling tergantung ditentukan salah

Menempatkan pustaka yang saling bergantung dalam urutan yang salah hanyalah salah satu cara di mana Anda bisa mendapatkan file yang membutuhkan definisi hal-hal yang datang kemudian dalam tautan daripada file yang memberikan definisi. Menempatkan pustaka di depan file objek yang merujuknya adalah cara lain untuk membuat kesalahan yang sama.

Mike Kinghan
sumber
18

Berteman dengan templat ...

Diberikan potongan kode jenis templat dengan operator teman (atau fungsi);

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

The operator<<sedang dinyatakan sebagai fungsi non-template. Untuk setiap jenis yang Tdigunakan Foo, perlu ada non-templated operator<<. Misalnya, jika ada tipe yang Foo<int>dideklarasikan, maka harus ada implementasi operator sebagai berikut;

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

Karena tidak diterapkan, tautan gagal menemukannya dan mengakibatkan kesalahan.

Untuk memperbaikinya, Anda dapat mendeklarasikan operator template sebelum Foojenis dan kemudian menyatakan sebagai teman, instantiasi yang sesuai. Sintaksnya sedikit canggung, tetapi terlihat sebagai berikut;

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

Kode di atas membatasi persahabatan operator dengan instantiasi yang sesuai Foo, yaitu operator<< <int>instantiasi terbatas untuk mengakses anggota pribadi dari instantiasi Foo<int>.

Alternatif termasuk;

  • Mengizinkan pertemanan meluas ke semua instansiasi templat, sebagai berikut;

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
  • Atau, implementasi untuk operator<<dapat dilakukan inline di dalam definisi kelas;

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

Catatan , ketika pernyataan operator (atau fungsi) hanya muncul di kelas, nama tidak tersedia untuk pencarian "normal", hanya untuk pencarian bergantung argumen, dari cppreference ;

Sebuah nama yang pertama kali dideklarasikan dalam deklarasi teman di dalam kelas atau templat kelas X menjadi anggota dari namespace X yang terlampir, tetapi tidak dapat diakses untuk pencarian (kecuali pencarian yang bergantung pada argumen yang menganggap X) kecuali deklarasi yang sesuai pada lingkup namespace adalah disediakan ...

Ada bacaan lebih lanjut tentang teman templat di cppreference dan C ++ FAQ .

Daftar kode yang menunjukkan teknik-teknik di atas .


Sebagai catatan untuk contoh kode yang gagal; g ++ memperingatkan tentang ini sebagai berikut

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)

Niall
sumber
16

Ketika jalur sertakan Anda berbeda

Kesalahan tautan dapat terjadi ketika file header dan pustaka bersama yang terkait (file .lib) tidak sinkron. Biarkan saya jelaskan.

Bagaimana cara kerja tautan? Linker cocok dengan deklarasi fungsi (dideklarasikan di header) dengan definisinya (di perpustakaan bersama) dengan membandingkan tanda tangan mereka. Anda bisa mendapatkan kesalahan linker jika linker tidak menemukan definisi fungsi yang cocok dengan sempurna.

Apakah mungkin untuk tetap mendapatkan kesalahan linker meskipun deklarasi dan definisi tampaknya cocok? Iya! Mereka mungkin terlihat sama dalam kode sumber, tetapi itu benar-benar tergantung pada apa yang dilihat kompiler. Pada dasarnya Anda bisa berakhir dengan situasi seperti ini:

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

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

Perhatikan bagaimana meskipun kedua deklarasi fungsi terlihat identik dalam kode sumber, tetapi keduanya benar-benar berbeda menurut kompiler.

Anda mungkin bertanya bagaimana seseorang berakhir dalam situasi seperti itu? Sertakan jalur tentu saja! Jika saat mengkompilasi pustaka bersama, jalur sertakan mengarah ke header1.hdan Anda akhirnya menggunakan header2.hdi program Anda sendiri, Anda akan dibiarkan menggaruk tajuk Anda bertanya-tanya apa yang terjadi (pun intended).

Contoh bagaimana ini bisa terjadi di dunia nyata dijelaskan di bawah ini.

Elaborasi lebih lanjut dengan sebuah contoh

Saya punya dua proyek: graphics.libdan main.exe. Kedua proyek bergantung common_math.h. Misalkan perpustakaan mengekspor fungsi berikut:

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

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

Dan kemudian Anda pergi ke depan dan memasukkan perpustakaan ke dalam proyek Anda sendiri.

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

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

Ledakan! Anda mendapatkan kesalahan linker dan Anda tidak tahu mengapa itu gagal. Alasannya adalah bahwa perpustakaan umum menggunakan versi yang berbeda dari yang sama termasuk common_math.h(saya telah membuatnya jelas di sini dalam contoh dengan memasukkan jalur yang berbeda, tetapi mungkin tidak selalu begitu jelas. Mungkin jalur sertakan berbeda dalam pengaturan kompiler) .

Perhatikan dalam contoh ini, penghubung akan memberi tahu Anda bahwa itu tidak dapat ditemukan draw(), padahal kenyataannya Anda tahu itu sedang diekspor oleh perpustakaan. Anda bisa menghabiskan waktu berjam-jam menggaruk-garuk kepala bertanya-tanya apa yang salah. Masalahnya, linker melihat tanda tangan yang berbeda karena tipe parameternya sedikit berbeda. Dalam contoh ini, vec3ada jenis yang berbeda di kedua proyek sejauh menyangkut kompiler. Ini bisa terjadi karena mereka berasal dari dua file sertakan yang sedikit berbeda (mungkin file sertakan berasal dari dua versi perpustakaan yang berbeda).

Mendebug tautan

DUMPBIN adalah teman Anda, jika Anda menggunakan Visual Studio. Saya yakin kompiler lain memiliki alat serupa lainnya.

Prosesnya seperti ini:

  1. Catat nama aneh yang diberikan dalam kesalahan tautan. (mis. draw @ graphics @ XYZ).
  2. Buang simbol yang diekspor dari perpustakaan ke file teks.
  3. Cari simbol minat yang diekspor, dan perhatikan bahwa nama yang rusak itu berbeda.
  4. Perhatikan mengapa nama-nama yang hancur itu berbeda. Anda akan dapat melihat bahwa tipe-tipe parameter berbeda, meskipun mereka terlihat sama dalam kode sumber.
  5. Alasan mengapa mereka berbeda. Dalam contoh yang diberikan di atas, mereka berbeda karena menyertakan file yang berbeda.

[1] Yang saya maksud dengan proyek adalah seperangkat file sumber yang dihubungkan bersama untuk menghasilkan perpustakaan atau yang dapat dieksekusi.

EDIT 1: Tulis ulang bagian pertama agar lebih mudah dimengerti. Berikan komentar di bawah ini untuk memberi tahu saya jika ada sesuatu yang perlu diperbaiki. Terima kasih!

fafaro
sumber
15

UNICODEDefinisi yang tidak konsisten

Windows UNICODE build dibuat dengan TCHARdll. Didefinisikan sebagai wchar_tdll. Ketika tidak membangun dengan UNICODEdidefinisikan sebagai build dengan TCHARdidefinisikan sebagai chardll. Ini UNICODEdan _UNICODEmendefinisikan mempengaruhi semua tipe string " T" ; LPTSTR, LPCTSTRdan rusa mereka.

Membangun satu perpustakaan dengan UNICODEdidefinisikan dan berusaha untuk menautkannya dalam proyek di mana UNICODEtidak didefinisikan akan menghasilkan kesalahan penghubung karena akan ada ketidakcocokan dalam definisi TCHAR; charvs. wchar_t.

Kesalahan biasanya mencakup fungsi nilai dengan tipe charatau wchar_tturunan, ini bisa termasuk std::basic_string<>dll juga. Saat menelusuri fungsi yang terpengaruh dalam kode, sering kali akan ada referensi ke TCHARatau std::basic_string<TCHAR>lain - lain. Ini adalah tanda bahwa kode awalnya ditujukan untuk UNICODE dan karakter Multi-Byte (atau "sempit") build .

Untuk memperbaikinya, buat semua perpustakaan dan proyek yang diperlukan dengan definisi UNICODE(dan _UNICODE) yang konsisten .

  1. Ini bisa dilakukan dengan;

    #define UNICODE
    #define _UNICODE
  2. Atau dalam pengaturan proyek;

    Properti Proyek> Umum> Default Proyek> Set Karakter

  3. Atau di baris perintah;

    /DUNICODE /D_UNICODE

Alternatif ini berlaku juga, jika UNICODE tidak dimaksudkan untuk digunakan, pastikan definisi tidak ditetapkan, dan / atau pengaturan multi-karakter digunakan dalam proyek dan diterapkan secara konsisten.

Jangan lupa untuk konsisten antara build "Release" dan "Debug".

Niall
sumber
14

Bersihkan dan bangun kembali

"Bersih" bangunan dapat menghilangkan "kayu mati" yang mungkin dibiarkan tergeletak di sekitar bangunan sebelumnya, bangunan gagal, bangunan tidak lengkap, dan masalah pembangunan sistem terkait lainnya.

Secara umum IDE atau build akan menyertakan beberapa bentuk fungsi "bersih", tetapi ini mungkin tidak dikonfigurasi dengan benar (misalnya dalam makefile manual) atau mungkin gagal (misalnya, binari perantara atau yang dihasilkan hanya baca-saja).

Setelah "clean" selesai, verifikasi bahwa "clean" telah berhasil dan semua file perantara yang dihasilkan (mis. Makefile otomatis) telah berhasil dihapus.

Proses ini dapat dilihat sebagai upaya terakhir, tetapi seringkali merupakan langkah pertama yang baik ; terutama jika kode yang terkait dengan kesalahan baru-baru ini ditambahkan (baik secara lokal atau dari repositori sumber).

Niall
sumber
10

"Extern" tidak ada dalam constdeklarasi / definisi variabel (khusus C ++)

Bagi orang-orang yang datang dari C mungkin mengejutkan bahwa dalam C ++ constvariabel global memiliki hubungan internal (atau statis). Dalam C ini tidak terjadi, karena semua variabel global secara implisit extern(yaitu ketika statickata kunci hilang).

Contoh:

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

benar adalah dengan menggunakan file header dan memasukkannya ke file2.cpp dan file1.cpp

extern const int test;
extern int test2;

Atau orang dapat mendeklarasikan constvariabel dalam file1.cpp dengan eksplisitextern

Andreas H.
sumber
8

Meskipun ini adalah pertanyaan yang cukup lama dengan beberapa jawaban yang diterima, saya ingin membagikan cara mengatasi kesalahan "referensi tidak jelas ke " yang tidak jelas .

Versi perpustakaan yang berbeda

Saya menggunakan alias untuk merujuk ke std::filesystem::path: filesystem di perpustakaan standar sejak C ++ 17 tetapi program saya juga perlu dikompilasi di C ++ 14 jadi saya memutuskan untuk menggunakan alias variabel:

#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

Katakanlah saya memiliki tiga file: main.cpp, file.h, file.cpp:

  • file.h # include < eksperimental :: filesystem > dan berisi kode di atas
  • file.cpp , implementasi file.h, # include " file.h "
  • main.cpp # include < filesystem > dan " file.h "

Perhatikan berbagai pustaka yang digunakan dalam main.cpp dan file.h. Karena main.cpp # include'd " file.h " setelah < filesystem >, versi filesystem yang digunakan adalah C ++ 17 . Saya biasa mengkompilasi program dengan perintah berikut:

$ g++ -g -std=c++17 -c main.cpp-> mengkompilasi main.cpp ke main.o
$ g++ -g -std=c++17 -c file.cpp-> mengkompilasi file.cpp dan file.h ke file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs-> tautan main.o dan file.o

Dengan cara ini setiap fungsi yang terkandung dalam file.o dan digunakan dalam main.o yang diperlukanpath_t memberikan kesalahan "undefined reference" karena main.o merujuk std::filesystem::pathtetapi file.o ke std::experimental::filesystem::path.

Resolusi

Untuk memperbaikinya, saya hanya perlu mengubah <eksperimental :: filesystem> di file.h ke <filesystem> .

Stypox
sumber
5

Saat menautkan ke pustaka bersama, pastikan simbol yang digunakan tidak disembunyikan.

Perilaku default gcc adalah bahwa semua simbol terlihat. Namun, ketika unit terjemahan dibangun dengan opsi -fvisibility=hidden, hanya fungsi / simbol yang ditandai dengan __attribute__ ((visibility ("default")))eksternal dalam objek bersama yang dihasilkan.

Anda dapat memeriksa apakah simbol yang Anda cari adalah eksternal dengan memohon:

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

simbol tersembunyi / lokal ditunjukkan oleh nmdengan tipe simbol huruf kecil, misalnya talih-alih `T untuk bagian kode:

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

Anda juga dapat menggunakan nmopsi -Cuntuk menghapus nama (jika C ++ digunakan).

Mirip dengan Windows-dll, orang akan menandai fungsi publik dengan define, misalnya DLL_PUBLICdidefinisikan sebagai:

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

DLL_PUBLIC int my_public_function(){
  ...
}

Yang kira-kira sesuai dengan versi Windows / MSVC:

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

Informasi lebih lanjut tentang visibilitas dapat ditemukan di wiki gcc.


Ketika unit terjemahan dikompilasi dengan -fvisibility=hiddensimbol yang dihasilkan masih memiliki hubungan eksternal (ditunjukkan dengan tipe simbol huruf besar oleh nm) dan dapat digunakan untuk hubungan eksternal tanpa masalah jika file objek menjadi bagian dari perpustakaan statis. Tautan menjadi lokal hanya ketika file objek ditautkan ke perpustakaan bersama.

Untuk menemukan simbol mana dalam file objek yang tersembunyi jalankan:

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2
benar-benar
sumber
Anda harus menggunakan nm -CDatau nm -gCDuntuk melihat simbol eksternal. Lihat juga Visibilitas di wiki GCC.
jww
2

Arsitektur yang berbeda

Anda mungkin melihat pesan seperti:

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

Dalam hal ini, itu berarti bahwa simbol yang tersedia adalah untuk arsitektur yang berbeda dari yang Anda kompilasi.

Pada Visual Studio, ini disebabkan oleh "Platform" yang salah, dan Anda harus memilih yang tepat atau menginstal versi perpustakaan yang tepat.

Di Linux, ini mungkin karena folder pustaka yang salah (menggunakan libbukan lib64sebagai contoh).

Di MacOS, ada opsi pengiriman kedua arsitektur dalam file yang sama. Mungkin saja tautannya mengharapkan kedua versi ada di sana, tetapi hanya satu yang ada. Ini juga bisa menjadi masalah dengan folder lib/ salah di lib64mana perpustakaan diambil.

Matthieu Brucher
sumber