Câu trả lời:
(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;
$ 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 -(
và -)
, 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ư -\(
và -\)
). 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
.
$ 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-needed
cờ 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.so
chống lại d.so
khi tạo cái trước. Bạn sẽ được yêu cầu chỉ định thư viện khi liên kết a
sau đó, nhưng a
thự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 b
cá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ỉ libb
là nó nên. Nhị phân sẽ cần phải được xem lại nếu libb
sau 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 libb
bằng cách sử dụng dlopen
trong 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 wrong
như là tốt.
lorder
+ tsort
là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.
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!
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ì libmysqlclient
nó 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
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).
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 !
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.
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).
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.
gcc
đã thay đổi thành hành vi nghiêm ngặt hơn (tương đối) gần đây.