Câu hỏi Lỗi tham chiếu ngoài / tham chiếu chưa được giải quyết là gì và cách khắc phục?


Lỗi biểu tượng bên ngoài chưa được giải quyết / không được giải quyết là gì? Nguyên nhân phổ biến là gì và cách sửa chữa / ngăn ngừa chúng?

Vui lòng chỉnh sửa / thêm của riêng bạn.


1198
2017-09-24 22:27


gốc


Một điều cần xem xét thêm là làm thế nào để đối phó với "undefined vtable" và "undefined typeinfo" lỗi cụ thể (vì chúng ít rõ ràng hơn các hàm hoặc biến không xác định). - Jeremiah Willcock
Tôi đã đánh dấu câu hỏi này là một sự lừa đảo có thể có của cái này. Nhưng sau khi trải qua tất cả các câu trả lời (rực rỡ) của bạn, tôi không thể thấy trường hợp này được đề cập ở đây. Tôi biết nó cụ thể về cách một IDE thiết lập loại dự án và phụ thuộc liên kết của nó. Nhưng đó là một câu hỏi thường gặp như vậy, tôi nghĩ rằng nó sẽ có giá trị bảo hiểm (có thể chỉ với một liên kết đến một dupe thích hợp) ở đây. Nếu nó đã được, và tôi chỉ không phát hiện ra nó, quên đi yêu cầu / bình luận này. - πάντα ῥεῖ
@LuchianGrigore 'cảm thấy tự do để thêm câu trả lời' Tôi thích thêm liên kết có liên quan (IMHO) vào câu trả lời chính của bạn, nếu bạn muốn cho phép. - πάντα ῥεῖ
Sai lầm khá phổ biến là bạn xác định một hàm là độc lập và quên bộ chọn lớp (ví dụ: A::) trong .cpp tập tin: Bạn làm điều này (sai):  void myFunc() { /* do stuff */ }  Thay vì điều này (phải):  void A::myFunc() { /* do stuff */ } - jave.web
Nếu điều này xảy ra với bạn với tín hiệu Qt, rất có thể bạn đã quên macro Q_OBJECT. - ManuelSchneid3r


Các câu trả lời:


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

Quyền ưu tiên trong các quy tắc cú pháp của bản dịch được xác định theo các giai đoạn sau [xem chú thích cuối trang].

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

[chú thích cuối trang] Việc triển khai phải hoạt động như thể các pha riêng biệt này xảy ra, mặc dù trong các giai đoạn thực hành 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 nó có nghĩa là bạn đã biên soạn một loạt các tệp triển khai thành các tệp đối tượng hoặc các thư viện và bây giờ bạn muốn làm cho chúng hoạt động cùng nhau.

Giả sử bạn đã xác định biểu tượng a trong a.cpp. Hiện nay, b.cpp  khai báo 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 một vài nơi, nhưng nó vẫn chưa quan tâm ở đâu. Giai đoạn 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 nó với b.cpp (tốt, thực sự đố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 rằng các dự án tạo ra .lib các tập tin. Chúng chứa một bảng các biểu tượng đã xuất và một bảng các biểu tượng được nhập. Các biểu tượng đã nhập được giải quyết dựa vào các thư viện mà bạn liên kết ngược lại và các ký hiệu đã xuất được cung cấp cho các thư viện sử dụng .lib (nếu có).

Cá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 LNK2019 cho Microsoft Visual Studio và undefined 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

Các nguyên nhân phổ biến bao gồm:


695
2017-09-24 22:27



@MirroredFate là có một cách để có được tốt hơn VS linker lỗi? Ví dụ, để làm cho nó trông giống gcc hơn. - TankorSmash
@TankorSmash Nếu chỉ. Tôi nghĩ bạn có thể sửa đổi đầu ra bằng điều nàynhưng tôi chưa thử. - MirroredFate
Cá nhân, tôi nghĩ rằng các thông báo lỗi liên kết MS chỉ là có thể đọc được như các lỗi GCC. Họ cũng có lợi thế là 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 bị cắt xén 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 nào của MSVC đã tạo ra các lỗi ở đây, nhưng các phiên bản mới hơn bao gồm tên (cả bị xáo trộn và chưa được sửa đổi) của hàm tham chiếu đến biểu tượng bên ngoài chưa được giải quyết. - Michael Burr
David Drysdale đã viết một bài viết tuyệt vời về cách liên kết hoạt động: Hướng dẫn cho người mới bắt đầu cho người liên kết. Với chủ đề của câu hỏi này, tôi nghĩ nó có thể hữu ích. - Pressacco
Và làm thế nào để xác định đâu là trường hợp của tôi ?! stackoverflow.com/questions/32915615/… - Aleksey Kontsevich


Thành viên lớp học:

Tinh khiết virtual destructor cần thực hiện.

Khai báo một destructor thuần túy vẫn yêu cầu bạn định nghĩa nó (không giống như một hàm bình 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 destructor lớp cơ sở được gọi khi đối tượng bị phá hủy hoàn toàn, do đó, một định nghĩa là bắt buộc.

virtual các phương thức 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ự như khôngvirtual các phương thức không có định nghĩa, với lý do được thêm vào khai báo thuần túy tạo ra 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() tinh khiết:

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

Khôngvirtual thành viên lớp học

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

struct A
{ 
    ~A();
};

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

A a;      //destructor undefined

Việc thực thi có thể là nội dòng, 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 nhiều định nghĩa.

Tất cả các phương thức thành viên đã sử dụng cần đượ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() {}

static các thành viên dữ liệu phải được định nghĩa bên ngoài lớp trong đơn vị dịch đơn:

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

Trình khởi tạo có thể được cung cấp cho static  const thành viên dữ liệu của loại 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ả static constthành viên dữ liệu.


149
2017-09-24 23:38



Dòng cuối cùng của câu trả lời này là không chính xác, một tuyên bố trong lớp không bao giờ là một định nghĩa. (Định nghĩa là không cần thiết cho các thành viên tĩnh không được sử dụng odr, mà là phổ biến cho hằng số biên dịch tích phân) - Ben Voigt
Không chắc chắn phần này - C ++ 11 cho phép khởi tạo bên trong lớp cho tất cả static const thành viên dữ liệu đúng. [class.static.data] / 3 nói rằng bạn cần đánh dấu các thành viên dữ liệu tĩnh constexpr nếu chúng không thuộc loại tích phân hoặc liệt kê. - Praetorian
Không cần phải xác định bất kỳ chức năng không ảo nào mà bạn không bao giờ sử dụng. Ngoài ra, không cần phải định nghĩa bất kỳ hàm ảo nào, nếu bạn không bao giờ xây dựng một đối tượng của lớp, hoặc cũng không gọi nó từ một lớp dẫn xuất bạn thực sự khởi tạo. Ngoài ra, tất cả các hàm ảo thuần túy có thể được định nghĩa. - Deduplicator
@Deduplicator "cần" so với "nên". Các chức năng ảo không thuần túy, cần phải được xác định (mặc dù, như đã đề cập, một số trình biên dịch sẽ không phàn nàn cho đến khi bạn gọi chúng, nhưng một số sẽ). Tôi không nghĩ rằng tôi nói rằng bạn không thể xác định ảo tinh khiết. - Luchian Grigore
@Deduplicator nhìn thấy "Một hàm ảo được khai báo trong một lớp sẽ được xác định, hoặc khai báo thuần túy (10.4) trong lớp đó, hoặc cả hai, nhưng không cần chẩn đoán" (10.3 Hàm ảo) - trừ khi điều này được thay đổi trong C ++ 14 - Luchian Grigore


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

Thông thường, mỗi đơn vị dịch sẽ tạo một tệp đối tượng chứa các định nghĩa của các ký hiệu được xác định trong đơn vị dịch đó. Để 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 đó.

Dưới gcc bạn sẽ chỉ định tất cả các tệp đối tượng được liên kết với nhau trong dòng lệnh hoặc biên dịch các tệp triển khai cùng nhau.

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

Các libraryName đây chỉ là tên trống của thư viện, mà không có phần bổ sung nền tảng cụ thể. Vì vậy, ví dụ: trên các tệp thư viện Linux thường được gọi libfoo.so như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 -l hoặc là -L.

Dành cho 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 vào thư mục dự án.

Dưới 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à lib tệp sẽ được tạo (sử dụng phổ biến). Để sử dụng các biểu tượng trong một dự án riêng biệt, bạn cần bao gồm lib tệp trong cài đặt dự án. Điều này được thực hiện trong phần Trình liên kết của thuộc tính dự án, trong Input -> Additional Dependencies. (đường dẫn đến lib tệp phải là đượ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 kèm theo lib tập tin, không làm như vậy thường dẫn đến lỗi.

Nó cũng có thể xảy ra mà bạn quên thêm tệp vào trình biên dịch, trong trường hợp đó tệp đối tượng sẽ không được tạo. Trong gcc bạn sẽ thêm các tệp vào dòng lệnh. Trong MSVS thêm tệp vào dự án sẽ làm cho nó biên dịch tự động (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 báo hiệu 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à bạn nên sử dụng thư viện nào cần sử dụng. Ví dụ, MSDN đặt thông tin trong 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".


98
2017-09-24 23:37





Đã khai báo nhưng không xác định biến hoặc hàm.

Một tuyên bố biến điển hình là

extern int x;

Vì đây chỉ là một tuyên bố, một duy nhất độ né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

Các nhận xét tương tự áp dụng cho các hàm. 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 hàm bạn thực hiện khớp chính xác với hàm bạn đã khai báo. Ví dụ: bạn có thể có các loại 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 khớ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 tên 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 điện 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 bản khai đầy đủ của 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. Đảm bảo mọi chi tiết phù hợp.


92
2017-09-24 23:38



@Raymond Tôi đã bỏ sót tên chức năng sai chính tả vì nó khá rõ ràng. Đối với tên tham số - cái gì? - Luchian Grigore
Mọi người hỏi về bên ngoài chưa được giải quyết do tên lỗi chính tả, vì vậy nó không hoàn toàn rõ ràng. (Không chắc bạn đang nói gì về tên tham số. Tên tham số không phải là một phần của loại.) - Raymond Chen
@RaymondChen đã được bao phủ bởi stackoverflow.com/a/12574420/673730 - Luchian Grigore
Trong VS, các tệp cpp khớp với các tiêu đề trong tiêu đề #includes không phải thêm vào thư mục nguồn cũng thuộc danh mục thiếu định nghĩa. - Laurie Stearn


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

Thứ tự trong đó các thư viện được liên kết DOES quan trọng nếu các thư viện phụ thuộc lẫn nhau. Nói chung, nếu thư viện A phụ thuộc vào thư viện B, sau đó libA  PHẢI xuất hiện trước libB trong 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 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ự LÀM vấn đề!


74
2017-07-10 11:46



Tôi tò mò thực tế là trong trường hợp của tôi, tôi đã có một tập tin đối tượng phụ thuộc vào một thư viện được chia sẻ. 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 làm việc không có vấn đề gì. - Marco Sulla
-Wl, - nhóm bắt đầu .....- Wl, - nhóm cuối giải quyết vấn đề này. - user2672165


"biểu tượng bên ngoài chưa được giải quyết / 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 chưa được giải quyết / 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 tệp đối tượng

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

Sau giai đoạn lắp ráp, chúng ta có một tệp đối tượng, chứa bất kỳ biểu tượng 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, bởi vì họ không quan trọng

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

[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 được gì và chúng ta không thấy biểu tượng 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 và liên kết nó. Bây giờ chúng tôi cố gắng để bỏ ghi chú 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), bở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

Điều này 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 pha 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 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 khả năng hiển thị của local_var_name và đặt giá trị của nó là 456789. Cố gắng xây dựng một tệp đối tượng từ nó

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

ok, xem đầu ra đọc (biểu tượng)

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

bây giờ local_var_name có Bind GLOBAL (là LOCAL)

liên kết

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

và chạy nó

$ ./prog 
123456789

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

Do đó, kết quả là "lỗi biểu tượng bên ngoài tham chiếu / chưa được giải quyết" xảy ra khi trình liên kết không thể tìm thấy các biểu tượng chung trong các tệp đối tượng.


64
2017-09-24 23:39





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

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

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

Trình liên kết C ++ dự kiến ​​các tên bị xáo trộn, vì vậy bạn phải khai báo hàm này là:

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

Tương đương, 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 một 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 một tệp tiêu đề (và được biên dịch dưới dạng mã C); bao gồm sẽ cần phải như sau;

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

61
2017-12-03 18:11



Hoặc ngược lại, nếu bạn phát triển một thư viện C, một quy tắc tốt 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 và #ifdef __cplusplus [\n] } [\n] #endif ([\n] trở lại thực sự nhưng tôi không thể viết điều này đúng trong bình luận). - Bentoy13
Như trong nhận xét ở trên, phần 'Tạo tiêu đề hỗn hợp' ở đây đã giúp: oracle.com/technetwork/articles/servers-storage-dev/… - zanbri