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 lỗi này?

1566
Luchian Grigore 2012-09-25 12:27.

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

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

30 answers

880
Luchian Grigore 2012-09-25 12:27.

Biên dịch một chương trình C ++ diễn ra trong một số bước, như được chỉ định bởi 2.2 (tín dụng cho Keith Thompson để tham khảo) :

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

  1. Các ký tự của tệp nguồn vật lý được ánh xạ, theo cách được triển khai xác định, với 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 trường hợp của một ký tự gạch chéo ngược (\) ngay sau 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ả nhận xét). [SNIP]
  4. Các chỉ thị tiền xử lý được thực thi, các lệnh gọi macro được mở rộng và các biểu thức toán tử một ngôi _Pragma được thực thi. [SNIP]
  5. Mỗi thành viên tập hợp ký tự nguồn trong một ký tự chữ hoặc một chuỗi ký tự, cũng như mỗi chuỗi thoát và tên-ký tự phổ thông trong một ký tự ký tự hoặc một ký tự chuỗi không phải nguyên, được chuyển đổi thành thành viên tương ứng của bộ ký tự thực thi; [SNIP]
  6. Các mã thông báo theo chuỗi liền kề được nối với nhau.
  7. Các ký tự khoảng trắng phân tách các mã thông báo 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 mã thông báo 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. [SNIP]
  8. Đơn vị dịch đã dịch và đơn vị trình diễn đượ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)

[footnote] Việc triển khai phải hoạt động như thể các 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, nó có nghĩa là bạn đã biên dịch một loạt các tệp triển khai thành các tệp đối tượng hoặc thư viện và bây giờ bạn muốn chúng hoạt động cùng nhau.

Giả sử bạn đã xác định ký hiệu atrong a.cpp. Bây giờ, 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 ở đâu đó , 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 kiếm biểu tượng và liên kết chính xác nó với b.cpp(thực sự là 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 rằng các dự án tạo ra .libcác tệp. Chúng chứa một bảng các ký hiệu đã xuất và một bảng các ký hiệu đã nhập. Các ký hiệu đã nhập được phân giải dựa trên các thư viện bạn liên kết và các ký hiệu đã xuất được cung cấp cho các thư viện sử dụng nó .lib(nếu có).

Các cơ chế tương tự cũng tồn tại đối với 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)" ([email protected]@YAXXZ)
1>test2.obj : error LNK2001: unresolved external symbol "int x" ([email protected]@3HA)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall A::~A(void)" ([email protected]@[email protected])
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall X::foo(void)" ([email protected]@@UAEXXZ)
1>...\test2.exe : fatal error LNK1120: 4 unresolved externals

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

185
Luchian Grigore 2012-09-25 13:38.

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

Một trình virtualhủy thuần túy cần được triển khai.

Khai báo 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 một cách ngầm định, vì vậy cần phải có một định nghĩa.

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ự với các virtualphương thức không có định nghĩa, với lý do bổ sung rằng khai báo thuần túy tạo ra một vtable giả và bạn có thể gặp lỗi trình liên kết nếu 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
}

Để điều này hoạt động, hãy khai báo X::foo()là thuần túy:

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

Phi virtualthành viên 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();
};

Điều sau sẽ dẫn đến lỗi:

A a;      //destructor undefined

Việc triển khai 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 nằm trong tiêu đề, các phương thức phải được đánh dấu là inlinengăn chặn định nghĩa nhiều.

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

Một lỗi thường gặp là quên tên đủ điều kiện:

struct A
{
   void foo();
};

void foo() {}

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

Định nghĩa phải 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 duy nhất :

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

Bộ khởi tạo có thể được cung cấp cho static constthành viên dữ liệu của kiểu tích phân hoặc kiểu 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 sẽ vẫn 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.

121
Luchian Grigore 2012-09-25 13:37.

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 triển khai

Thông thường, mỗi đơn vị dịch sẽ tạo ra một tệp đối tượng chứa đị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 đó.

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 triển khai với nhau.

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

Đây libraryNamechỉ là tên trần của thư viện, không có bổ sung nền tảng cụ thể. Vì vậy, ví dụ: trên thư viện Linux, các tệp thường được gọi libfoo.sonhưng bạn chỉ viết -lfoo. Trên Windows, tệp tương tự 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ó thể tìm thấy các tệp đó bằng cách sử dụng -L‹directory›. Đảm bảo không viế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 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ủa chúng được liên kết với nhau và một libtệp sẽ được tạo (theo cách sử dụng phổ biến). Để 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 Trình liên kết của thuộc tính dự án, trong Input -> Additional Dependencies. (đường dẫn đến libtệp phải đượ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.

Cũng có thể xảy ra trường hợp bạn quên thêm tệp vào 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, việc thêm tệp vào dự án sẽ làm cho nó tự động biên dịch (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 cho thấy bạn không liên kết thư viện cần thiết là tên của ký hiệu chưa được giải mã 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 bạn cần sử dụng thư viện nào. 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 được gọi là "Thư viện".

105
Luchian Grigore 2012-09-25 13:38.

Đã khai báo nhưng không xác định 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 khai báo nên cần một định nghĩa duy nhất . Một định nghĩa tương ứng sẽ là:

int x;

Ví dụ: điều sau sẽ tạo ra 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 hàm bạn triển khai khớp chính xác với hàm bạn đã khai báo. Ví dụ: bạn có thể có các định lượng 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ề của hàm, số và kiểu tham số và quy ước gọi không hoàn toàn đồng ý với nhau.

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 đủ của biến hoặc hàm đã được khai báo nhưng chưa bao giờ được định nghĩa. 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 đều khớp.

89
Svalorzen 2014-07-11 01:46.

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

Thứ tự mà các thư viện được liên kết KHÔNG quan trọng nếu các thư viện phụ thuộc vào 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ờ trình 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ự KHÔNG quan trọng!

76
Kastaneda 2014-10-08 00:09.

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

Tôi sẽ cố gắng giải thích "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ì.

lưu ý: tôi sử dụng g ++ và Linux và tất cả các ví dụ đều 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 hợp ngữ, chúng ta có một tệp đối tượng, tệp này chứa bất kỳ ký hiệu nào để xuất. Nhìn vào các ký hiệu

$ 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ì chúng không quan trọng

Vì vậy, chúng tôi thấy các biểu tượng follow để 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 gì và chúng tôi 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

Trình liên kết nhìn thấy các ký hiệu đã xuất và liên kết nó. Bây giờ chúng ta thử bỏ ghi chú các 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, việc liên kết vẫn 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 bởi 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 nào 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 chữ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ó thành 456789. Cố gắng tạo một tệp đối tượng từ nó

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

được rồi, xem đầu ra tự đọc (ký hiệu)

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

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à - "tham chiếu không xác định / lỗi ký hiệu bên ngoài 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 ký hiệu chung trong tệp đối tượng.

73
Luchian Grigore 2012-09-25 13:39.

Các ký hiệu đượ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 định nghĩa 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ẽ bị xáo trộn, 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 một chương trình C ++ có 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 như sau;

extern "C" {
    #include "cheader.h"
}
70
sgryzko 2013-12-04 08:11.

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

Gần đây, tôi đã có thể loại bỏ lỗi bên ngoài chưa được khắc phục 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ó 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ẽ không thành công vì nó không thể tìm thấy B.lib. A.lib sẽ được tạo, nhưng không có dll. Sau đó, bạn biên dịch B, sẽ thành công và tạo ra B.lib. Việc biên dịch lại A bây giờ sẽ hoạt động vì B.lib hiện đã được tìm thấy.

60
Luchian Grigore 2012-09-25 13:39.

Nhập / xuất các 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 các ký hiệu để xuất và nhập bằng cách sử dụ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 đó, khai báo:

DLLIMPEXP void foo();

mở rộng đến

__declspec(dllexport) void foo();

và yêu cầu 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 thành

__declspec(dllimport) void foo();

và nói với 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 với (xem thêm 1) ).

Bạn có thể nhập / xuất các lớp tương tự:

class DLLIMPEXP X
{
};
59
Luchian Grigore 2012-09-25 13:38.

Triển khai mẫu không hiển thị.

Các mẫu không chuyên dụng phải hiển thị định nghĩa của chúng cho tất cả các đơn vị dịch thuật sử dụng chúng. Điều đó có nghĩa là bạn không thể tách định nghĩa của một mẫu thành một tệp triển khai. Nếu bạn phải tách việc triển khai, giải pháp 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::foovào tệp tiêu đề hoặc một số nơi hiển thị cho đơn vị dịch sử dụng nó.

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

Để biết thêm lời giải thích và một giải pháp khả thi khác (thuyết minh rõ ràng), hãy xem câu hỏi và câu trả lời này .

58
Nima Soroush 2015-04-14 06:42.

Đâ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. Hãy làm cho mọi thứ rõ ràng trước.

A. Biểu tượng là gì? 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 ngôn ngữ C ++. Nó được người dùng xác định hoặc giới thiệu bởi một thư viện phụ thuộc (một người dùng khác do người dùng định nghĩa).

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 này) Mọi thứ trong đơn vị dịch đượ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 ký hiệu 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" là gì? 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 tệp đối tượng mà không thể tìm thấy định nghĩa của nó trong nội bộ. 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à các phần phụ thuộc bổ sung của ứng dụng xây dựng 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 Ký hiệu 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 ký hiệu không có định nghĩa bên trong, nó sẽ báo cáo lỗi Ký hiệu Bên ngoài Chưa được Giải quyết.

E. Nguyên nhân có thể gây ra LNK2019 : Lỗi Biểu tượng Bên ngoài chưa được khắc phục. 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ó có thể được sắp xếp như:

  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 định nghĩa 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 một thân hàm khác, hãy nói 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). Đây là trường hợp mà định nghĩa tồn tại.

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

Nếu chúng ta muốn gọi một số hàm 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 (đặ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 vì định nghĩa không tồn tại trong phạm vi tìm kiếm hiện tại.

43
πάντα ῥεῖ 2014-03-04 13:52.

tham chiếu không xác định đến [email protected]hoặc tham chiếu điểm vào 'bất thường' tương tựmain() (đặc biệt đối với visual-studio ).

Bạn có thể đã bỏ qua việc chọn loại dự án phù hợp với IDE thực tế của mình. IDE có thể muốn liên kết các dự án Ứng dụng Windows, ví dụ như các dự án điểm vào như vậy (như được chỉ định trong tài liệu tham khảo còn 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 thuần túy, 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à Sự cố khi nhập thư viện vào dự án c ++ của tôi, làm cách nào để khắc phục sự cố này?case2 được xử lý chi tiết hơn từ một vấn đề trong thế giới thực .

37
Dula 2014-06-19 07:06.

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 rằng bạn có mã nhị phân 32/64 bit chính xác

35
Niall 2014-09-10 02:09.

Microsoft cung cấp một #pragmatham chiếu đến đú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.

35
Malvineous 2015-01-17 16:24.

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 một 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 vào 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 để giữ nguyên các đường dẫn tên tệp 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.

35
Noname 2015-08-12 05:33.

Giả sử bạn có một dự án lớn được viết bằng c ++ có một nghìn tệp .cpp và một nghìn tệp .h Và giả sử dự án cũng phụ thuộc vào mười thư viện tĩnh. Giả sử chúng tôi đang sử dụng 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 ta chỉ có một dự án trong giải pháp)

Ý nghĩa của việc biên dịch là gì?

  • Tìm kiếm Visual Studio trong 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 được 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 tệp .cpp phụ thuộc vào tệp .h bổ sung để tìm các ký hiệu có thể được xác định hoặc không đượ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 trình biên dịch sẽ xuất hiện thông báo Không thể tìm thấy biểu tượng x
  • Đố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 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 thứ hai của quá trình biên dịch được thực hiện bởi Linker.Linker nên hợp nhất tất 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 liên kết dự án

  • Phân tích cú pháp tất cả các tệp đối tượng và tìm định nghĩa chỉ được khai báo trong tiêu đề (ví dụ: Mã của một phương thức của 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 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 ký hiệu mà bạn viết trong một .cpp, anh ta sẽ tạo ra lỗi thời gian của trình liên kết , điều này có thể giống như error LNK2001: unresolved external symbol "void __cdecl foo(void)" ([email protected]@YAXXZ)

Quan sát

  1. Khi Người liên kết tìm thấy một biểu tượng, anh ta không tìm kiếm nó trong các thư viện khác
  2. Thứ tự liên kết các thư viện rất quan trọng .
  3. Nếu Trình liên kết tìm thấy một biểu tượng bên ngoài trong một thư viện tĩnh, anh ta đưa biểu tượng đó vào đầ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ã (biểu tượng) trong đầu ra, nhưng Run-Time có thể gặp sự cố xảy ra

Cách giải quyết loại lỗi này

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

  • Đảm bảo rằng bạn viết đúng cú pháp của dự án c ++.

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

  • Xác định tất cả ký hiệu mà bạn khai báo trong tệp tiêu đề của mình
  • 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 ký hiệu có thể xung đột với các ký hiệu khác mà bạn đã xác định trong tệp tiêu đề của mình
  • Khi bạn sử dụng mẫu, hãy đảm bảo rằng bạn bao gồm định nghĩa của từng chức năng 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ỳ khởi tạo nào.
29
developerbmw 2015-01-03 12:06.

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

Gần đây tôi đã gặp sự cố 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 đó có thể là 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ừ mã nguồn gốc.
27
Niall 2015-07-28 00:20.

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 có thể in ra ở các mức độ khác nhau;

  • Liên kết lời gọi (dòng lệnh),
  • Dữ liệu về những thư viện nào đượ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à clang; bạn thường 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
kiriloff 2014-04-05 05:02.

Tệp .lib được liên kết được liên kết với .dll

Tôi gặp vấn đề tương tự. Giả sử 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 dưới dạng DLL cho MyProject được tạo. 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ỉ có quyền 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ó đang biên dịch vì lib mà TestProject được liên kết chứa mã cho tất cả các phương thức trong các lớp trong MyProject.

25
Plankalkül 2015-09-11 01:03.

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

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

Nếu bạn gặp lỗi trình 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ì điều đó có thể chỉ ra rằng bạn đang cố gắng liên kết các tệp đối tượng với nhau đượ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 cần phải 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 trình liên kết khi chuyển sang GCC sau 5.1.0, đây sẽ là một điều cần kiểm tra.

21
Mike Kinghan 2017-04-10 00:34.

Liên kết của bạn sử dụng các thư viện trước các tệp đối tượng tham chiếu đến chúng

  • Bạn đang cố gắng biên dịch và liên kết chương trình của mình 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 số lỗi.
  • Nhưng tất cả những thứ không xác định được khai báo trong tệp tiêu đề mà bạn có #included và trên thực tế được xác định trong thư viện mà bạn đang liên kết.

Ví dụ bằng C. Chúng cũng có thể là C ++

Một ví dụ tối thiểu liên quan đến thư viện tĩnh do 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

vd1.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 mình:

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

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

$ 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 được chia sẻ, thư viện nén libz

vd2.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à không thành công:

$ 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 bao gồm 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 mà 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 đúng ví dụ 1:

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

Sự thành công:

$ ./eg1 
Hello World

Liên kết đúng ví dụ 2:

$ gcc -o eg2 eg2.o -lz

Sự thành công:

$ ./eg2 
1.2.8

Liên kết pkg-configchính xác biến thể ví dụ 2 :

$ 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 trở đi .

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

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

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 trình tự liên kết và tìm 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 trong đó hay không libmy_lib.a.

libmy_lib.aCụ thể là chỉ có tệp đối tượng trong my_lib.ovà chỉ có một thứ được định nghĩa trong đó my_lib.o, cụ thể 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 tham chiếu đế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 mà nó đã thêm chứa định nghĩa cho hw.

Nếu điều đó là đúng, thì trình liên kết sẽ trích xuất một bản sao của my_lib.othư 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, do đó, các tham chiếu đến 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 đã không 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 libmy_lib.a.

Tiếp theo, nó tìm 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 tạo 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 được chia sẻlibz

Thư viện được chia sẻ không phải là nơi lưu trữ các tệp đối tượng hoặc bất kỳ thứ gì tương tự. Nó giống một chương trình không có mainchức năng hơn và thay vào đó hiển thị nhiều ký hiệu khác mà nó định nghĩa, để các chương trình khác có thể sử dụng chúng trong thời gian 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 bản phân phối đó.

Đ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à phát hiện ra rằng điều này đề cập đến thư viện được chia sẻ (giả sử) /usr/lib/x86_64-linux-gnu/libz.so, nó muốn biết liệu bất kỳ tham chiếu 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 xuất khẩu bởilibz

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

  • Trong thời gian chạy, trình tải chương trình hệ thống sẽ tải một bản sao của libzvào cùng một quy 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 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 libztham chiếu đến thứ gì đó được định nghĩa , tham chiếu đó sẽ sử dụng định nghĩa được xuất bởi bản sao của libztrong cùng một quy trình.

Chương trình của bạn chỉ muốn đề cập đến một thứ có định nghĩa được xuất bởi libz, cụ thể là hàm zlibVersion, được tham chiếu 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 sai giống như ví dụ 1. Tại thời điểm khi trình liên kết tìm thấy -lz, không có tham chiếu nào đến bất kỳ thứ gì trong chương trình: tất cả chúng đều ở trong eg2.o, chưa được nhìn thấy. Vì vậy, trình liên kết quyết định nó không có giá trị sử dụng libz. Khi nó đạt đến eg2.o, hãy thêm nó vào chương trình, và sau đó có tham chiếu không xác định đến zlibVersion, trình tự liên kết đã kết thúc; tham chiếu đó chưa được giải quyết và liên kết không thành công.

Cuối cùng, pkg-configbiến thể của ví dụ 2 đã có 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

mà 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

Sự liên kết:

gcc -o eg2 -lz eg2.o

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

(Hoặc: Liên kết đó hoạt động tốt cho bạn, chẳng hạn như 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 định cấu hình chuỗi công cụ GCC của nó để liên kết các thư viện được chia sẻ khi cần thiết .

Ngày trước, các hệ thống giống unix liên kết các thư viện tĩnh và thư viện 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 một chuỗi liên kết được liên kết trên cơ sở khi cần thiết đượ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 tiết kiệm tại thời gian liên kết vì trình liên kết không phải cân nhắc xem chương trình có cần một thư viện chia sẻ hay không: nếu đó là một thư viện được chia sẻ, 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 đều là thư viện dùng chung. Nhưng cũng có nhược điểm: -

  • Nó không kinh tế trong thời gian chạy , vì nó có thể khiến các thư viện dùng chung đượ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 thư viện tĩnh và thư viện được chia sẻ có thể gây nhầm lẫn cho các lập trình viên không thành thạo, những người có thể không biết liệu -lfooliên kết của họ sẽ giải quyết đến /some/where/libfoo.ahay đến /some/where/libfoo.sovà dù sao cũng có thể không hiểu sự khác biệt giữa thư viện được chia sẻ và thư viện tĩnh.

Sự đánh đổi này đã dẫn đến tình trạng phân liệt ngày nay. Một số bản phân phối đã 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 khi cần thiết áp dụng cho tất cả các thư viện. Một số bản phân phối đã bị mắc kẹt với cách cũ.

Tại sao tôi vẫn gặp sự cố 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ì vậy, làm thế nào nó có thể không 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ì việc biên dịch và liên kết bằng một lệnh duy nhất không làm thay đổi thứ tự của chuỗi liên kết.

Khi bạn chạy lệnh trên, hãy gcctìm ra 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 mà bạn nhận thấy trong lỗi này 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, vì bạn không yêu cầu 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ự mà 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 hơn là các tệp cung cấp định nghĩa. Đặt thư viện trước các tệp đối tượng tham chiếu đến chúng là một cách khác khiến bạn mắc lỗi tương tự.

20
JDiMatteo 2015-03-31 06:03.

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

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

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 mainwin gcc wrapper mwdip loại bỏ các tệp lệnh tập lệnh trình liên kết trong danh sách đầu ra dài 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 đầu vào tập lệnh trình liên kết tệp với một bản sao của tệp thay thế (hoặc một liên kết tượng trưng), ví dụ:

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ì -ltbbdo/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2

18
Niall 2016-03-10 02:08.

Mẫu kết bạn ...

Đưa ra đoạn mã của một loại 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);
};

Các operator<<đã được khai báo là một tổ chức phi mẫu chức năng. Đối với mỗi loại Tđược sử dụng với Foo, cần phải có một không khuôn mẫu operator<<. Ví dụ, nếu có một kiểu được Foo<int>khai báo, thì phải có một triển khai toán tử như sau;

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

Vì nó không được triển khai, 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ử khuôn mẫu trước Fookiểu và sau đó khai báo là bạn, tức thời thích hợp. Cú pháp là một chút khó xử, nhưng trông 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 nhà điều hành đối với việc khởi tạo tương ứng Foo, nghĩa là việc operator<< <int>khởi tạo bị 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 giải pháp thay thế bao gồm;

  • Cho phép tình bạn mở rộng đến tất cả các bản tóm tắt 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 triển khai cho 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 toán tử (hoặc hàm) chỉ xuất hiện trong lớp, tên này không khả dụng cho tra cứu "bình thường", chỉ 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 vùng tên bao bọc 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 vào đối số xem xét X) trừ khi khai báo phù hợp ở phạm vi vùng tên là cung cấp ...

Có thêm bài đọc về bạn bè mẫu tại cppreferenceCâu hỏi thường gặp về C ++ .

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


Như một lưu ý phụ cho mẫu mã không đạt; 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
fafaro 2017-03-03 20:57.

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

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

Trình liên kết hoạt động như thế nào? Trình liên kết khớp 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 được chia sẻ) bằng cách so sánh chữ ký của chúng. Bạn có thể gặp lỗi trình liên kết nếu trình liên kết không tìm thấy định nghĩa hàm phù hợp hoàn hảo.

Có thể vẫn gặp lỗi trình liên kết mặc dù khai báo và định nghĩa có vẻ khớp nhau không? Đú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 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 tùy 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? Tất nhiên là bao gồm các đường dẫn ! Nếu khi biên dịch thư viện được chia sẻ, đườ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 tự hỏi điều gì đã xảy ra (ý định chơi chữ).

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

Chi tiết hơn với một ví dụ

Tôi có hai dự án: graphics.libmain.exe. Cả hai dự án 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ùm! Bạn gặp lỗi trình liên kết và bạn không biết tại sao nó không thành công. 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õ điều này ở đâ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 như vậy. Có thể đường dẫn bao gồm khác 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(), trong khi thực tế bạn biết nó rõ ràng đang được xuất bởi thư viện. Bạn có thể dành hàng giờ để vò đầu bứt tai tự hỏi điều gì đã xảy ra. Vấn đề là, trình liên kết thấy một chữ ký khác vì các loại tham số hơi khác nhau. Trong ví dụ, vec3là một kiểu 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 rằng 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 bị xáo trộn kỳ lạ được đưa ra trong lỗi trình liên kết. (ví dụ: draw @ graphics @ XYZ).
  2. Đưa các ký hiệu đã xuất từ ​​thư viện vào một tệp văn bản.
  3. Tìm kiếm biểu tượng được quan tâm đã xuất và lưu ý rằng tên bị xáo trộn là khác.
  4. Hãy chú ý đến lý do tại sao những cái tên bị xáo trộn cuối cùng lại 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 chúng khác nhau. Trong ví dụ ở 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.

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

15
Niall 2016-04-08 01:53.

Các 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ọ.

Việc xây dựng một thư viện với thư viện đã UNICODExác định và cố gắng liên kết nó trong một dự án UNICODEchưa được xác định sẽ dẫn đến lỗi trình liên kết vì sẽ có sự không khớp trong định nghĩa của TCHAR; charvs wchar_t.

Lỗi thường bao gồm một hàm một giá trị có kiểu charhoặc wchar_tkiểu dẫn xuất, chúng cũng có thể bao gồm, std::basic_string<>v.v. Khi duyệt qua hàm bị ảnh hưởng trong mã, thường sẽ có tham chiếu đến TCHARhoặc std::basic_string<TCHAR>v.v. Đây là dấu hiệu cho thấy mã ban đầu được dành cho cả UNICODE và bản dựng 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 yêu cầu với định nghĩa nhất quán về 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
    

Phương án thay thế cũng có thể áp dụng được, nếu UNICODE không được dự định 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 nhất quán giữa bản dựng "Phát hành" và "Gỡ lỗi".

14
Niall 2016-02-25 00:40.

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

Một công trình "sạch" có thể loại bỏ "phần gỗ chết" có thể còn sót lại từ các bản xây dựng trước đó, các bản xây dựng bị lỗi, các bản dựng chưa hoàn thiện và các vấn đề 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 định cấu hình chính xác (ví dụ như trong tệp tạo thủ công) hoặc có thể bị lỗi (ví dụ: tệp nhị phân trung gian hoặc kết quả là chỉ đọc).

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

Quá trình này có thể được coi là phương sách cuối cùng, nhưng thường là bước đầu tiên tốt ; đặc biệt 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
Andreas H. 2017-08-03 22:01.

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ể ngạc nhiên rằng trong C ++ các constbiến toàn cục có liên kết nội bộ (hoặc tĩnh). Trong C thì không phải như vậy, vì tất cả các biến toàn cục là ngầm định extern(tức 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ớiextern

8
Stypox 2018-08-28 06:43.

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, nhưng tôi muốn chia sẻ cách giải quyết lỗi "tham chiếu không xác định đến" khó hiểu .

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

Tôi đang sử dụng một bí danh để tham chiếu std::filesystem::path: hệ thống tệp nằm trong thư viện 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 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 # include's < testing :: filesystem > và chứa đoạn mã ở trên
  • file.cpp , việc triển khai " file.h " của file.h, # include
  • main.cpp # include's < filesystem > 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 # include'd " file.h " sau < filesystem >, nên phiên bản hệ thống tệp đượ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ỳ chức năng chứa trong file.o và được sử dụng trong main.o rằng cầnpath_t đưa ra "tài liệu tham khảo không xác định" sai lầm vì main.o gọi std::filesystem::pathnhưng file.o tới std::experimental::filesystem::path.

Độ phân giải

Để khắc phục điều này, tôi chỉ cần thay đổi <Experiment :: filesystem> trong file.h thành <filesystem> .

5
ead 2018-09-07 20:22.

Khi liên kết với các thư viện được chia sẻ, hãy đảm bảo rằng các ký hiệu đã 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 hiển thị. 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 hàm / ký hiệu được đánh dấu __attribute__ ((visibility ("default")))là bên ngoài trong đối tượng được chia sẻ kết quả.

Bạn có thể kiểm tra xem các ký hiệu 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 nmtùy chọn -Cđể phân loại tên (nếu sử dụng C ++).

Tương tự như Windows-dlls, người ta sẽ đánh dấu các chức năng công khai bằng một định nghĩa, ví dụ DLL_PUBLICđược định nghĩa 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

Có thể tìm thấy thêm thông tin về khả năng hiển thị 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 bên ngoài (được hiển thị với kiểu ký hiệu chữ hoa bởi nm) và có thể được sử dụng để liên kết bên ngoài mà không có vấn đề gì nếu 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 một thư viện được chia sẻ.

Để tìm ký hiệu nào trong tệp đối tượng bị ẩn, hãy chạy:

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2
3
Mike Kinghan 2019-01-22 09:44.

Hàm hoặc phương thức lớp được định nghĩa trong tệp nguồn với trình inlinexác định.

Một ví dụ:-

main.cpp

#include "gum.h"
#include "foo.h"

int main()
{
    gum();
    foo f;
    f.bar();
    return 0;
}

foo.h (1)

#pragma once

struct foo {
    void bar() const;
};

gôm.h (1)

#pragma once

extern void gum();

foo.cpp (1)

#include "foo.h"
#include <iostream>

inline /* <- wrong! */ void foo::bar() const {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

gum.cpp (1)

#include "gum.h"
#include <iostream>

inline /* <- wrong! */ void gum()
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

Nếu bạn chỉ định rằng gum(tương tự, foo::bar) inlinetheo định nghĩa của nó thì trình biên dịch sẽ nội tuyến gum(nếu nó chọn), bằng cách: -

  • không đưa ra bất kỳ định nghĩa duy nhất nào về gum, và do đó
  • không phát ra bất kỳ ký hiệu nào mà trình liên kết có thể tham chiếu đến định nghĩa gumvà thay vào đó
  • thay thế tất cả các lệnh gọi đến gumbằng các bản sao nội tuyến của phần thân đã biên dịch của gum.

Kết quả là, nếu bạn xác định gumnội tuyến trong tệp nguồn gum.cpp, nó sẽ được biên dịch thành tệp đối tượng gum.otrong đó tất cả các lệnh gọi đến gumđều được nội tuyến và không có ký hiệu nào được xác định mà trình liên kết có thể tham chiếu đến gum. Khi bạn liên kết gum.ovào một chương trình cùng với một tệp đối tượng khác, ví dụ như main.o tạo tham chiếu đến một ký hiệu bên ngoài gum, trình liên kết không thể giải quyết các tham chiếu đó. Vì vậy, liên kết không thành công:

Biên dịch:

g++ -c  main.cpp foo.cpp gum.cpp

Liên kết:

$ g++ -o prog main.o foo.o gum.o
main.o: In function `main':
main.cpp:(.text+0x18): undefined reference to `gum()'
main.cpp:(.text+0x24): undefined reference to `foo::bar() const'
collect2: error: ld returned 1 exit status

Bạn chỉ có thể định nghĩa gumnhư inlinethể trình biên dịch có thể thấy định nghĩa của nó trong mọi tệp nguồn gumcó thể được gọi. Điều đó có nghĩa là định nghĩa nội tuyến của nó cần phải tồn tại trong tệp tiêu đề mà bạn đưa vào mọi tệp nguồn mà bạn biên dịch trong đó gumcó thể được gọi. Thực hiện một trong hai điều:

Hoặc không nội dòng các định nghĩa

Xóa thông số kỹ thuật inlinekhỏi định nghĩa tệp nguồn:

foo.cpp (2)

#include "foo.h"
#include <iostream>

void foo::bar() const {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

gum.cpp (2)

#include "gum.h"
#include <iostream>

void gum()
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

Xây dựng lại với điều đó:

$ g++ -c  main.cpp foo.cpp gum.cpp
[email protected]:~/develop/so/scrap1$ g++ -o prog main.o foo.o gum.o
[email protected]:~/develop/so/scrap1$ ./prog
void gum()
void foo::bar() const

Sự thành công.

Hoặc nội dòng một cách chính xác

Định nghĩa nội tuyến trong tệp tiêu đề:

foo.h (2)

#pragma once
#include <iostream>

struct foo {
    void bar() const  { // In-class definition is implicitly inline
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};
// Alternatively...
#if 0
struct foo {
    void bar() const;
};
inline void foo::bar() const  {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}
#endif

gôm.h (2)

#pragma once
#include <iostream>

inline void gum() {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

Bây giờ chúng tôi không cần foo.cpphoặc gum.cpp:

$ g++ -c main.cpp
$ g++ -o prog main.o
$ ./prog
void gum()
void foo::bar() const

Related questions

MORE COOL STUFF

Cate Blanchett chia tay chồng sau 3 ngày bên nhau và vẫn kết hôn với anh ấy 25 năm sau

Cate Blanchett chia tay chồng sau 3 ngày bên nhau và vẫn kết hôn với anh ấy 25 năm sau

Cate Blanchett đã bất chấp những lời khuyên hẹn hò điển hình khi cô gặp chồng mình.

Tại sao Michael Sheen là một diễn viên phi lợi nhuận

Tại sao Michael Sheen là một diễn viên phi lợi nhuận

Michael Sheen là một diễn viên phi lợi nhuận nhưng chính xác thì điều đó có nghĩa là gì?

Hallmark Star Colin Egglesfield Các món ăn gây xúc động mạnh đối với người hâm mộ tại RomaDrama Live! [Loại trừ]

Hallmark Star Colin Egglesfield Các món ăn gây xúc động mạnh đối với người hâm mộ tại RomaDrama Live! [Loại trừ]

Ngôi sao của Hallmark Colin Egglesfield chia sẻ về những cuộc gặp gỡ với người hâm mộ ly kỳ tại RomaDrama Live! cộng với chương trình INSPIRE của anh ấy tại đại hội.

Tại sao bạn không thể phát trực tuyến 'chương trình truyền hình phía Bắc'

Tại sao bạn không thể phát trực tuyến 'chương trình truyền hình phía Bắc'

Bạn sẽ phải phủi sạch đầu đĩa Blu-ray hoặc DVD để xem tại sao Northern Exposure trở thành một trong những chương trình nổi tiếng nhất của thập niên 90.

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

8 công dụng tuyệt vời của Baking Soda và Giấm

8 công dụng tuyệt vời của Baking Soda và Giấm

Bạn biết đấy, hai sản phẩm này là nguồn điện để làm sạch, riêng chúng. Nhưng cùng với nhau, chúng có một loạt công dụng hoàn toàn khác.

Hạn hán, biến đổi khí hậu đe dọa tương lai của thủy điện Hoa Kỳ

Hạn hán, biến đổi khí hậu đe dọa tương lai của thủy điện Hoa Kỳ

Thủy điện rất cần thiết cho lưới điện của Hoa Kỳ, nhưng nó chỉ tạo ra năng lượng khi có nước di chuyển. Bao nhiêu nhà máy thủy điện có thể gặp nguy hiểm khi các hồ và sông cạn kiệt?

Quyên góp tóc của bạn để giúp giữ nước sạch của chúng tôi

Quyên góp tóc của bạn để giúp giữ nước sạch của chúng tôi

Tóc tỉa từ các tiệm và các khoản quyên góp cá nhân có thể được tái sử dụng như những tấm thảm thấm dầu và giúp bảo vệ môi trường.

Tận dụng lợi thế của việc bán hàng nhân Ngày của Cha này

Tận dụng lợi thế của việc bán hàng nhân Ngày của Cha này

Màn hình Samsung Galaxy S9 Plus. Chủ nhật này là Ngày của Cha⁠ — trong trường hợp nó khiến bạn suy nghĩ — và thay vì mua cho anh ấy một chiếc cà vạt trong năm nay, có lẽ đã đến lúc bạn mua cho anh ấy thứ mà anh ấy sẽ thực sự sử dụng.

Assassin's Creed Snuck Into Monster Hunter: World Last Night

Assassin's Creed Snuck Into Monster Hunter: World Last Night

Monster Hunter: World yêu thích các sự kiện chéo. Dù có nghĩa là hóa trang thành Dante của Devil May Cry, giả dạng Horizon: Zero Dawn's Aloy, hay chiến đấu với quái vật Final Fantasy, các nhiệm vụ sự kiện khác nhau của Thế giới được nâng cấp tự do so với các trò chơi khác.

Ava DuVernay Có Quà Tặng Ngày Của Mẹ cho Tất Cả Chúng Ta: Nếp Nhăn Thời Gian Sẽ Được Viết Lại Vào Cuối Tuần Ngày Của Mẹ!

Ava DuVernay Có Quà Tặng Ngày Của Mẹ cho Tất Cả Chúng Ta: Nếp Nhăn Thời Gian Sẽ Được Viết Lại Vào Cuối Tuần Ngày Của Mẹ!

Storm Reid, Oprah Winfrey, Mindy Kaling, Reese Witherspoon và Ava DuVernay tại buổi chiếu đặc biệt của A Wrinkle in Time tại Nhà hát Walter Reade ở Thành phố New York vào ngày 7 tháng 3 năm 2018 “Ava rất mong được nói chuyện với bạn,” một trong những người của Array dư luận viên nói qua điện thoại. (Array là tập thể phân phối, nghệ thuật và vận động chính sách của Ava DuVernay tập trung vào các bộ phim của người da màu và phụ nữ.

Nhiệm vụ bất khả thi 5 sẽ khôi phục niềm tin của bạn trong phim hành động Tentpole

Nhiệm vụ bất khả thi 5 sẽ khôi phục niềm tin của bạn trong phim hành động Tentpole

Mission Impossible: Rogue Nation bắt đầu ở một cấp độ khác. Theo nghĩa đen.

Edwin McCain ra mắt Grand Ole Opry: Quay cảnh hậu trường với nhạc sĩ 'I'll Be'

Edwin McCain ra mắt Grand Ole Opry: Quay cảnh hậu trường với nhạc sĩ 'I'll Be'

McCain, người đang làm việc cho một album mới, lần đầu tiên bước vào vòng kết nối vào tối thứ Sáu ở Nashville

Nicky Hilton Forced to Borrow Paris' 'I Love Paris' Sweatshirt After 'Airline Loses All [My] Luggage'

Nicky Hilton Forced to Borrow Paris' 'I Love Paris' Sweatshirt After 'Airline Loses All [My] Luggage'

Nicky Hilton Rothschild's luggage got lost, but luckily she has an incredible closet to shop: Sister Paris Hilton's!

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa. Từ Hollywood đến New York và mọi nơi ở giữa, hãy xem các ngôi sao yêu thích của bạn đang làm gì!

17 tuổi bị đâm chết trong khi 4 người khác bị thương trong một cuộc tấn công bằng dao trên sông Wisconsin

17 tuổi bị đâm chết trong khi 4 người khác bị thương trong một cuộc tấn công bằng dao trên sông Wisconsin

Các nhà điều tra đang xem xét liệu nhóm và nghi phạm có biết nhau trước vụ tấn công hay không

Tôi viết như thế nào

Tôi viết như thế nào

Đối với tôi, mọi thứ là về dòng đầu tiên đó và nó sẽ đưa bạn đến đâu. Một số nhà văn bị điều khiển bởi cốt truyện, sự sắp xếp tinh tế của các quân cờ, trong khi những người khác bị lôi cuốn bởi một nhân vật và khả năng thực hiện một cuộc hành trình với một người bạn hư cấu mới.

Đường băng hạ cánh

Đường băng hạ cánh

Cuối hè đầu thu là mùa hoài niệm. Những chiếc đèn đường chiếu ánh sáng của chúng qua những con đường đẫm mưa, và những chiếc lá dưới chân - màu đỏ cam tắt trong bóng chạng vạng - là lời nhắc nhở về những ngày đã qua.

Hãy tưởng tượng tạo ra một chiến lược nội dung thực sự CHUYỂN ĐỔI. Nó có thể.

Hãy tưởng tượng tạo ra một chiến lược nội dung thực sự CHUYỂN ĐỔI. Nó có thể.

Vào năm 2021, tôi khuyến khích bạn suy nghĩ lại mọi thứ bạn biết về khách hàng mà bạn phục vụ và những câu chuyện bạn kể cho họ. Lùi lại.

Sự mất mát của voi ma mút đã mở ra trái tim tôi để yêu

Sự mất mát của voi ma mút đã mở ra trái tim tôi để yêu

Vào ngày sinh nhật thứ 9 của Felix The Cat, tôi nhớ về một trong những mất mát lớn nhất trong cuộc đời trưởng thành của tôi - Sophie của tôi vào năm 2013. Tôi đã viết bài luận này và chia sẻ nó trên nền tảng này một thời gian ngắn vào năm 2013.

Language