Tại sao thứ tự các thư viện được liên kết đôi khi gây ra lỗi trong GCC?


Câu trả lời:


558

(Xem lịch sử về câu trả lời này để có được văn bản phức tạp hơn, nhưng bây giờ tôi nghĩ người đọc dễ dàng nhìn thấy các dòng lệnh thực sự hơn).


Các tệp phổ biến được chia sẻ bởi tất cả các lệnh bên dưới

$ cat a.cpp
extern int a;
int main() {
  return a;
}

$ cat b.cpp
extern int b;
int a = b;

$ cat d.cpp
int b;

Liên kết đến thư viện tĩnh

$ g++ -c b.cpp -o b.o
$ ar cr libb.a b.o
$ g++ -c d.cpp -o d.o
$ ar cr libd.a d.o

$ g++ -L. -ld -lb a.cpp # wrong order
$ g++ -L. -lb -ld a.cpp # wrong order
$ g++ a.cpp -L. -ld -lb # wrong order
$ g++ a.cpp -L. -lb -ld # right order

Trình liên kết tìm kiếm từ trái sang phải và ghi chú các ký hiệu chưa được giải quyết khi nó đi. Nếu một thư viện giải quyết ký hiệu, nó sẽ lấy các tệp đối tượng của thư viện đó để phân giải ký hiệu (bo ra khỏi libb.a trong trường hợp này).

Sự phụ thuộc của các thư viện tĩnh đối với nhau hoạt động như nhau - thư viện cần biểu tượng phải là đầu tiên, sau đó là thư viện giải quyết biểu tượng.

Nếu một thư viện tĩnh phụ thuộc vào thư viện khác, nhưng thư viện khác lại phụ thuộc vào thư viện cũ, có một chu kỳ. Bạn có thể giải quyết vấn đề này bằng cách kèm theo các thư viện phụ thuộc theo chu kỳ bằng -(-), chẳng hạn như -( -la -lb -)(bạn có thể cần phải thoát khỏi các parens, chẳng hạn như -\(-\)). Trình liên kết sau đó tìm kiếm những lib kèm theo nhiều lần để đảm bảo các phụ thuộc theo chu kỳ được giải quyết. Ngoài ra, bạn có thể chỉ định các thư viện nhiều lần, vì vậy mỗi thư viện nằm trước nhau : -la -lb -la.

Liên kết đến các thư viện động

$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -L. -ld -o libb.so # specifies its dependency!

$ g++ -L. -lb a.cpp # wrong order (works on some distributions)
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong order
$ g++ -Wl,--as-needed a.cpp -L. -lb # right order

Ở đây cũng vậy - các thư viện phải theo các tệp đối tượng của chương trình. Sự khác biệt ở đây so với các thư viện tĩnh là bạn không cần quan tâm đến sự phụ thuộc của các thư viện với nhau, bởi vì các thư viện động tự sắp xếp các phụ thuộc của chúng .

Một số bản phân phối gần đây dường như mặc định sử dụng --as-neededcờ liên kết, điều này cho thấy các tệp đối tượng của chương trình xuất hiện trước các thư viện động. Nếu cờ đó được thông qua, trình liên kết sẽ không liên kết đến các thư viện không thực sự cần thiết cho người thực thi (và nó phát hiện điều này từ trái sang phải). Phân phối archlinux gần đây của tôi không sử dụng cờ này theo mặc định, vì vậy nó không gây ra lỗi vì không tuân theo đúng thứ tự.

Nó không đúng để bỏ qua sự phụ thuộc b.sochống lại d.sokhi tạo cái trước. Bạn sẽ được yêu cầu chỉ định thư viện khi liên kết asau đó, nhưng athực sự không cần số nguyên b, vì vậy không nên thực hiện để quan tâm đến bcác phụ thuộc của chính nó.

Dưới đây là một ví dụ về các hàm ý nếu bạn bỏ lỡ chỉ định các phụ thuộc cho libb.so

$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -o libb.so # wrong (but links)

$ g++ -L. -lb a.cpp # wrong, as above
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong, as above
$ g++ a.cpp -L. -lb # wrong, missing libd.so
$ g++ a.cpp -L. -ld -lb # wrong order (works on some distributions)
$ g++ -Wl,--as-needed a.cpp -L. -ld -lb # wrong order (like static libs)
$ g++ -Wl,--as-needed a.cpp -L. -lb -ld # "right"

Nếu bây giờ bạn nhìn vào những gì phụ thuộc của nhị phân, bạn lưu ý rằng chính nhị phân cũng phụ thuộc vào libd, không chỉ libblà nó nên. Nhị phân sẽ cần phải được xem lại nếu libbsau này phụ thuộc vào thư viện khác, nếu bạn làm theo cách này. Và nếu người khác tải libbbằng cách sử dụng dlopentrong thời gian chạy (nghĩ về việc tải plugin một cách linh hoạt), cuộc gọi cũng sẽ thất bại. Vì vậy, "right"thực sự nên là một wrongnhư là tốt.


10
Lặp lại cho đến khi tất cả các biểu tượng được giải quyết, eh - bạn nghĩ rằng chúng có thể quản lý một loại cấu trúc liên kết. LLVM có 78 thư viện tĩnh, với các phụ thuộc ai biết được. Đúng là nó cũng có một tập lệnh để tìm ra các tùy chọn biên dịch / liên kết - nhưng bạn không thể sử dụng nó trong mọi trường hợp.
Steve314

6
@Steve đó là những gì các chương trình lorder+ tsortlàm. Nhưng đôi khi không có thứ tự, nếu bạn có tài liệu tham khảo theo chu kỳ. Sau đó, bạn chỉ cần quay vòng qua danh sách thư viện cho đến khi mọi thứ được giải quyết.
Julian Schaub - litb

10
@Johannes - Xác định các thành phần được kết nối mạnh tối đa (ví dụ thuật toán Tarjans) sau đó sắp xếp theo cấu trúc liên kết cấu trúc (vốn không phải là chu kỳ) của các thành phần. Mỗi thành phần có thể được coi là một thư viện - nếu cần một thư viện từ thành phần đó, (các) chu kỳ phụ thuộc sẽ khiến tất cả các thư viện trong thành phần đó là cần thiết. Vì vậy, không, thực sự không cần phải duyệt qua tất cả các thư viện để giải quyết mọi thứ và không cần các tùy chọn dòng lệnh khó xử - một phương pháp sử dụng hai thuật toán nổi tiếng có thể xử lý chính xác tất cả các trường hợp.
Steve314

4
Tôi muốn thêm một chi tiết quan trọng vào câu trả lời xuất sắc này: Sử dụng "- (lưu trữ -)" hoặc "- lưu trữ nhóm bắt đầu - nhóm -end" là cách duy nhất chắc chắn để giải quyết các phụ thuộc vòng tròn , vì mỗi lần trình liên kết truy cập vào một kho lưu trữ, nó kéo vào (và đăng ký các ký hiệu chưa được giải quyết của) chỉ các tệp đối tượng giải quyết các ký hiệu hiện chưa được giải quyết . Do đó, thuật toán lặp lại các thành phần được kết nối của CMake trong biểu đồ phụ thuộc đôi khi có thể bị lỗi. (Xem thêm bài viết tuyệt vời của Ian Lance Taylor trên các trình liên kết để biết thêm chi tiết.)
jorgen

3
Câu trả lời của bạn đã giúp tôi giải quyết các lỗi liên kết của mình và bạn đã giải thích rất rõ CÁCH để tránh gặp rắc rối, nhưng bạn có biết TẠI SAO nó được thiết kế để hoạt động theo cách này không?
Anton Daneyko

102

Trình liên kết ld GNU là một trình liên kết thông minh. Nó sẽ theo dõi các chức năng được sử dụng bởi các thư viện tĩnh trước đó, loại bỏ vĩnh viễn các chức năng không được sử dụng từ các bảng tra cứu của nó. Kết quả là nếu bạn liên kết một thư viện tĩnh quá sớm, thì các hàm trong thư viện đó sẽ không còn khả dụng cho các thư viện tĩnh sau này trên dòng liên kết.

Trình liên kết UNIX điển hình hoạt động từ trái sang phải, vì vậy hãy đặt tất cả các thư viện phụ thuộc của bạn ở bên trái và các thư viện đáp ứng các phụ thuộc đó ở bên phải của đường liên kết. Bạn có thể thấy rằng một số thư viện phụ thuộc vào những người khác đồng thời các thư viện khác phụ thuộc vào họ. Đây là nơi nó trở nên phức tạp. Khi nói đến tài liệu tham khảo tròn, sửa mã của bạn!


2
Đây có phải là một cái gì đó chỉ với gnu ld / gcc? Hoặc đây là một cái gì đó phổ biến với các trình liên kết?
Mike

2
Rõ ràng nhiều trình biên dịch Unix có vấn đề tương tự. MSVC không hoàn toàn không có những vấn đề này, xa hơn, nhưng chúng không có vẻ tồi tệ như vậy.
MSalters

4
Các công cụ phát triển MS không có xu hướng hiển thị các vấn đề này nhiều vì nếu bạn sử dụng chuỗi công cụ toàn MS, nó sẽ kết thúc việc thiết lập thứ tự liên kết đúng cách và bạn không bao giờ nhận thấy vấn đề.
Michael Kohne

16
Trình liên kết MSVC ít nhạy cảm hơn với vấn đề này vì nó sẽ tìm kiếm tất cả các thư viện cho một biểu tượng không được ước tính. Thư viện để vẫn có thể ảnh hưởng biểu tượng được giải quyết nếu có nhiều hơn một thư viện có biểu tượng. Từ MSDN: "Các thư viện cũng được tìm kiếm theo thứ tự dòng lệnh, với lời cảnh báo sau: Các biểu tượng không được giải quyết khi đưa tệp đối tượng từ thư viện được tìm kiếm trong thư viện đó trước, sau đó các thư viện sau từ dòng lệnh và / DEFAULTLIB (Chỉ định thư viện mặc định), sau đó đến bất kỳ thư viện nào ở đầu dòng lệnh "
Michael Burr

4
"... Trình liên kết thông minh ..." - Tôi tin rằng nó được phân loại là một trình liên kết "vượt qua một lần", không phải là "trình liên kết thông minh".
jww

54

Dưới đây là một ví dụ để làm rõ mọi thứ hoạt động với GCC khi các thư viện tĩnh có liên quan. Vì vậy, giả sử chúng ta có kịch bản sau đây:

  • myprog.o- chứa main()chức năng, phụ thuộc vàolibmysqlclient
  • libmysqlclient- tĩnh, vì lợi ích của ví dụ (tất nhiên, bạn thích thư viện dùng chung, vì libmysqlclientnó rất lớn); trong /usr/local/lib; và phụ thuộc vào những thứ từlibz
  • libz (năng động)

Làm thế nào để chúng ta liên kết này? (Lưu ý: ví dụ từ việc biên dịch trên Cygwin bằng gcc 4.3.4)

gcc -L/usr/local/lib -lmysqlclient myprog.o
# undefined reference to `_mysql_init'
# myprog depends on libmysqlclient
# so myprog has to come earlier on the command line

gcc myprog.o -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress'
# we have to link with libz, too

gcc myprog.o -lz -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress'
# libz is needed by libmysqlclient
# so it has to appear *after* it on the command line

gcc myprog.o -L/usr/local/lib -lmysqlclient -lz
# this works

31

Nếu bạn thêm -Wl,--start-group vào các cờ liên kết, nó không quan tâm chúng theo thứ tự nào hoặc nếu có phụ thuộc vòng tròn.

Trên Qt, điều này có nghĩa là thêm:

QMAKE_LFLAGS += -Wl,--start-group

Tiết kiệm vô số thời gian lộn xộn và dường như nó không làm chậm liên kết nhiều (dù mất ít thời gian hơn nhiều so với việc biên dịch).


8

Một cách khác là chỉ định danh sách các thư viện hai lần:

gcc prog.o libA.a libB.a libA.a libB.a -o prog.x

Làm điều này, bạn không phải bận tâm với trình tự đúng vì tham chiếu sẽ được giải quyết trong khối thứ hai.


5

Bạn có thể sử dụng tùy chọn -Xlinker.

g++ -o foobar  -Xlinker -start-group  -Xlinker libA.a -Xlinker libB.a -Xlinker libC.a  -Xlinker -end-group 

là CÒN bằng

g++ -o foobar  -Xlinker -start-group  -Xlinker libC.a -Xlinker libB.a -Xlinker libA.a  -Xlinker -end-group 

Cẩn thận !

  1. Thứ tự trong một nhóm là quan trọng! Đây là một ví dụ: thư viện gỡ lỗi có thói quen gỡ lỗi, nhưng thư viện không gỡ lỗi có phiên bản yếu giống nhau. Bạn phải đặt thư viện gỡ lỗi FIRST trong nhóm nếu không bạn sẽ giải quyết phiên bản không gỡ lỗi.
  2. Bạn cần đi trước mỗi thư viện trong danh sách nhóm với -Xlinker

5

Một mẹo nhanh làm tôi vấp ngã: nếu bạn gọi trình liên kết là "gcc" hoặc "g ++", thì sử dụng "--start-group" và "--end-group" sẽ không chuyển các tùy chọn đó qua linker - cũng sẽ không gắn cờ lỗi. Nó sẽ chỉ thất bại liên kết với các biểu tượng không xác định nếu bạn có thứ tự thư viện sai.

Bạn cần viết chúng dưới dạng "-Wl, - nhóm bắt đầu", v.v. để nói với GCC chuyển đối số qua trình liên kết.


2

Thứ tự liên kết chắc chắn có vấn đề, ít nhất là trên một số nền tảng. Tôi đã thấy sự cố cho các ứng dụng được liên kết với các thư viện theo thứ tự sai (trong đó sai có nghĩa là A được liên kết trước B nhưng B phụ thuộc vào A).


2

Tôi đã thấy điều này rất nhiều, một số mô-đun của chúng tôi liên kết vượt quá 100 thư viện mã của chúng tôi cộng với libs của hệ thống và bên thứ 3.

Tùy thuộc vào các trình liên kết khác nhau HP / Intel / GCC / SUN / SGI / IBM / etc, bạn có thể nhận được các hàm / biến chưa được giải quyết, v.v., trên một số nền tảng bạn phải liệt kê các thư viện hai lần.

Đối với hầu hết các phần, chúng tôi sử dụng cấu trúc phân cấp của các thư viện, lõi, nền tảng, các lớp trừu tượng khác nhau, nhưng đối với một số hệ thống, bạn vẫn phải chơi theo thứ tự trong lệnh liên kết.

Khi bạn nhấn vào một tài liệu giải pháp để nhà phát triển tiếp theo không phải làm việc lại.

Giảng viên cũ của tôi đã từng nói, " độ gắn kết cao và khớp nối thấp ", ngày nay nó vẫn đúng.

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