Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 39.224 phút đọc

Thứ tự -l và cách trình liên kết duyệt

Sau bài này bạn sẽ làm được

  • Tái hiện lỗi undefined reference do sai thứ tự
  • Giải thích cách trình liên kết duyệt trái sang phải
  • Xử lý phụ thuộc vòng giữa hai thư viện
  • Biết khi nào cần --start-group và --whole-archive

Đây là câu hỏi phỏng vấn kinh điển và cũng là thứ làm mất nhiều giờ nhất của người mới: cùng một lệnh, chỉ đổi chỗ hai từ, một cái chạy một cái không. Lý do rất đơn giản và rất hợp lý.

#Hiện tượng

terminal
# Thư viện đứng TRƯỚC
gcc -lmath -Lbuild -Iinclude main.c -o app.exe
main.c:(.text+0x1a): undefined reference to `dt_hinh_tron'
main.c:(.text+0x47): undefined reference to `cv_hinh_tron'
main.c:(.text+0x6d): undefined reference to `giai_thua'
collect2.exe: error: ld returned 1 exit status
# Thư viện đứng SAU
gcc -Iinclude main.c -Lbuild -lmath -o app.exe && ./app.exe
dt(2)  = 12.56636
cv(2)  = 12.56636
5!     = 120

#Vì sao trình liên kết làm vậy

Trình liên kết đi một lượt duy nhất từ trái sang phải, và không quay lại.
Thuật toán của trình liên kết, viết ra bằng lời
tap_thieu = {}          /* ky hieu can ma chua co */
tap_co    = {}          /* ky hieu da co dinh nghia */

cho moi doi so tren dong lenh, TU TRAI SANG PHAI:

    neu la mot tep .o:
        nap NO VAO KET QUA, khong dieu kien
        them ky hieu no dinh nghia vao tap_co
        them ky hieu no can vao tap_thieu

    neu la mot thu vien .a:
        cho moi tep .o trong thu vien:
            neu no dinh nghia mot ky hieu dang nam trong tap_thieu:
                nap .o do vao ket qua
                cap nhat tap_co va tap_thieu
            nguoc lai:
                BO QUA, va khong bao gio xet lai

cuoi cung: neu tap_thieu con phan tu -> undefined reference

#Phụ thuộc vòng

/* liba.a can mot ham trong libb.a
   libb.a can mot ham trong liba.a */

gcc main.c -la -lb          /* liba lay xong, den libb lay xong,
                               nhung libb lam liba thieu them ->
                               undefined reference */

gcc main.c -la -lb -la      /* GIAI PHAP THO: liet ke lai */

gcc main.c -Wl,--start-group -la -lb -Wl,--end-group   /* GIAI PHAP DUNG */

/* --start-group va --end-group bao trinh lien ket:
   quet cac thu vien trong nhom NHIEU LUOT cho toi khi khong
   lay them duoc gi nua.

   Cham hon mot chut, nhung giai quyet moi phu thuoc vong. */

Phụ thuộc bắc cầu

/* app can libfoo, libfoo can libz */

gcc main.c -lfoo            /* thieu: undefined reference toi ham cua libz */
gcc main.c -lfoo -lz        /* dung: libz dat SAU libfoo */
gcc main.c -lz -lfoo        /* SAI: luc gap -lz chua ai can no */

/* Quy tac tong quat: sap xep theo THU TU PHU THUOC GIAM DAN.
   Thu gan chuong trinh nhat dung truoc, thu nen tang nhat dung sau.

   Do la ly do -lm gan nhu luon la co cuoi cung:
     gcc main.c -lmylib -lpng -lz -lm
*/

#Khi cần cả thư viện

gcc main.c -Wl,--whole-archive -lplugin -Wl,--no-whole-archive -lc

/* --whole-archive: nap MOI tep .o trong thu vien, ke ca cai
   khong ai can toi.

   Nho dat --no-whole-archive ngay sau, neu khong thi moi thu vien
   phia sau, ke ca libc, cung bi nap toan bo. */
Mẫu tự đăng ký, và vì sao nó biến mất
/* test_sinhvien.c, mot tep trong thu vien kiem thu */

#include "khung_thu.h"

static void thu_tao(void) { ... }

__attribute__((constructor))
static void dang_ky(void) {
    khung_them("sinhvien.tao", thu_tao);
}

/* KHONG ham nao ben ngoai goi vao tep .o nay.
   Nen khi lien ket "gcc main.c -ltest", trinh lien ket thay
   test_sinhvien.o khong dinh nghia ky hieu nao dang thieu,
   va BO QUA no.

   Ket qua: bo kiem thu chay ra "0 kiem tra" ma khong bao loi gi.

   Ba cach chua: */

/* 1. --whole-archive */
gcc main.c -Wl,--whole-archive -ltest -Wl,--no-whole-archive

/* 2. Lien ket thang tep .o, khong qua thu vien */
gcc main.c test_*.o

/* 3. Bo tu dang ky, goi tuong minh trong main. Cach nay do "ao thuat"
   nhat va de go loi nhat, nen thuong la lua chon dung. */

#Quy tắc sắp thứ tự

Thư viện đứng trước
gcc -lm -lpng -lz main.c -o app

gcc -L. -lmylib main.c foo.o -o app

gcc $(LDFLAGS) main.c -o app        # LDFLAGS chua -l..., dat truoc
Thư viện đứng sau, xếp theo phụ thuộc giảm dần
gcc main.c -lpng -lz -lm -o app

gcc main.c foo.o -L. -lmylib -o app

gcc main.c -o app $(LDLIBS)         # LDLIBS o cuoi

# Quy uoc cua Make:
#   CFLAGS   co cho trinh bien dich, dat truoc
#   LDFLAGS  duong dan tim thu vien (-L), dat truoc
#   LDLIBS   danh sach thu vien (-l), dat SAU CUNG
#
# $(CC) $(CFLAGS) $(LDFLAGS) main.c -o app $(LDLIBS)
Tình huốngCách xử lý
undefined reference tới hàm bạn biết là có trong thư việnKiểm tra thứ tự -l trước tiên
libA cần libBĐặt -lA trước -lB
libA cần libB và libB cần libA--start-group ... --end-group, và ghi vào việc cần làm
Mã tự đăng ký bị biến mất--whole-archive, hoặc gọi tường minh
Không rõ trình liên kết mở tệp nàogcc -Wl,-t để in ra từng tệp
Không rõ ký hiệu đến từ đâugcc -Wl,--trace-symbol=ten_ham
terminal
# Hai cờ chẩn đoán đáng nhớ
gcc main.c -Lbuild -lmath -Wl,--trace-symbol=dt_hinh_tron -o app
build/libmath.a(hinhhoc.o): definition of dt_hinh_tron
/tmp/ccXXXX.o: reference to dt_hinh_tron

Tự làm thử

  1. Tái hiện lỗi bằng cách đặt -lmath trước main.c.
  2. Chứng minh bằng nm rằng thư viện thật sự có hàm đó.
  3. Tạo hai thư viện phụ thuộc vòng và sửa bằng --start-group.
  4. Viết một tệp có __attribute__((constructor)), đóng vào thư viện, và xác nhận nó bị bỏ qua.
  5. Sửa trường hợp trên bằng --whole-archive.
  6. Dùng -Wl,-t để xem trình liên kết mở những tệp nào.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • Trình liên kết đi một lượt duy nhất từ trái sang phải và không quay lại.
  • Tệp .o luôn được nạp; tệp .a chỉ đóng góp những .o đang được cần tại thời điểm gặp nó.
  • Sắp thư viện theo thứ tự phụ thuộc giảm dần, và -lm gần như luôn đứng cuối.
  • Phụ thuộc vòng chữa được bằng --start-group, nhưng nó là dấu hiệu hai thư viện nên gộp làm một.
  • Mã tự đăng ký trong thư viện tĩnh bị bỏ qua nếu không ai gọi tới, nên cần --whole-archive hoặc gọi tường minh.