Bài 2 · Nâng cao · 22 phút· Cập nhật 11/06/2026
Thư viện & liên kết
Biên soạn bởi Nguyễn Anh Tuấn
Thư viện tĩnh (.a) vs thư viện động (.so) trong C: symbol, linker giải quyết ký hiệu, include guard - hiểu lỗi undefined reference & multiple definition.
Mỗi file .c là một đơn vị dịch (translation unit), biên dịch độc lập thành một .o. Để dùng hàm ở file khác, bạn cần khai báo (prototype) nó - thường đặt trong header:
ba-file.c
/* util.h - KHAI BAO: "co ham nay, kieu the nay" */
int tinh(int x);
/* util.c - DINH NGHIA: than ham */
int tinh(int x) { return x * x; }
/* main.c - DUNG: can khai bao qua #include */
#include "util.h"
int main(void) { return tinh(5); } - ▸Khai báo: “có ký hiệu này, kiểu thế này” (prototype) - được phép lặp.
- ▸Định nghĩa: thân hàm hoặc storage của biến - chỉ MỘT lần (ODR).
- ▸Mỗi .o ghi rõ: ký hiệu nó ĐỊNH NGHĨA và ký hiệu nó còn THIẾU.
Trình liên kết (linker) ghép các .o và thư viện, rồi tìm định nghĩa cho mọi ký hiệu còn thiếu. Bật/tắt các thành phần để xem ký hiệu được giải quyết - hay thất bại với undefined reference:
Thử bỏ util.o → mất tinh; bỏ libm → mất sqrt. Đây chính là lỗi “undefined reference” ở Bài 1, giờ bạn thấy trình liên kết ghép ký hiệu thế nào.
- ▸Mỗi ký hiệu cần (tinh, printf, sqrt) phải có ĐÚNG MỘT nơi cung cấp.
- ▸Không nơi nào cung cấp → undefined reference (lỗi liên kết).
- ▸Hai nơi cùng định nghĩa → multiple definition (cũng lỗi liên kết).
Header chia sẻ khai báo giữa các file. Nhưng nếu một header bị dán hai lần (qua include lồng nhau), bạn gặp lỗi định nghĩa lặp - nên luôn bọc include guard:
util.h
#ifndef UTIL_H /* neu chua dinh nghia UTIL_H */
#define UTIL_H /* thi dinh nghia no, va... */
int tinh(int x); /* ...dan noi dung nay (chi 1 lan) */
extern int dem; /* bien toan cuc DUNG CHUNG: khai bao */
#endif /* het guard */ - ▸#ifndef/#define/#endif (hoặc #pragma once) → mỗi header chỉ dán một lần.
- ▸static ở phạm vi file = internal linkage: riêng tư, linker không thấy.
- ▸Biến toàn cục dùng chung: extern trong header, định nghĩa MỘT lần trong .c.
Thư viện tĩnh là một “kho” các .o gộp bằng ar. Khi liên kết, mã cần thiết được copy thẳng vào chương trình:
tao-dung-thu-vien-tinh
gcc -std=c17 -c util.c -o util.o
ar rcs libutil.a util.o # dong goi .o thanh thu vien tinh
gcc -std=c17 main.c -L. -lutil -o app # -L. tim trong thu muc nay; -lutil = libutil.a
./app # chay doc lap, khong can libutil.a nua Đặc điểm liên kết tĩnh
Thư viện động được liên kết lúc chạy, không copy vào exe. Nhiều chương trình dùng chung một bản trong RAM:
tao-dung-thu-vien-dong
gcc -std=c17 -fPIC -c util.c -o util.o # -fPIC: ma doc lap vi tri
gcc -shared util.o -o libutil.so # tao thu vien dong
gcc -std=c17 main.c -L. -lutil -o app # link DONG
./app # can libutil.so luc CHAY
ldd app # xem cac thu vien dong app can - ▸Liên kết lúc chạy: exe nhỏ, nhiều app dùng chung một .so trong bộ nhớ.
- ▸Vá lỗi thư viện: thay .so là xong, không cần build lại app.
- ▸Đổi lại: .so PHẢI có mặt lúc chạy (ldd, LD_LIBRARY_PATH, rpath).
Tách dự án thành nhiều file, tự tạo thư viện tĩnh (.a) và động (.so), và tái tạo 'undefined reference' bằng cách bỏ một file khi liên kết.
$ unzip 02-thu-vien-lien-ket.zip && cd 02-thu-vien-lien-ket # tai zip bang nut o tren $ gcc -c util.c -o util.o $ ar rcs libutil.a util.o # thu vien tinh $ gcc main.c -L. -lutil -o app_tinh # link tinh $ gcc -fPIC -shared util.c -o libutil.so # thu vien dong $ gcc -c main.c -o main.o $ gcc main.o -o broken # tai tao undefined reference
Xem từng file (4) ↡
Câu hỏi thường gặp
Khai báo (declaration) nói “có thứ này tồn tại, kiểu thế này” - vd prototype int tinh(int);. Định nghĩa (definition) cung cấp thân hàm hoặc storage - int tinh(int x) { … }. Một ký hiệu được khai báo nhiều lần, nhưng chỉ định nghĩa MỘT lần (Quy tắc Một Định Nghĩa - ODR).
Khi nhiều file (hoặc header lồng nhau) cùng #include một header, nội dung bị dán nhiều lần → định nghĩa lặp, lỗi biên dịch. Include guard (#ifndef X / #define X / #endif) hoặc #pragma once đảm bảo mỗi header chỉ được dán MỘT lần cho mỗi đơn vị dịch.
Đó là internal linkage: hàm/biến chỉ nhìn thấy trong file .c đó, KHÔNG lộ ra cho trình liên kết. Dùng để đóng gói (hàm phụ trợ riêng tư) và tránh đụng tên với file khác. Ngược lại, mặc định hàm/biến toàn cục có external linkage (lộ ra cho linker).
Thư viện TĨNH (.a): tập hợp các .o, liên kết lúc BUILD - mã được COPY vào chương trình. Exe chạy độc lập nhưng to; vá lỗi thư viện phải build lại. Thư viện ĐỘNG (.so/.dll/.dylib): liên kết lúc CHẠY - nhiều chương trình DÙNG CHUNG một bản trong RAM; exe nhỏ, vá thư viện không cần build lại app - nhưng phải có .so lúc chạy.
Với liên kết tĩnh: CÓ. Trình liên kết quét trái → phải; một thư viện phải đứng SAU file dùng nó: gcc main.c -lutil (đúng), không phải gcc -lutil main.c (có thể “undefined reference” vì lúc gặp lib chưa biết cần tinh).
Cùng một ký hiệu được ĐỊNH NGHĨA ở hai nơi - thường do định nghĩa hàm/biến trong header rồi include vào nhiều .c. Sửa: header chỉ KHAI BÁO; định nghĩa nằm trong đúng một .c. Với biến toàn cục: extern int g; trong header, int g; định nghĩa một lần trong .c.
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Theo Quy tắc Một Định Nghĩa (ODR), một ký hiệu được phép xuất hiện thế nào?
- 1
Bỏ util.o
Trong công cụ ở Bước 2, bỏ chọn
util.o. Ký hiệu nào thành undefined? Lỗi này nói gì?Hoàn thành khi:
tinh→ "undefined reference to tinh": linker không tìm thấy định nghĩa củatinh(nó nằm trongutil.o). - 2
Bỏ libm
Bỏ chọn
libm(-lm). Ký hiệu nào mất? Cách sửa trên dòng lệnh?Hoàn thành khi:
sqrt→ thêm-lmvào lệnh liên kết để nối thư viện math. - 3
Header chứa gì
util.hnên chứa khai báo hay định nghĩa củatinh? Vì sao?Hoàn thành khi: Khai báo (prototype). Nếu định nghĩa trong header, nhiều
.cinclude sẽ gây "multiple definition". Định nghĩa đặt trongutil.c. - 4
Tạo thư viện tĩnh
ar rcs libfoo.a a.o b.olàm gì? Rồi link nó vàomainthế nào?Hoàn thành khi: Đóng gói
a.o,b.othành thư viện tĩnhlibfoo.a. Link:gcc -std=c17 main.c -L. -lfoo -o app. - 5
Lỗi runtime của link động
Chương trình link động báo "
error while loading shared libraries: cannot open shared object file". Vì sao và sửa sao?Hoàn thành khi: Thiếu
.solúc CHẠY (linker động không tìm thấy). Đặt.sovào đường chuẩn, hoặc setLD_LIBRARY_PATH, hoặc nhúngrpathkhi build. - 6
Tĩnh hay động?
Khi nào nên link tĩnh, khi nào động? Nêu một lý do mỗi bên.
Hoàn thành khi: Tĩnh: triển khai đơn giản, exe độc lập (không phụ thuộc
.so). Động: nhiều chương trình chung lib → tiết kiệm RAM/đĩa, vá lib không cần build lại app.