Một tham chiếu không xác định / lỗi biểu tượng bên ngoài chưa được giải quyết là gì và làm cách nào để khắc phục nó?


1493

Tham chiếu không xác định / lỗi biểu tượng bên ngoài chưa được giải quyết là gì? Nguyên nhân phổ biến là gì và làm thế nào để khắc phục / ngăn chặn chúng?

Hãy chỉnh sửa / thêm của riêng bạn.


@LuchianGrigore 'cứ thoải mái thêm câu trả lời' Tôi thích thêm liên kết có liên quan (IMHO) câu trả lời chính của bạn, nếu bạn muốn cho phép.
πάντα ῥεῖ

19
Câu hỏi này quá chung chung để thừa nhận một câu trả lời hữu ích. Tôi không thể tin được số lượng hơn 1000 người danh tiếng bình luận về điều này, mà không bỏ qua câu hỏi. Trong khi đó tôi thấy rất nhiều câu hỏi hợp lý và rất hữu ích với tôi đã bị đóng.
Albert van der Horst

10
@AlbertvanderHorst "Câu hỏi này quá chung chung để thừa nhận một câu trả lời hữu ích" Các câu trả lời dưới đây không hữu ích?
LF

4
Chúng có thể hữu ích, cũng như nhiều câu trả lời cho các câu hỏi được gắn cờ là quá chung chung.
Albert van der Horst

1
Tôi muốn xem ví dụ tái tạo tối thiểu như một cái gì đó chúng tôi yêu cầu của hầu hết người dùng mới, một cách trung thực. Tôi không có ý gì với nó, chỉ là - chúng ta không thể mong đợi mọi người tuân theo các quy tắc mà chúng ta không truyền vào chính mình.
Danilo

Câu trả lời:


850

Việc biên dịch chương trình C ++ diễn ra theo nhiều bước, như được chỉ định bởi 2.2 (tín dụng cho Keith Thompson để tham khảo) :

Ưu tiên trong số các quy tắc cú pháp của dịch thuật được chỉ định bởi các giai đoạn sau [xem chú thích] .

  1. Các ký tự tệp nguồn vật lý được ánh xạ, theo cách được xác định theo triển khai, đến bộ ký tự nguồn cơ bản (giới thiệu các ký tự dòng mới cho các chỉ báo cuối dòng) nếu cần. [SNIP]
  2. Mỗi phiên bản của một ký tự dấu gạch chéo ngược (\) ngay sau đó là một ký tự dòng mới sẽ bị xóa, nối các dòng nguồn vật lý để tạo thành các dòng nguồn logic. [SNIP]
  3. Tệp nguồn được phân tách thành các mã thông báo tiền xử lý (2.5) và các chuỗi ký tự khoảng trắng (bao gồm cả các nhận xét). [SNIP]
  4. Các chỉ thị tiền xử lý được thực thi, các lệnh macro được mở rộng và các biểu thức toán tử đơn nguyên _Pragma được thực thi. [SNIP]
  5. Mỗi ký tự nguồn được đặt thành viên theo một ký tự hoặc một chuỗi ký tự, cũng như từng chuỗi thoát và tên ký tự phổ quát trong một ký tự bằng chữ hoặc một chuỗi ký tự không thô, được chuyển đổi thành thành viên tương ứng của bộ ký tự thực thi; [SNIP]
  6. Chuỗi mã thông báo liền kề được nối liền nhau.
  7. Các ký tự khoảng trắng phân tách mã thông báo không còn đáng kể. Mỗi mã thông báo tiền xử lý được chuyển đổi thành mã thông báo. (2.7). Các mã thông báo kết quả được phân tích tổng hợp và ngữ nghĩa và được dịch là một đơn vị dịch thuật. [SNIP]
  8. Các đơn vị dịch thuật và các đơn vị dịch thuật được kết hợp như sau: [SNIP]
  9. Tất cả các tham chiếu thực thể bên ngoài được giải quyết. Các thành phần thư viện được liên kết để đáp ứng các tham chiếu bên ngoài đến các thực thể không được xác định trong bản dịch hiện tại. Tất cả đầu ra của trình dịch như vậy được thu thập vào một hình ảnh chương trình chứa thông tin cần thiết để thực thi trong môi trường thực thi của nó. (nhấn mạnh của tôi)

[chú thích] Việc triển khai phải hành xử như thể những giai đoạn riêng biệt này xảy ra, mặc dù trong thực tế, các giai đoạn khác nhau có thể được xếp lại với nhau.

Các lỗi được chỉ định xảy ra trong giai đoạn biên dịch cuối cùng này, thường được gọi là liên kết. Về cơ bản, điều đó có nghĩa là bạn đã biên dịch một loạt các tệp thực hiện thành các tệp đối tượng hoặc thư viện và bây giờ bạn muốn làm cho chúng hoạt động cùng nhau.

Nói rằng bạn xác định biểu tượng atrong a.cpp. Bây giờ, b.cpp tuyên bố biểu tượng đó và sử dụng nó. Trước khi liên kết, nó chỉ đơn giản giả định rằng biểu tượng đó đã được xác định ở đâu đó , nhưng nó không quan tâm đến đâu. Pha liên kết chịu trách nhiệm tìm biểu tượng và liên kết chính xác với nó b.cpp(tốt, thực sự với đối tượng hoặc thư viện sử dụng nó).

Nếu bạn đang sử dụng Microsoft Visual Studio, bạn sẽ thấy các dự án tạo .libtệp. Chúng bao gồm một bảng các ký hiệu được xuất và một bảng các ký hiệu được nhập. Các biểu tượng đã nhập được giải quyết đối với các thư viện mà bạn liên kết và các biểu tượng đã xuất được cung cấp cho các thư viện sử dụng .lib(nếu có).

Cơ chế tương tự tồn tại cho các trình biên dịch / nền tảng khác.

Thông báo lỗi phổ biến là error LNK2001, error LNK1120, error LNK2019cho Microsoft Visual Studioundefined reference to symbolName cho GCC .

Mật mã:

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

sẽ tạo ra các lỗi sau với 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

và các lỗi tương tự với 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

Nguyên nhân phổ biến bao gồm:


16
Cá nhân, tôi nghĩ rằng các thông báo lỗi liên kết MS cũng dễ đọc như các lỗi GCC. Họ cũng có lợi thế bao gồm cả tên bị xáo trộn và không bị xáo trộn cho bên ngoài chưa được giải quyết. Có tên được đọc sai có thể hữu ích khi bạn cần xem trực tiếp các thư viện hoặc tệp đối tượng để xem vấn đề có thể là gì (ví dụ: một quy ước gọi không khớp). Ngoài ra, tôi không chắc chắn phiên bản MSVC nào đã tạo ra lỗi ở đây, nhưng các phiên bản mới hơn bao gồm tên (cả bị sai lệch và không được chỉnh sửa) của hàm đề cập đến biểu tượng bên ngoài chưa được giải quyết.
Michael Burr

5
David Drysdale đã viết một bài viết tuyệt vời về cách các trình liên kết hoạt động: Hướng dẫn cho người liên kết mới bắt đầu . Với chủ đề của câu hỏi này, tôi nghĩ rằng nó có thể hữu ích.
Pressacco

@TankorSmash Sử dụng gcc? MinGW để chính xác hơn.
doug65536

1
@luchian sẽ thật tuyệt nếu bạn thêm chính xác, sửa các lỗi trên
Phalgun

178

Các thành viên trong lớp:

Một tàu khu trục thuần túy virtualcần thực hiện.

Khai báo một hàm hủy thuần túy vẫn yêu cầu bạn xác định nó (không giống như một hàm thông thường):

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

Điều này xảy ra bởi vì các hàm hủy của lớp cơ sở được gọi khi đối tượng bị hủy hoàn toàn, do đó cần có một định nghĩa.

virtual phương pháp phải được thực hiện hoặc được định nghĩa là thuần túy.

Điều này tương tự với các virtualphương thức không có định nghĩa, với lý do được thêm vào rằng khai báo thuần tạo ra một vtable giả và bạn có thể gặp lỗi liên kết mà không sử dụng hàm:

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

Để làm việc này, hãy khai báo X::foo()là thuần túy:

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

virtualThành viên không thuộc lớp

Một số thành viên cần được xác định ngay cả khi không được sử dụng rõ ràng:

struct A
{ 
    ~A();
};

Sau đây sẽ mang lại lỗi:

A a;      //destructor undefined

Việc thực hiện có thể là nội tuyến, trong chính định nghĩa lớp:

struct A
{ 
    ~A() {}
};

hoặc bên ngoài:

A::~A() {}

Nếu việc triển khai nằm ngoài định nghĩa lớp, nhưng trong một tiêu đề, các phương thức phải được đánh dấu là inlineđể ngăn chặn một định nghĩa nhiều.

Tất cả các phương thức thành viên được sử dụng cần phải được xác định nếu được sử dụng.

Một lỗi phổ biến là quên để đủ điều kiện tên:

struct A
{
   void foo();
};

void foo() {}

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

Định nghĩa nên là

void A::foo() {}

staticcác thành viên dữ liệu phải được xác định bên ngoài lớp trong một đơn vị dịch thuật :

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

Một bộ khởi tạo có thể được cung cấp cho một static constthành viên dữ liệu thuộc kiểu tích phân hoặc liệt kê trong định nghĩa lớp; tuy nhiên, việc sử dụng odr của thành viên này vẫn sẽ yêu cầu định nghĩa phạm vi không gian tên như được mô tả ở trên. C ++ 11 cho phép khởi tạo bên trong lớp cho tất cả các static constthành viên dữ liệu.


Chỉ cần nghĩ rằng bạn có thể muốn nhấn mạnh rằng làm cả hai là có thể, và dtor thực sự không phải là một ngoại lệ. (không rõ ràng từ cách diễn đạt của bạn từ cái nhìn đầu tiên.)
Ded repeatator

112

Không thể liên kết với các thư viện / tệp đối tượng thích hợp hoặc biên dịch tệp thực thi

Thông thường, mỗi đơn vị dịch thuật sẽ tạo ra một tệp đối tượng có chứa các định nghĩa của các ký hiệu được xác định trong đơn vị dịch thuật đó. Để sử dụng các ký hiệu đó, bạn phải liên kết với các tệp đối tượng đó.

Trong gcc, bạn sẽ chỉ định tất cả các tệp đối tượng sẽ được liên kết với nhau trong dòng lệnh hoặc biên dịch các tệp thực hiện với nhau.

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

libraryNameđây chỉ là tên trần của thư viện, không có bổ sung dành riêng cho nền tảng. Vì vậy, ví dụ trên các tệp thư viện Linux thường được gọi libfoo.sonhưng bạn chỉ viết -lfoo. Trên Windows, cùng một tệp có thể được gọi foo.lib, nhưng bạn sẽ sử dụng cùng một đối số. Bạn có thể phải thêm thư mục nơi các tệp đó có thể được tìm thấy bằng cách sử dụng -L‹directory›. Đảm bảo không viết một khoảng trắng sau -lhoặc -L.

Đối với XCode : Thêm Đường dẫn tìm kiếm tiêu đề người dùng -> thêm Đường dẫn tìm kiếm thư viện -> kéo và thả tham chiếu thư viện thực tế vào thư mục dự án.

Trong MSVS , các tệp được thêm vào dự án sẽ tự động có các tệp đối tượng được liên kết với nhau và một libtệp sẽ được tạo (theo cách sử dụng chung). Để sử dụng các ký hiệu trong một dự án riêng biệt, bạn cần bao gồm các libtệp trong cài đặt dự án. Điều này được thực hiện trong phần Linker của các thuộc tính dự án, trong Input -> Additional Dependencies. (đường dẫn đến libtệp nên được thêm vào Linker -> General -> Additional Library Directories) Khi sử dụng thư viện của bên thứ ba được cung cấp cùng với libtệp, việc không làm như vậy thường dẫn đến lỗi.

Nó cũng có thể xảy ra khi bạn quên thêm tệp vào phần biên dịch, trong trường hợp đó tệp đối tượng sẽ không được tạo. Trong gcc, bạn thêm các tệp vào dòng lệnh. Trong MSVS, việc thêm tệp vào dự án sẽ khiến nó tự động biên dịch nó (mặc dù các tệp có thể, theo cách thủ công, được loại trừ riêng lẻ khỏi bản dựng).

Trong lập trình Windows, dấu hiệu nhận biết rằng bạn không liên kết một thư viện cần thiết là tên của biểu tượng chưa được giải quyết bắt đầu bằng __imp_. Tra cứu tên của hàm trong tài liệu và nó sẽ cho biết thư viện nào bạn cần sử dụng. Ví dụ: MSDN đặt thông tin vào một hộp ở dưới cùng của mỗi chức năng trong một phần gọi là "Thư viện".


1
Sẽ tốt hơn nếu bạn có thể che đậy một cách rõ ràng lỗi sai gcc main.cthay vì gcc main.c other.c (điều mà người mới bắt đầu thường làm trước khi các dự án của họ lớn đến mức xây dựng các tệp .o).
MM

101

Khai báo nhưng không xác định một biến hoặc hàm.

Một khai báo biến điển hình là

extern int x;

Vì đây chỉ là một tuyên bố, một định nghĩa duy nhất là cần thiết. Một định nghĩa tương ứng sẽ là:

int x;

Ví dụ: sau đây sẽ tạo ra một lỗi:

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

Nhận xét tương tự áp dụng cho các chức năng. Khai báo một hàm mà không xác định nó dẫn đến lỗi:

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

Hãy cẩn thận rằng chức năng bạn thực hiện chính xác khớp với chức năng bạn đã khai báo. Ví dụ: bạn có thể có vòng loại cv không khớp:

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)

Các ví dụ khác về sự không phù hợp bao gồm

  • Hàm / biến được khai báo trong một không gian tên, được định nghĩa trong một không gian khác.
  • Hàm / biến được khai báo là thành viên lớp, được định nghĩa là toàn cục (hoặc ngược lại).
  • Kiểu trả về hàm, số tham số và kiểu và quy ước gọi không hoàn toàn đồng ý.

Thông báo lỗi từ trình biên dịch thường sẽ cung cấp cho bạn khai báo đầy đủ về biến hoặc hàm được khai báo nhưng không bao giờ được xác định. So sánh nó chặt chẽ với định nghĩa bạn cung cấp. Hãy chắc chắn rằng mọi chi tiết phù hợp.


Trong VS, các tệp cpp khớp với các tệp trong tiêu đề #includeskhông được thêm vào thư mục nguồn cũng thuộc danh mục định nghĩa bị thiếu.
Laurie Stearn

86

Thứ tự trong đó các thư viện liên kết phụ thuộc lẫn nhau được chỉ định là sai.

Thứ tự các thư viện được liên kết DOES có vấn đề nếu các thư viện phụ thuộc lẫn nhau. Nói chung, nếu thư viện Aphụ thuộc vào thư viện B, thì libA PHẢI xuất hiện trước libBtrong các cờ liên kết.

Ví dụ:

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

Tạo các thư viện:

$ 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

Biên dịch:

$ 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

Vì vậy, để lặp lại một lần nữa, thứ tự DOES vấn đề!


Tôi tò mò thực tế là trong trường hợp của tôi, tôi đã có một tệp đối tượng phụ thuộc vào thư viện dùng chung. Tôi đã phải sửa đổi Makefile và đặt thư viện SAU đối tượng với gcc 4.8.4 trên Debian. Trên Centos 6.5 với gcc 4.4, Makefile không hoạt động.
Marco Sulla

74

"tham chiếu không xác định / biểu tượng bên ngoài chưa được giải quyết" là gì

Tôi sẽ cố gắng giải thích "biểu tượng bên ngoài không xác định / chưa được giải quyết" là gì.

lưu ý: tôi sử dụng g ++ và Linux và tất cả các ví dụ là dành cho nó

Ví dụ: chúng tôi có một số mã

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

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

Tạo các tệp đối tượng

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

Sau giai đoạn biên dịch chương trình, chúng ta có một tệp đối tượng, chứa bất kỳ ký hiệu nào để xuất. Nhìn vào các biểu tượng

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

Tôi đã từ chối một số dòng từ đầu ra, vì chúng không quan trọng

Vì vậy, chúng tôi thấy theo biểu tượng để xuất khẩu.

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

src2.cpp không xuất khẩu gì và chúng tôi không thấy biểu tượng nào của nó

Liên kết các tệp đối tượng của chúng tôi

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

và chạy nó

$ ./prog
123

Linker thấy các biểu tượng xuất khẩu và liên kết nó. Bây giờ chúng tôi cố gắng bỏ dòng trong src2.cpp như ở đây

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

và xây dựng lại một tệp đối tượng

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

OK (không có lỗi), vì chúng tôi chỉ xây dựng tệp đối tượng, liên kết chưa được thực hiện. Cố gắng liên kết

$ 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

Nó đã xảy ra vì local_var_name của chúng tôi là tĩnh, tức là nó không hiển thị cho các mô-đun khác. Bây giờ sâu sắc hơn. Nhận đầu ra giai đoạn dịch

$ 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

Vì vậy, chúng tôi đã thấy không có nhãn cho local_var_name, đó là lý do tại sao trình liên kết không tìm thấy nó. Nhưng chúng tôi là tin tặc :) và chúng tôi có thể sửa nó. Mở src1.s trong trình soạn thảo văn bản của bạn và thay đổi

.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4

đến

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

tức là bạn nên có như dưới đây

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

chúng tôi đã thay đổi mức độ hiển thị của local_var_name và đặt giá trị của nó thành 456789. Hãy thử xây dựng một tệp đối tượng từ nó

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

ok, xem đầu ra sẵn sàng (ký hiệu)

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

bây giờ local_var_name có Bind GLOBAL (là ĐỊA PHƯƠNG)

liên kết

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

và chạy nó

$ ./prog 
123456789

ok, chúng tôi hack nó :)

Vì vậy, kết quả là - một "lỗi biểu tượng bên ngoài không được giải quyết / không được giải quyết" xảy ra khi trình liên kết không thể tìm thấy các ký hiệu toàn cục trong các tệp đối tượng.


71

Các biểu tượng được định nghĩa trong chương trình C và được sử dụng trong mã C ++.

Hàm (hoặc biến) void foo()đã được xác định trong chương trình C và bạn cố gắng sử dụng nó trong chương trình C ++:

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

Trình liên kết C ++ dự kiến ​​các tên sẽ được đọc sai, vì vậy bạn phải khai báo hàm là:

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

Tương tự, thay vì được định nghĩa trong chương trình C, hàm (hoặc biến) void foo()được định nghĩa trong C ++ nhưng với liên kết C:

extern "C" void foo();

và bạn cố gắng sử dụng nó trong chương trình C ++ với liên kết C ++.

Nếu toàn bộ thư viện được bao gồm trong tệp tiêu đề (và được biên dịch dưới dạng mã C); bao gồm sẽ cần phải được như sau;

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

5
Hoặc ngược lại, nếu bạn phát triển thư viện C, một quy tắc hay là bảo vệ (các) tệp tiêu đề bằng cách bao quanh tất cả các khai báo đã xuất với #ifdef __cplusplus [\n] extern"C" { [\n] #endif#ifdef __cplusplus [\n] } [\n] #endif( [\n]là trả lại vận chuyển thực sự nhưng tôi không thể viết điều này trong bình luận).
Bentoy13

Như trong nhận xét trên, phần 'Tạo tiêu đề ngôn ngữ hỗn hợp' ở đây đã giúp: oracle.com/technetwork/articles/servers-st
Storage

Thực sự đã giúp tôi ra ngoài! Đây là trường hợp của tôi khi tôi tìm câu hỏi này
hiệp sĩ

Điều này cũng có thể xảy ra nếu bạn bao gồm tệp tiêu đề C ++ thông thường của bạn một cách tình cờ được bao quanh bởi extern C : extern "C" { #include <myCppHeader.h> }.
ComFalet

68

Nếu vẫn thất bại, biên dịch lại.

Gần đây tôi đã có thể thoát khỏi một lỗi bên ngoài chưa được giải quyết trong Visual Studio 2012 chỉ bằng cách biên dịch lại tệp vi phạm. Khi tôi xây dựng lại, lỗi đã biến mất.

Điều này thường xảy ra khi hai (hoặc nhiều) thư viện có sự phụ thuộc theo chu kỳ. Thư viện A cố gắng sử dụng các ký hiệu trong B.lib và thư viện B cố gắng sử dụng các ký hiệu từ A.lib. Không tồn tại để bắt đầu với. Khi bạn cố gắng biên dịch A, bước liên kết sẽ thất bại vì không thể tìm thấy B.lib. A.lib sẽ được tạo ra, nhưng không có dll. Sau đó, bạn biên dịch B, sẽ thành công và tạo B.lib. Biên dịch lại A bây giờ sẽ hoạt động vì B.lib đã được tìm thấy.


1
Đúng - điều này xảy ra khi các thư viện có sự phụ thuộc theo chu kỳ.
Luchian Grigore

1
Tôi mở rộng câu trả lời của bạn và liên kết trong câu hỏi chính. Cảm ơn.
Luchian Grigore

57

Nhập / xuất phương thức / lớp không chính xác trên các mô-đun / dll (trình biên dịch cụ thể).

MSVS yêu cầu bạn chỉ định biểu tượng nào cần xuất và nhập bằng __declspec(dllexport)__declspec(dllimport).

Chức năng kép này thường có được thông qua việc sử dụng macro:

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

Macro THIS_MODULEsẽ chỉ được xác định trong mô-đun xuất hàm. Bằng cách đó, tuyên bố:

DLLIMPEXP void foo();

mở rộng đến

__declspec(dllexport) void foo();

và báo cho trình biên dịch xuất hàm, vì mô-đun hiện tại chứa định nghĩa của nó. Khi bao gồm khai báo trong một mô-đun khác, nó sẽ mở rộng sang

__declspec(dllimport) void foo();

và báo cho trình biên dịch rằng định nghĩa nằm trong một trong các thư viện mà bạn đã liên kết (cũng xem 1) ).

Bạn có thể nhập / xuất các lớp mô phỏng:

class DLLIMPEXP X
{
};

2
Để hoàn thành, câu trả lời này nên đề cập đến các tệp của GCC visibilityvà Windows .def, vì những điều này cũng ảnh hưởng đến tên và sự hiện diện của biểu tượng.
rubenvb

@rubenvb Tôi chưa sử dụng .deftệp trong độ tuổi. Hãy thêm một câu trả lời hoặc chỉnh sửa câu trả lời này.
Luchian Grigore

57

Đây là một trong những thông báo lỗi khó hiểu nhất mà mọi lập trình viên VC ++ đã gặp lại nhiều lần. Trước tiên hãy làm cho mọi thứ rõ ràng.

A. Biểu tượng là gì? Nói tóm lại, một biểu tượng là một cái tên. Nó có thể là tên biến, tên hàm, tên lớp, tên typedef hoặc bất cứ thứ gì ngoại trừ những tên và dấu hiệu đó thuộc về ngôn ngữ C ++. Nó được xác định hoặc giới thiệu bởi một thư viện phụ thuộc (do người dùng khác xác định).

B. Bên ngoài là gì? Trong VC ++, mọi tệp nguồn (.cpp, .c, v.v.) được coi là một đơn vị dịch, trình biên dịch biên dịch một đơn vị tại một thời điểm và tạo một tệp đối tượng (.obj) cho đơn vị dịch hiện tại. (Lưu ý rằng mọi tệp tiêu đề mà tệp nguồn này bao gồm sẽ được xử lý trước và sẽ được coi là một phần của đơn vị dịch thuật này) Mọi thứ trong đơn vị dịch thuật được coi là nội bộ, mọi thứ khác được coi là bên ngoài. Trong C ++, bạn có thể tham chiếu một biểu tượng bên ngoài bằng cách sử dụng các từ khóa như extern, __declspec (dllimport)v.v.

C. Giải quyết vấn đề thế nào? Giải quyết là một thuật ngữ thời gian liên kết. Trong thời gian liên kết, trình liên kết cố gắng tìm định nghĩa bên ngoài cho mọi ký hiệu trong các tệp đối tượng không thể tìm thấy định nghĩa bên trong. Phạm vi của quá trình tìm kiếm này bao gồm:

  • Tất cả các tệp đối tượng được tạo trong thời gian biên dịch
  • Tất cả các thư viện (.lib) được chỉ định rõ ràng hoặc ngầm định là phụ thuộc bổ sung của ứng dụng tòa nhà này.

Quá trình tìm kiếm này được gọi là giải quyết.

D. Cuối cùng, tại sao Biểu tượng bên ngoài chưa được giải quyết? Nếu trình liên kết không thể tìm thấy định nghĩa bên ngoài cho một biểu tượng không có định nghĩa bên trong, nó sẽ báo cáo lỗi Biểu tượng bên ngoài chưa được giải quyết.

E. Nguyên nhân có thể của LNK2019 : Lỗi Biểu tượng bên ngoài chưa được giải quyết. Chúng tôi đã biết rằng lỗi này là do trình liên kết không tìm thấy định nghĩa của các ký hiệu bên ngoài, các nguyên nhân có thể có thể được sắp xếp là:

  1. Định nghĩa tồn tại

Ví dụ: nếu chúng ta có một hàm gọi là foo được xác định trong a.cpp:

int foo()
{
    return 0;
}

Trong b.cpp chúng tôi muốn gọi hàm foo, vì vậy chúng tôi thêm

void foo();

để khai báo hàm foo () và gọi nó trong phần thân hàm khác, giả sử bar():

void bar()
{
    foo();
}

Bây giờ khi bạn xây dựng mã này, bạn sẽ gặp lỗi LNK2019 phàn nàn rằng foo là một biểu tượng chưa được giải quyết. Trong trường hợp này, chúng ta biết rằng foo () có định nghĩa của nó trong a.cpp, nhưng khác với định nghĩa mà chúng ta đang gọi (giá trị trả về khác nhau). Đây là trường hợp định nghĩa tồn tại.

  1. Định nghĩa không tồn tại

Nếu chúng tôi muốn gọi một số chức năng trong thư viện, nhưng thư viện nhập không được thêm vào danh sách phụ thuộc bổ sung (được đặt từ Project | Properties | Configuration Properties | Linker | Input | Additional Dependency:) của cài đặt dự án của bạn. Bây giờ trình liên kết sẽ báo cáo LNK2019 do định nghĩa không tồn tại trong phạm vi tìm kiếm hiện tại.


1
Cảm ơn bạn đã giải thích có hệ thống. Tôi có thể hiểu các câu trả lời khác ở đây sau khi đọc câu này.
displayName

55

Mẫu triển khai không hiển thị.

Các mẫu không chuyên biệt phải có định nghĩa hiển thị cho tất cả các đơn vị dịch sử dụng chúng. Điều đó có nghĩa là bạn không thể tách định nghĩa của mẫu thành tệp thực hiện. Nếu bạn phải tách riêng việc thực hiện, cách giải quyết thông thường là có một impltệp mà bạn đưa vào ở cuối tiêu đề khai báo mẫu. Một tình huống phổ biến là:

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

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

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

Để khắc phục điều này, bạn phải di chuyển định nghĩa của X::footệp tiêu đề hoặc một số vị trí hiển thị cho đơn vị dịch sử dụng nó.

Các mẫu chuyên dụng có thể được thực hiện trong một tệp thực hiện và việc triển khai không phải hiển thị, nhưng chuyên môn phải được khai báo trước đó.

Để được giải thích thêm và một giải pháp khả thi khác (khởi tạo rõ ràng) hãy xem câu hỏi và câu trả lời này .


1
Thông tin thêm cho "các mẫu phải được xác định trong tiêu đề" có thể được tìm thấy trong stackoverflow.com/questions/495021
PlasmaHH

41

tham chiếu không xác định đến WinMain@16hoặc tham chiếu điểm nhập cảnh 'bất thường' tương tựmain() (đặc biệt đối với).

Bạn có thể đã bỏ lỡ để chọn loại dự án phù hợp với IDE thực tế của bạn. IDE có thể muốn liên kết, ví dụ các dự án Ứng dụng Windows với chức năng điểm vào như vậy (như được chỉ định trong tham chiếu bị thiếu ở trên), thay vì int main(int argc, char** argv);chữ ký thường được sử dụng .

Nếu IDE của bạn hỗ trợ Dự án Bảng điều khiển Đồng bằng, bạn có thể muốn chọn loại dự án này, thay vì dự án ứng dụng Windows.


Dưới đây là case1case2 được xử lý chi tiết hơn từ một vấn đề trong thế giới thực .


2
Không thể không chỉ ra câu hỏi này và thực tế là điều này thường xảy ra do không có chức năng chính nào hơn là không có WinMain. Các chương trình C ++ hợp lệ cần a main.
chris

36

Ngoài ra nếu bạn đang sử dụng thư viện của bên thứ 3, hãy đảm bảo bạn có mã nhị phân 32/64 bit chính xác


34

Microsoft cung cấp một #pragmatài liệu tham khảo đúng thư viện tại thời điểm liên kết;

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

Ngoài đường dẫn thư viện bao gồm thư mục của thư viện, đây phải là tên đầy đủ của thư viện.


34

Gói Visual Studio NuGet cần được cập nhật cho phiên bản bộ công cụ mới

Tôi vừa gặp sự cố này khi cố gắng liên kết libpng với Visual Studio 2013. Vấn đề là tệp gói chỉ có thư viện cho Visual Studio 2010 và 2012.

Giải pháp chính xác là hy vọng nhà phát triển phát hành gói cập nhật và sau đó nâng cấp, nhưng nó hoạt động với tôi bằng cách hack trong một cài đặt bổ sung cho VS2013, chỉ vào các tệp thư viện VS2012.

Tôi đã chỉnh sửa gói (trong packagesthư mục bên trong thư mục của giải pháp) bằng cách tìm packagename\build\native\packagename.targetsvà bên trong tệp đó, sao chép tất cả các v110phần. Tôi đã thay đổi v110thành v120trong các trường điều kiện chỉ rất cẩn thận để lại tất cả các đường dẫn tên tệp là v110. Điều này chỉ đơn giản là cho phép Visual Studio 2013 liên kết với các thư viện cho năm 2012 và trong trường hợp này, nó đã hoạt động.


1
Điều này có vẻ quá cụ thể - có lẽ một chủ đề mới sẽ là một nơi tốt hơn cho câu trả lời này.
Luchian Grigore

3
@LuchianGrigore: Tôi đã muốn đăng ở đây vì câu hỏi đó chính xác là vấn đề này, nhưng nó được đánh dấu là một bản sao của câu hỏi này vì vậy tôi không thể trả lời nó ở đó. Vì vậy, tôi đã đăng câu trả lời của tôi ở đây để thay thế.
Malvineous

Câu hỏi đó đã có một câu trả lời được chấp nhận. Nó được đánh dấu là trùng lặp vì nguyên nhân chung được liệt kê ở trên. Điều gì sẽ xảy ra nếu chúng ta có câu trả lời ở đây cho mọi lời quảng cáo với một thư viện không được bao gồm?
Luchian Grigore

6
@LuchianGrigore: Vấn đề này không dành riêng cho thư viện, nó ảnh hưởng đến tất cả các thư viện sử dụng hệ thống quản lý gói của Visual Studio. Tôi tình cờ tìm thấy câu hỏi khác vì cả hai chúng tôi đều có vấn đề với libpng. Tôi cũng gặp vấn đề tương tự (với cùng một giải pháp) cho libxml2, libiconv và glew. Câu hỏi đó là về một vấn đề với hệ thống quản lý gói của Visual Studio và câu trả lời của tôi giải thích lý do và cung cấp cách giải quyết. Ai đó chỉ nhìn thấy "bên ngoài chưa được giải quyết" và cho rằng đó là một vấn đề liên kết tiêu chuẩn khi nó thực sự là một vấn đề quản lý gói.
Malvineous

34

Giả sử bạn có một dự án lớn được viết bằng c ++ có hàng ngàn tệp .cpp và một nghìn tệp .h. Hãy nói rằng dự án cũng phụ thuộc vào mười thư viện tĩnh. Hãy nói rằng chúng tôi đang ở trên Windows và chúng tôi xây dựng dự án của mình trong Visual Studio 20xx. Khi bạn nhấn Ctrl + F7 Visual Studio để bắt đầu biên dịch toàn bộ giải pháp (giả sử chúng tôi chỉ có một dự án trong giải pháp)

Ý nghĩa của việc biên soạn là gì?

  • Visual Studio tìm kiếm vào tệp .vcxproj và bắt đầu biên dịch từng tệp có phần mở rộng .cpp. Thứ tự biên dịch không xác định. Vì vậy, bạn không được cho rằng tệp main.cpp được biên dịch trước
  • Nếu các tệp .cpp phụ thuộc vào các tệp .h bổ sung để tìm các ký hiệu có thể hoặc không thể được xác định trong tệp .cpp
  • Nếu tồn tại một tệp .cpp trong đó trình biên dịch không thể tìm thấy một ký hiệu, lỗi thời gian của trình biên dịch sẽ làm tăng thông báo Biểu tượng x không thể tìm thấy
  • Đối với mỗi tệp có phần mở rộng .cpp được tạo một tệp đối tượng .o và Visual Studio cũng ghi đầu ra trong một tệp có tên ProjectName.Cpp.Clean.txt chứa tất cả các tệp đối tượng phải được xử lý bởi trình liên kết.

Bước biên dịch thứ hai được thực hiện bởi Linker.Linker nên hợp nhất tất cả các tệp đối tượng và xây dựng cuối cùng là đầu ra (có thể là tệp thực thi hoặc thư viện)

Các bước trong liên kết một dự án

  • Phân tích tất cả các tệp đối tượng và tìm định nghĩa chỉ được khai báo trong các tiêu đề (ví dụ: Mã của một phương thức của một lớp như được đề cập trong các câu trả lời trước hoặc sự kiện khởi tạo một biến tĩnh là thành viên bên trong một lớp)
  • Nếu một biểu tượng không thể được tìm thấy trong các tập tin đối tượng ông cũng được tìm kiếm trong bổ sung Libraries.For thêm một thư viện mới cho một dự án bất động sản Cấu hình -> VC ++ Thư mục -> Thư viện Thư mục và đây bạn chỉ định thư mục bổ sung cho việc tìm kiếm các thư viện và các thuộc tính cấu hình -> Trình liên kết -> Đầu vào để chỉ định tên của thư viện. -Nếu Trình liên kết không thể tìm thấy biểu tượng mà bạn viết trong một .cpp, anh ta sẽ phát sinh lỗi thời gian của trình liên kết có thể giống như error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

Quan sát

  1. Khi Trình liên kết tìm thấy một biểu tượng, anh ta không tìm kiếm trong các thư viện khác cho nó
  2. Thứ tự của các thư viện liên kết có vấn đề .
  3. Nếu Linker tìm thấy một ký hiệu bên ngoài trong một thư viện tĩnh, anh ta sẽ bao gồm ký hiệu đó trong đầu ra của dự án. Tuy nhiên, nếu thư viện được chia sẻ (động), anh ta không bao gồm mã (ký hiệu) trong đầu ra, nhưng thời gian chạy có thể bị sập xảy ra

Làm thế nào để giải quyết loại lỗi này

Lỗi thời gian biên dịch:

  • Hãy chắc chắn rằng bạn viết đúng dự án c ++ của bạn.

Lỗi thời gian liên kết

  • Xác định tất cả các biểu tượng mà bạn khai báo trong các tệp tiêu đề của bạn
  • Sử dụng #pragma onceđể cho phép trình biên dịch không bao gồm một tiêu đề nếu nó đã được bao gồm trong .cpp hiện tại được biên dịch
  • Đảm bảo rằng thư viện bên ngoài của bạn không chứa các biểu tượng có thể xung đột với các biểu tượng khác mà bạn đã xác định trong các tệp tiêu đề của mình
  • Khi bạn sử dụng mẫu để đảm bảo bạn bao gồm định nghĩa của từng hàm mẫu trong tệp tiêu đề để cho phép trình biên dịch tạo mã thích hợp cho bất kỳ cảnh báo nào.

Không phải câu trả lời của bạn là cụ thể cho phòng thu trực quan? Câu hỏi không chỉ định bất kỳ công cụ IDE / trình biên dịch nào, vì vậy nó làm cho câu trả lời của bạn trở nên vô dụng đối với phần không phải là phòng thu trực quan.
Victor Polevoy

Bạn đúng . Nhưng mọi quy trình biên dịch / liên kết IDE đều được thực hiện hơi khác nhau. Nhưng các tệp được xử lý giống hệt nhau (ngay cả g ++ cũng làm điều tương tự khi phân tích các cờ ..)

Vấn đề không thực sự là về IDE mà là về câu trả lời cho các vấn đề liên kết. Các vấn đề liên kết không liên quan đến IDE mà liên quan đến trình biên dịch và quá trình xây dựng.
Victor Polevoy

Có. Nhưng quá trình xây dựng / liên kết đang được thực hiện trong g ++ / Visual Studio (trình biên dịch do Microsoft cung cấp cho VS) / Eclipse / Net Đậu theo cách tương tự

29

Một lỗi trong trình biên dịch / IDE

Gần đây tôi đã gặp vấn đề này và hóa ra đó là một lỗi trong Visual Studio Express 2013 . Tôi đã phải xóa một tệp nguồn khỏi dự án và thêm lại nó để khắc phục lỗi.

Các bước để thử nếu bạn tin rằng nó có thể là một lỗi trong trình biên dịch / IDE:

  • Làm sạch dự án (một số IDE có tùy chọn để thực hiện việc này, bạn cũng có thể thực hiện thủ công bằng cách xóa các tệp đối tượng)
  • Hãy thử bắt đầu một dự án mới, sao chép tất cả mã nguồn từ dự án ban đầu.

5
Tin rằng các công cụ của bạn bị hỏng rất có thể sẽ khiến bạn tránh xa nguyên nhân thực sự. Rất có khả năng là bạn đã mắc lỗi so với trình biên dịch gây ra vấn đề của bạn. Làm sạch giải pháp của bạn hoặc tạo lại cấu hình bản dựng của bạn có thể khắc phục lỗi bản dựng, nhưng điều đó không có nghĩa là có lỗi trong trình biên dịch. Liên kết "hóa ra đó là một lỗi" không được xác nhận bởi Microsoft và không thể tái sản xuất.
JDiMatteo

4
@JDiMatteo Có 21 câu trả lời cho câu hỏi này và do đó, một lượng câu trả lời đáng kể sẽ không phải là một giải pháp "có khả năng". Nếu bạn bỏ qua tất cả các câu trả lời dưới ngưỡng thích của bạn thì trang này thực sự trở nên vô dụng vì hầu hết các trường hợp phổ biến đều dễ dàng bị phát hiện.
nhà phát triển

27

Sử dụng trình liên kết để giúp chẩn đoán lỗi

Hầu hết các trình liên kết hiện đại bao gồm một tùy chọn dài dòng in ra các mức độ khác nhau;

  • Liên kết gọi (dòng lệnh),
  • Dữ liệu về những thư viện được bao gồm trong giai đoạn liên kết,
  • Vị trí của các thư viện,
  • Đường dẫn tìm kiếm được sử dụng.

Đối với gcc và tiếng kêu; bạn thường sẽ thêm -v -Wl,--verbosehoặc -v -Wl,-vvào dòng lệnh. Nhiều thông tin thêm có thế được tìm thấy ở đây;

Đối với MSVC, /VERBOSE(đặc biệt /VERBOSE:LIB) được thêm vào dòng lệnh liên kết.


26

Tập tin .lib được liên kết có liên quan đến một.

Tôi gặp vấn đề tương tự. Nói rằng tôi có dự án MyProject và TestProject. Tôi đã liên kết hiệu quả tệp lib cho MyProject với TestProject. Tuy nhiên, tệp lib này được tạo ra khi DLL cho MyProject được xây dựng. Ngoài ra, tôi không chứa mã nguồn cho tất cả các phương thức trong MyProject, mà chỉ truy cập vào các điểm nhập của DLL.

Để giải quyết vấn đề, tôi đã xây dựng MyProject dưới dạng LIB và liên kết TestProject với tệp .lib này (tôi sao chép dán tệp .lib đã tạo vào thư mục TestProject). Sau đó tôi có thể xây dựng lại MyProject dưới dạng DLL. Nó được biên dịch vì lib mà TestProject được liên kết có chứa mã cho tất cả các phương thức trong các lớp trong MyProject.


25

Vì mọi người dường như được hướng đến câu hỏi này khi nói đến lỗi liên kết, tôi sẽ thêm nó vào đây.

Một lý do có thể gây ra lỗi liên kết với GCC 5.2.0 là mặc định thư viện libstdc ++ ABI mới được chọn theo mặc định.

Nếu bạn gặp lỗi liên kết về các tham chiếu không xác định đến các ký hiệu liên quan đến các loại trong không gian tên std :: __ cxx11 hoặc thẻ [abi: cxx11] thì có lẽ bạn đang cố gắng liên kết các tệp đối tượng được biên dịch với các giá trị khác nhau cho _GLIBCXX_USE_CXX11_ABI vĩ mô. Điều này thường xảy ra khi liên kết đến thư viện của bên thứ ba được biên dịch với phiên bản GCC cũ hơn. Nếu thư viện của bên thứ ba không thể được xây dựng lại bằng ABI mới thì bạn sẽ cần biên dịch lại mã của mình với ABI cũ.

Vì vậy, nếu bạn đột nhiên gặp lỗi liên kết khi chuyển sang GCC sau 5.1.0 thì đây sẽ là một điều cần kiểm tra.


20

Một trình bao bọc xung quanh GNU ld không hỗ trợ các tập lệnh liên kết

Một số tệp .so thực sự là các tập lệnh liên kết GNU ld , ví dụ tệp libtbb.so là tệp văn bản ASCII có nội dung này:

INPUT (libtbb.so.2)

Một số bản dựng phức tạp hơn có thể không hỗ trợ điều này. Ví dụ: nếu bạn bao gồm -v trong các tùy chọn trình biên dịch, bạn có thể thấy rằng trình bao bọc gcc mainwin mwdip loại bỏ các tệp lệnh tập lệnh liên kết trong danh sách đầu ra của các thư viện để liên kết. Một công việc đơn giản là thay thế lệnh nhập tập lệnh liên kết. thay vào đó là một bản sao của tệp (hoặc một liên kết tượng trưng), vd

cp libtbb.so.2 libtbb.so

Hoặc bạn có thể thay thế đối số -l bằng đường dẫn đầy đủ của .so, ví dụ thay vì -ltbblàm/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2


20

Liên kết của bạn tiêu thụ các thư viện trước các tệp đối tượng đề cập đến chúng

  • Bạn đang cố gắng biên dịch và liên kết chương trình của bạn với chuỗi công cụ GCC.
  • Liên kết của bạn chỉ định tất cả các thư viện cần thiết và đường dẫn tìm kiếm thư viện
  • Nếu libfoophụ thuộc vào libbar, thì liên kết của bạn đặt chính xác libfootrước libbar.
  • Liên kết của bạn không thành công với undefined reference to một cái gì đó lỗi.
  • Nhưng tất cả những thứ không xác định được khai báo trong các tệp tiêu đề bạn có #included và trên thực tế được xác định trong các thư viện mà bạn đang liên kết.

Ví dụ như trong C. Họ cũng có thể là C ++

Một ví dụ tối thiểu liên quan đến một thư viện tĩnh bạn tự xây dựng

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

ví dụ1.c

#include <my_lib.h>

int main()
{
    hw();
    return 0;
}

Bạn xây dựng thư viện tĩnh của mình:

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

Bạn biên dịch chương trình của bạn:

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

Bạn cố gắng liên kết nó với libmy_lib.avà thất bại:

$ 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

Kết quả tương tự nếu bạn biên dịch và liên kết trong một bước, như:

$ 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

Một ví dụ tối thiểu liên quan đến thư viện hệ thống dùng chung, thư viện nén libz

ví dụ2.c

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

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

Biên dịch chương trình của bạn:

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

Cố gắng liên kết chương trình của bạn với libzvà thất bại:

$ 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

Tương tự nếu bạn biên dịch và liên kết trong một lần:

$ 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

Và một biến thể trong ví dụ 2 liên quan đến pkg-config:

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

Bạn đang làm gì sai?

Trong chuỗi các tệp đối tượng và thư viện bạn muốn liên kết để tạo chương trình của mình, bạn đang đặt các thư viện trước các tệp đối tượng tham chiếu đến chúng. Bạn cần đặt các thư viện sau các tệp đối tượng tham chiếu đến chúng.

Liên kết ví dụ 1 chính xác:

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

Sự thành công:

$ ./eg1 
Hello World

Liên kết ví dụ 2 chính xác:

$ gcc -o eg2 eg2.o -lz

Sự thành công:

$ ./eg2 
1.2.8

Liên kết pkg-configbiến thể của ví dụ 2 một cách chính xác:

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

Lời giải thích

Đọc là tùy chọn từ đây về .

Theo mặc định, một lệnh liên kết được tạo bởi GCC, trên bản phân phối của bạn, tiêu thụ các tệp trong liên kết từ trái sang phải theo trình tự dòng lệnh. Khi nhận thấy rằng một tệp đề cập đến một cái gì đó và không chứa định nghĩa cho nó, sẽ tìm kiếm một định nghĩa trong các tệp ở bên phải. Nếu cuối cùng nó tìm thấy một định nghĩa, tham chiếu được giải quyết. Nếu bất kỳ tài liệu tham khảo nào vẫn chưa được giải quyết ở cuối, liên kết không thành công: trình liên kết không tìm kiếm ngược.

Đầu tiên, ví dụ 1 , với thư viện tĩnhmy_lib.a

Một thư viện tĩnh là một kho lưu trữ được lập chỉ mục của các tệp đối tượng. Khi trình liên kết tìm thấy -lmy_libtrong chuỗi liên kết và chỉ ra rằng điều này đề cập đến thư viện tĩnh ./libmy_lib.a, nó muốn biết liệu chương trình của bạn có cần bất kỳ tệp đối tượng nào không libmy_lib.a.

Chỉ có tệp đối tượng libmy_lib.a, cụ thể là my_lib.o, và chỉ có một thứ được định nghĩa trong my_lib.ođó là hàm hw.

Trình liên kết sẽ quyết định rằng chương trình của bạn cần my_lib.onếu và chỉ khi nó biết rằng chương trình của bạn đề cập đến hw, trong một hoặc nhiều tệp đối tượng mà nó đã thêm vào chương trình và không có tệp đối tượng nào được thêm vào có chứa định nghĩa cho hw.

Nếu đó là sự thật, thì trình liên kết sẽ trích xuất một bản sao my_lib.otừ thư viện và thêm nó vào chương trình của bạn. Sau đó, chương trình của bạn chứa một định nghĩa cho hw, vì vậy các tham chiếu của nó hwđược giải quyết .

Khi bạn cố gắng liên kết chương trình như:

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

Trình liên kết chưa được thêm vào eg1.o chương trình khi nó nhìn thấy -lmy_lib. Bởi vì tại thời điểm đó, nó đã không nhìn thấy eg1.o. Chương trình của bạn vẫn chưa thực hiện bất kỳ tài liệu tham khảo để hw: nó chưa thực hiện bất kỳ tài liệu tham khảo ở tất cả , bởi vì tất cả các tài liệu tham khảo mà nó chỉ ra trong eg1.o.

Vì vậy, trình liên kết không thêm my_lib.ovào chương trình và không sử dụng thêm cho libmy_lib.a.

Tiếp theo, nó tìm thấy eg1.ovà thêm nó vào chương trình. Một tệp đối tượng trong chuỗi liên kết luôn được thêm vào chương trình. Bây giờ, chương trình đưa ra một tham chiếu đến hw, và không chứa định nghĩa về hw; nhưng không còn gì trong chuỗi liên kết có thể cung cấp định nghĩa còn thiếu. Tham chiếu đến hwkết thúc không được giải quyết , và liên kết không thành công.

Thứ hai, ví dụ 2 , với thư viện dùng chunglibz

Một thư viện chia sẻ không phải là một kho lưu trữ các tệp đối tượng hoặc bất cứ thứ gì giống như nó. Nó giống như một chương trình không có mainchức năng và thay vào đó hiển thị nhiều biểu tượng khác mà nó xác định, để các chương trình khác có thể sử dụng chúng khi chạy.

Nhiều Linux distro hiện nay cấu hình GCC họ toolchain để trình điều khiển ngôn ngữ của nó ( gcc, g++, gfortranvv) chỉ thị cho mối liên kết hệ thống ( ld) để liên kết các thư viện chia sẻ trên một khi cần cơ sở. Bạn đã có một trong những distro đó.

Điều này có nghĩa là khi trình liên kết tìm thấy -lztrong chuỗi liên kết và chỉ ra rằng điều này đề cập đến thư viện dùng chung (giả sử) /usr/lib/x86_64-linux-gnu/libz.so, nó muốn biết liệu có bất kỳ tài liệu tham khảo nào mà nó đã thêm vào chương trình của bạn chưa được xác định có định nghĩa không xuất khẩu bởilibz

Nếu đó là sự thật, thì trình liên kết sẽ không sao chép bất kỳ đoạn nào ra libzvà thêm chúng vào chương trình của bạn; thay vào đó, nó sẽ chỉ kiểm tra mã chương trình của bạn để: -

  • Khi chạy, trình tải chương trình hệ thống sẽ tải một bản sao libzvào cùng tiến trình với chương trình của bạn bất cứ khi nào nó tải một bản sao của chương trình của bạn, để chạy nó.

  • Trong thời gian chạy, bất cứ khi nào chương trình của bạn đề cập đến một cái gì đó được định nghĩa libz, tham chiếu đó sử dụng định nghĩa được xuất bởi bản sao libztrong cùng một quy trình.

Chương trình của bạn muốn chỉ một điều có định nghĩa được xuất bởi libz, cụ thể là hàm zlibVersion, được đề cập chỉ một lần, trong eg2.c. Nếu trình liên kết thêm tham chiếu đó vào chương trình của bạn và sau đó tìm định nghĩa được xuất bởi libz, tham chiếu được giải quyết

Nhưng khi bạn cố gắng liên kết chương trình như:

gcc -o eg2 -lz eg2.o

thứ tự của các sự kiện là sai theo cách tương tự như với ví dụ 1. Tại thời điểm khi trình liên kết tìm thấy -lz, không có tài liệu tham khảo nào trong chương trình: tất cả chúng đều eg2.ochưa được nhìn thấy. Vì vậy, trình liên kết quyết định nó không có sử dụng cho libz. Khi nó đạt đến eg2.o, thêm nó vào chương trình, và sau đó có tham chiếu không xác định zlibVersion, chuỗi liên kết đã kết thúc; tham chiếu đó không được giải quyết và liên kết thất bại.

Cuối cùng, pkg-configbiến thể của ví dụ 2 có một lời giải thích rõ ràng. Sau khi mở rộng vỏ:

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

trở thành:

gcc -o eg2 -lz eg2.o

đó chỉ là ví dụ 2 một lần nữa.

Tôi có thể tái tạo vấn đề trong ví dụ 1, nhưng không phải trong ví dụ 2

Liên kết:

gcc -o eg2 -lz eg2.o

chỉ hoạt động tốt cho bạn!

(Hoặc: Liên kết đó hoạt động tốt với bạn trên, giả sử, Fedora 23, nhưng không thành công trên Ubuntu 16.04)

Đó là bởi vì bản phân phối mà liên kết hoạt động là một trong những bản không cấu hình chuỗi công cụ GCC của nó để liên kết các thư viện dùng chung khi cần .

Trước đây, việc các hệ thống giống như unix liên kết các thư viện tĩnh và chia sẻ theo các quy tắc khác nhau là điều bình thường. Các thư viện tĩnh trong chuỗi liên kết được liên kết trên cơ sở khi cần được giải thích trong ví dụ 1, nhưng các thư viện dùng chung được liên kết vô điều kiện.

Hành vi này là kinh tế tại thời gian liên kết vì trình liên kết không phải suy ngẫm liệu chương trình chia sẻ có cần thiết cho chương trình hay không: nếu đó là thư viện dùng chung, hãy liên kết nó. Và hầu hết các thư viện trong hầu hết các liên kết là thư viện chia sẻ. Nhưng cũng có nhược điểm: -

  • Nó không kinh tế khi chạy , bởi vì nó có thể khiến các thư viện chia sẻ được tải cùng với một chương trình ngay cả khi không cần chúng.

  • Các quy tắc liên kết khác nhau cho các thư viện tĩnh và chia sẻ có thể gây nhầm lẫn cho các lập trình viên thiếu kinh nghiệm, những người có thể không biết liệu -lfootrong liên kết của họ sẽ giải quyết /some/where/libfoo.ahay không /some/where/libfoo.sovà có thể không hiểu sự khác biệt giữa các thư viện chia sẻ và tĩnh.

Sự đánh đổi này đã dẫn đến tình trạng ly giáo ngày nay. Một số distro đã thay đổi quy tắc liên kết GCC của họ cho các thư viện dùng chung để nguyên tắc cần thiết áp dụng cho tất cả các thư viện. Một số distro đã bị mắc kẹt với cách cũ.

Tại sao tôi vẫn gặp vấn đề này ngay cả khi tôi biên dịch và liên kết cùng một lúc?

Nếu tôi chỉ làm:

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

chắc chắn gcc phải biên dịch eg1.ctrước, và sau đó liên kết tệp đối tượng kết quả với libmy_lib.a. Vậy làm thế nào để nó không thể biết rằng tệp đối tượng là cần thiết khi nó thực hiện liên kết?

Bởi vì biên dịch và liên kết với một lệnh duy nhất không thay đổi thứ tự của chuỗi liên kết.

Khi bạn chạy lệnh ở trên, hãy chỉ gccra rằng bạn muốn biên dịch + liên kết. Vì vậy, đằng sau hậu trường, nó tạo ra một lệnh biên dịch và chạy nó, sau đó tạo một lệnh liên kết và chạy nó, như thể bạn đã chạy hai lệnh:

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

Vì vậy, các liên kết không giống như nó nếu bạn làm chạy hai lệnh. Sự khác biệt duy nhất bạn nhận thấy trong thất bại là gcc đã tạo một tệp đối tượng tạm thời trong trường hợp biên dịch + liên kết, bởi vì bạn không bảo nó sử dụng eg1.o. Chúng tôi thấy:

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

thay vì:

eg1.o: In function `main':

Xem thêm

Thứ tự trong đó các thư viện liên kết phụ thuộc lẫn nhau được chỉ định là sai

Đặt các thư viện phụ thuộc lẫn nhau theo thứ tự sai chỉ là một cách mà bạn có thể nhận được các tệp cần định nghĩa về những thứ đến sau trong liên kết so với các tệp cung cấp định nghĩa. Đặt các thư viện trước các tệp đối tượng đề cập đến chúng là một cách khác để gây ra lỗi tương tự.


18

Kết bạn với các mẫu ...

Đưa ra đoạn mã của một kiểu mẫu với toán tử bạn bè (hoặc hàm);

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

Việc operator<<này đang được khai báo là một hàm không phải mẫu. Đối với mỗi loại Tđược sử dụng với Foo, cần phải có một templated operator<<. Ví dụ, nếu có một kiểu Foo<int>khai báo, thì phải có một toán tử thực hiện như sau;

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

Vì nó không được thực hiện, trình liên kết không tìm thấy nó và dẫn đến lỗi.

Để sửa lỗi này, bạn có thể khai báo một toán tử mẫu trước Fookiểu và sau đó khai báo là một người bạn, cách khởi tạo thích hợp. Cú pháp hơi khó xử, nhưng có vẻ như sau;

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

Đoạn mã trên giới hạn tình bạn của toán tử đối với việc khởi tạo tương ứng của Foo, tức là việc operator<< <int>khởi tạo được giới hạn để truy cập vào các thành viên riêng của việc khởi tạo Foo<int>.

Các lựa chọn thay thế bao gồm;

  • Cho phép tình bạn mở rộng đến tất cả các phần khởi tạo của các mẫu, như sau;

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
  • Hoặc, việc thực hiện operator<<có thể được thực hiện nội tuyến bên trong định nghĩa lớp;

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

Lưu ý , khi khai báo của toán tử (hoặc hàm) chỉ xuất hiện trong lớp, tên không có sẵn cho tra cứu "bình thường", chỉ dành cho tra cứu phụ thuộc đối số, từ cppreference ;

Tên được khai báo đầu tiên trong khai báo bạn bè trong lớp hoặc mẫu lớp X trở thành thành viên của không gian tên kèm theo trong cùng của X, nhưng không thể truy cập để tra cứu (ngoại trừ tra cứu phụ thuộc đối số xem xét X) trừ khi khai báo khớp với phạm vi không gian tên là cung cấp ...

Có thêm đọc về bạn bè mẫu tại cppreferenceC ++ FAQ .

Danh sách mã hiển thị các kỹ thuật trên .


Như một lưu ý phụ cho mẫu mã thất bại; g ++ cảnh báo về điều này như sau

warning: friend declaration 'std::ostream& operator<<(...)' declares a non-template function [-Wnon-template-friend]

note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)


16

Khi đường dẫn bao gồm của bạn khác nhau

Lỗi trình liên kết có thể xảy ra khi tệp tiêu đề và thư viện dùng chung được liên kết (tệp .lib) không đồng bộ. Hãy để tôi giải thích.

Làm thế nào để liên kết làm việc? Trình liên kết khớp với một khai báo hàm (được khai báo trong tiêu đề) với định nghĩa của nó (trong thư viện dùng chung) bằng cách so sánh chữ ký của chúng. Bạn có thể gặp lỗi liên kết nếu trình liên kết không tìm thấy định nghĩa hàm hoàn toàn khớp.

Có thể vẫn nhận được một lỗi liên kết mặc dù khai báo và định nghĩa dường như khớp? Đúng! Chúng có thể trông giống nhau trong mã nguồn, nhưng nó thực sự phụ thuộc vào những gì trình biên dịch nhìn thấy. Về cơ bản, bạn có thể kết thúc với một tình huống như thế này:

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

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

Lưu ý rằng mặc dù cả hai khai báo hàm trông giống hệt nhau trong mã nguồn, nhưng chúng thực sự khác nhau theo trình biên dịch.

Bạn có thể hỏi làm thế nào một người kết thúc trong một tình huống như vậy? Bao gồm các con đường tất nhiên! Nếu khi biên dịch thư viện dùng chung, đường dẫn bao gồm dẫn đến header1.hvà bạn kết thúc sử dụng header2.htrong chương trình của riêng mình, bạn sẽ phải gãi đầu tiêu đề của bạn tự hỏi điều gì đã xảy ra (ý định chơi chữ).

Một ví dụ về cách điều này có thể xảy ra trong thế giới thực được giải thích dưới đây.

Xây dựng thêm với một ví dụ

Tôi có hai dự án: graphics.libmain.exe. Cả hai dự án đều phụ thuộc vào common_math.h. Giả sử thư viện xuất hàm sau:

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

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

Và sau đó bạn tiếp tục và đưa thư viện vào dự án của riêng bạn.

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

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

Bùng nổ! Bạn nhận được một lỗi liên kết và bạn không biết tại sao nó lại thất bại. Lý do là thư viện chung sử dụng các phiên bản khác nhau của cùng một bao gồm common_math.h(tôi đã làm rõ ở đây trong ví dụ bằng cách bao gồm một đường dẫn khác, nhưng nó có thể không phải lúc nào cũng rõ ràng. Có thể đường dẫn bao gồm khác nhau trong cài đặt trình biên dịch) .

Lưu ý trong ví dụ này, trình liên kết sẽ cho bạn biết nó không thể tìm thấy draw(), khi thực tế bạn biết nó rõ ràng đang được thư viện xuất khẩu. Bạn có thể dành hàng giờ gãi đầu tự hỏi điều gì đã xảy ra. Vấn đề là, trình liên kết nhìn thấy một chữ ký khác vì các loại tham số hơi khác nhau. Trong ví dụ này, vec3là một loại khác nhau trong cả hai dự án liên quan đến trình biên dịch. Điều này có thể xảy ra vì chúng đến từ hai tệp bao gồm hơi khác nhau (có thể các tệp bao gồm đến từ hai phiên bản khác nhau của thư viện).

Gỡ lỗi trình liên kết

DUMPBIN là bạn của bạn, nếu bạn đang sử dụng Visual Studio. Tôi chắc chắn các trình biên dịch khác có các công cụ tương tự khác.

Quá trình diễn ra như sau:

  1. Lưu ý tên sai lệch kỳ lạ được đưa ra trong lỗi liên kết. (ví dụ: vẽ @ đồ họa @ XYZ).
  2. Kết xuất các ký hiệu được xuất từ ​​thư viện thành tệp văn bản.
  3. Tìm kiếm biểu tượng quan tâm đã xuất và chú ý rằng tên được đọc sai khác nhau.
  4. Hãy chú ý đến lý do tại sao các tên sai lệch kết thúc khác nhau. Bạn sẽ có thể thấy rằng các loại tham số là khác nhau, mặc dù chúng trông giống nhau trong mã nguồn.
  5. Lý do tại sao họ khác nhau. Trong ví dụ đưa ra ở trên, chúng khác nhau vì các tệp bao gồm khác nhau.

[1] Theo dự án, ý tôi là một tập hợp các tệp nguồn được liên kết với nhau để tạo ra một thư viện hoặc một tệp thực thi.

EDIT 1: Viết lại phần đầu tiên để dễ hiểu hơn. Vui lòng bình luận dưới đây để cho tôi biết nếu một cái gì đó khác cần phải được sửa chữa. Cảm ơn!


15

UNICODEĐịnh nghĩa không nhất quán

Một Windows UNICODE xây dựng được xây dựng với TCHAR, vv được định nghĩa là wchar_tvv Khi không xây dựng với UNICODEđịnh nghĩa là xây dựng với TCHARđịnh nghĩa là charvv Những UNICODE_UNICODEđịnh nghĩa ảnh hưởng đến tất cả các " T" loại chuỗi ; LPTSTR, LPCTSTRVà nai sừng tấm của họ.

Xây dựng một thư viện với UNICODEđịnh nghĩa và cố gắng liên kết nó trong một dự án UNICODEkhông được xác định sẽ dẫn đến lỗi liên kết do sẽ có sự không phù hợp trong định nghĩa TCHAR; charvs wchar_t.

Lỗi thường bao gồm một hàm một giá trị với một loại charhoặc wchar_tdẫn xuất, chúng cũng có thể bao gồm std::basic_string<>vv. Khi duyệt qua chức năng bị ảnh hưởng trong mã, thường sẽ có một tham chiếu đến TCHARhoặc std::basic_string<TCHAR>v.v ... Đây là dấu hiệu cho biết mã ban đầu được dành cho cả bản dựng UNICODE và ký tự nhiều byte (hoặc "hẹp") .

Để sửa lỗi này, hãy xây dựng tất cả các thư viện và dự án cần thiết với định nghĩa nhất quán UNICODE(và _UNICODE).

  1. Điều này có thể được thực hiện với một trong hai;

    #define UNICODE
    #define _UNICODE
  2. Hoặc trong cài đặt dự án;

    Thuộc tính dự án> Chung> Mặc định dự án> Bộ ký tự

  3. Hoặc trên dòng lệnh;

    /DUNICODE /D_UNICODE

Giải pháp thay thế cũng được áp dụng, nếu UNICODE không được sử dụng, hãy đảm bảo rằng các định nghĩa không được đặt và / hoặc cài đặt nhiều ký tự được sử dụng trong các dự án và được áp dụng nhất quán.

Đừng quên thống nhất giữa các bản dựng "Phát hành" và "Gỡ lỗi".


14

Làm sạch và xây dựng lại

Việc "dọn dẹp" bản dựng có thể loại bỏ "gỗ chết" có thể bị bỏ lại xung quanh các bản dựng trước, bản dựng không thành công, bản dựng không hoàn chỉnh và các vấn đề xây dựng liên quan đến hệ thống xây dựng khác.

Nói chung, IDE hoặc bản dựng sẽ bao gồm một số dạng hàm "sạch", nhưng điều này có thể không được cấu hình chính xác (ví dụ: trong tệp tạo thủ công) hoặc có thể không thành công (ví dụ: các nhị phân trung gian hoặc kết quả là chỉ đọc).

Khi "sạch" đã hoàn thành, hãy xác minh rằng "sạch" đã thành công và tất cả các tệp trung gian được tạo (ví dụ: tệp tạo tự động) đã được xóa thành công.

Quá trình này có thể được xem như là một phương sách cuối cùng, nhưng thường là bước đầu tiên tốt ; đặc biệt là nếu mã liên quan đến lỗi gần đây đã được thêm vào (cục bộ hoặc từ kho lưu trữ nguồn).


10

Thiếu "extern" trong constkhai báo / định nghĩa biến (chỉ C ++)

Đối với những người đến từ C, có thể có một điều ngạc nhiên là trong C ++, các constbiến toàn cục có liên kết bên trong (hoặc tĩnh). Trong C, đây không phải là trường hợp, vì tất cả các biến toàn cục đều ngầm định extern(nghĩa là khi staticthiếu từ khóa).

Thí dụ:

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

đúng sẽ là sử dụng tệp tiêu đề và đưa nó vào file2.cpp file1.cpp

extern const int test;
extern int test2;

Ngoài ra, người ta có thể khai báo constbiến trong file1.cpp với tường minhextern


8

Mặc dù đây là một câu hỏi khá cũ với nhiều câu trả lời được chấp nhận, tôi muốn chia sẻ cách khắc phục lỗi "tham chiếu không xác định" tối nghĩa .

Các phiên bản khác nhau của thư viện

Tôi đã sử dụng một bí danh để tham khảo std::filesystem::path: hệ thống tập tin nằm trong thư viện tiêu chuẩn kể từ C ++ 17 nhưng chương trình của tôi cũng cần phải biên dịch trong C ++ 14 vì vậy tôi quyết định sử dụng một bí danh biến:

#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

Giả sử tôi có ba tệp: main.cpp, file.h, file.cpp:

  • file.h # bao gồm < thử nghiệm :: hệ thống tập tin > và chứa mã ở trên
  • file.cpp , việc triển khai file.h, # bao gồm " file.h "
  • main.cpp # bao gồm < hệ thống tập tin > và " file.h "

Lưu ý các thư viện khác nhau được sử dụng trong main.cpp và file.h. Vì main.cpp # bao gồm " file.h " sau < filesystem >, phiên bản của hệ thống tập tin được sử dụng là C ++ 17 . Tôi đã sử dụng để biên dịch chương trình với các lệnh sau:

$ g++ -g -std=c++17 -c main.cpp-> biên dịch main.cpp thành main.o
$g++ -g -std=c++17 -c file.cpp -> biên dịch file.cpp và file.h thành file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs-> liên kết main.o và file.o

Bằng cách này, bất kỳ hàm nào có trong file.o và được sử dụng trong main.o yêu cầupath_t đã đưa ra các lỗi "tham chiếu không xác định" vì main.o đã đề cập đến std::filesystem::pathnhưng file.o đến std::experimental::filesystem::path.

Nghị quyết

Để sửa lỗi này, tôi chỉ cần thay đổi <thử nghiệm :: hệ thống tập tin> trong tệp.h thành <hệ thống tập tin> .


5

Khi liên kết với các thư viện dùng chung, hãy đảm bảo rằng các biểu tượng được sử dụng không bị ẩn.

Hành vi mặc định của gcc là tất cả các biểu tượng đều có thể nhìn thấy. Tuy nhiên, khi các đơn vị dịch được xây dựng với tùy chọn -fvisibility=hidden, chỉ các chức năng / ký hiệu được đánh dấu bằng__attribute__ ((visibility ("default"))) là bên ngoài trong đối tượng chia sẻ kết quả.

Bạn có thể kiểm tra xem các biểu tượng bạn đang tìm kiếm có bên ngoài hay không bằng cách gọi:

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

các ký hiệu ẩn / cục bộ được hiển thị bằng nmloại ký hiệu chữ thường, ví dụ tthay vì `T cho phần mã:

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

Bạn cũng có thể sử dụng nmvới tùy chọn -Cđể khử tên (nếu C ++ đã được sử dụng).

Tương tự như Windows-dlls, người ta sẽ đánh dấu các hàm công khai bằng một định nghĩa, ví dụ DLL_PUBLICđược xác định là:

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

DLL_PUBLIC int my_public_function(){
  ...
}

Tương đương với phiên bản Windows / MSVC:

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

Thông tin thêm về khả năng hiển thị có thể được tìm thấy trên wiki gcc.


Khi một đơn vị dịch được biên dịch với -fvisibility=hiddencác ký hiệu kết quả vẫn có liên kết ngoài (được hiển thị với loại ký hiệu chữ hoanm ) và có thể được sử dụng cho liên kết ngoài mà không gặp vấn đề gì nếu các tệp đối tượng trở thành một phần của thư viện tĩnh. Liên kết chỉ trở thành cục bộ khi các tệp đối tượng được liên kết vào thư viện dùng chung.

Để tìm các ký hiệu trong tệp đối tượng được ẩn chạy:

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

Bạn nên sử dụng nm -CDhoặc nm -gCDđể xem các biểu tượng bên ngoài. Cũng xem Hiển thị trên wiki GCC.
jww

2

Kiến trúc khác nhau

Bạn có thể thấy một thông báo như:

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

Trong trường hợp đó, điều đó có nghĩa là các biểu tượng có sẵn dành cho một kiến ​​trúc khác với kiến ​​trúc bạn đang biên dịch.

Trên Visual Studio, điều này là do "Nền tảng" sai và bạn cần chọn đúng hoặc cài đặt phiên bản thích hợp của thư viện.

Trên Linux, có thể do thư mục thư viện sai (sử dụng libthay vì lib64ví dụ).

Trên MacOS, có tùy chọn vận chuyển cả hai kiến ​​trúc trong cùng một tệp. Có thể là liên kết hy vọng cả hai phiên bản sẽ ở đó, nhưng chỉ có một phiên bản. Nó cũng có thể là một vấn đề với thư mục lib/ sai lib64trong đó thư viện được chọn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.