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?
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.
Trả lời
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] .
- 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]
- 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]
- 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]
- 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]
- 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]
- Các mã thông báo theo chuỗi liền kề được nối với nhau.
- 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]
- Đơn vị dịch đã dịch và đơn vị trình diễn được kết hợp như sau: [SNIP]
- 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 a
trong 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 .lib
cá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 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:
- 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
- Biến hoặc hàm đã khai báo và không xác định.
- Các vấn đề thường gặp với các thành viên kiểu lớp
- Triển khai mẫu không hiển thị.
- Các ký hiệu được định nghĩa trong chương trình C và được sử dụng trong mã C ++.
- 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. (MSVS cụ thể)
- Phụ thuộc thư viện tròn
- tham chiếu không xác định đến `WinMain @ 16 '
- Thứ tự thư viện phụ thuộc lẫn nhau
- Nhiều tệp nguồn có cùng tên
- Nhập nhầm hoặc không bao gồm phần mở rộng .lib khi sử dụng
#pragma
(Microsoft Visual Studio) - Vấn đề với bạn mẫu
- Các
UNICODE
định nghĩa không nhất quán - Thiếu "extern" trong khai báo / định nghĩa biến const (chỉ C ++)
Các thành viên trong lớp:
Một trình virtual
hủ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 virtual
phươ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 virtual
thà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à inline
ngă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() {}
static
cá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
const
thà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 const
thành viên dữ liệu.
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 libraryName
chỉ 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.so
như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 -l
hoặ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 lib
tệ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 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 đượ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 lib
tệ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".
Đã 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.
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 A
phụ thuộc vào thư viện B
, thì libA
PHẢI xuất hiện trước libB
trong 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!
"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;
}
và
// 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.
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"
}
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.
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)
và __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_MODULE
sẽ 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
{
};
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 impl
tệ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::foo
và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 .
Đâ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ư:
- Đị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.
- Đị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.
tham chiếu không xác định đến WinMain@16
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à case1 và case2 được xử lý chi tiết hơn từ một vấn đề trong thế giới thực .
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
Microsoft cung cấp một #pragma
tham 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.
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 packages
thư mục bên trong thư mục của giải pháp) bằng cách tìm packagename\build\native\packagename.targets
và bên trong tệp đó, sao chép tất cả các v110
phần. Tôi đã thay đổi v110
thành v120
trong 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.
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)" (?foo@@YAXXZ)
Quan sát
- 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
- Thứ tự liên kết các thư viện rất quan trọng .
- 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.
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.
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,--verbose
hoặc -v -Wl,-v
vào dòng lệnh. Nhiều thông tin thêm có thế được tìm thấy ở đây;
- Trang người dùng Linux ld .
- Trang liên kết LLVM .
- "Giới thiệu về GCC" chương 9 .
Đối với MSVC, /VERBOSE
(đặc biệt /VERBOSE:LIB
) được thêm vào dòng lệnh liên kết.
- Trang MSDN trên
/VERBOSE
tùy chọn trình liên kết .
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.
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.
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
libfoo
phụ thuộc vàolibbar
, thì liên kết của bạn đã đặt chính xáclibfoo
trướ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ó
#include
d 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.a
và 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 libz
và 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-config
chí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_lib
trong 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.a
Cụ thể là chỉ có tệp đối tượng trong my_lib.o
và 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.o
nế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.o
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
, 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.o
vào chương trình và không sử dụng thêm libmy_lib.a
.
Tiếp theo, nó tìm eg1.o
và 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 hw
kế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ó main
chứ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++
, gfortran
vv) 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 -lz
trong 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ừ đó libz
và 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
libz
và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
libz
tham 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ủalibz
trong 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-config
biế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
-lfoo
liên kết của họ sẽ giải quyết đến/some/where/libfoo.a
hay đến/some/where/libfoo.so
và 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.c
trướ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 gcc
tì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ự.
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ì -ltbb
do/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
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 Foo
kiể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 cppreference và Câ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)
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.h
và bạn kết thúc sử dụng header2.h
trong 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.lib
và main.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ụ, vec3
là 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:
- 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).
- Đưa các ký hiệu đã xuất từ thư viện vào một tệp văn bản.
- 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.
- 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.
- 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!
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_t
vv 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à char
vv Những UNICODE
và _UNICODE
định nghĩa ảnh hưởng đến tất cả các " T
" loại chuỗi ; LPTSTR
, LPCTSTR
và nai sừng tấm của họ.
Việc xây dựng một thư viện với thư viện đã UNICODE
xác định và cố gắng liên kết nó trong một dự án UNICODE
chư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
; char
vs wchar_t
.
Lỗi thường bao gồm một hàm một giá trị có kiểu char
hoặc wchar_t
kiể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 TCHAR
hoặ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
).
Điều này có thể được thực hiện với một trong hai;
#define UNICODE #define _UNICODE
Hoặc trong cài đặt dự án;
Thuộc tính dự án> Chung> Mặc định dự án> Bộ ký tự
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".
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).
Thiếu "extern" trong const
khai 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 const
biế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 static
thiế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 và file1.cpp
extern const int test;
extern int test2;
Ngoài ra, người ta có thể khai báo const
biến trong file1.cpp vớiextern
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::path
như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> .
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 nm
loại ký hiệu chữ thường, ví dụ t
thay 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 nm
tù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=hidden
cá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
Hàm hoặc phương thức lớp được định nghĩa trong tệp nguồn với trình inline
xá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
) inline
theo đị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
gum
và thay vào đó - thay thế tất cả các lệnh gọi đến
gum
bằng các bản sao nội tuyến của phần thân đã biên dịch củagum
.
Kết quả là, nếu bạn xác định gum
nộ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.o
trong đó 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.o
và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 gum
như inline
thể trình biên dịch có thể thấy định nghĩa của nó trong mọi tệp nguồn gum
có 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 đó gum
có 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 inline
khỏ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
imk@imk-Inspiron-7559:~/develop/so/scrap1$ g++ -o prog main.o foo.o gum.o
imk@imk-Inspiron-7559:~/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.cpp
hoặc gum.cpp
:
$ g++ -c main.cpp
$ g++ -o prog main.o
$ ./prog
void gum()
void foo::bar() const