Bài 20.426 phút đọc
Biên dịch, liên kết và build lại từng phần
Sau bài này bạn sẽ làm được
- Phân biệt lỗi biên dịch với lỗi liên kết qua thông báo
- Biên dịch từng tệp thành .o rồi liên kết lại
- Giải thích vì sao build lại từng phần nhanh hơn build lại tất cả
- Viết một Makefile tối thiểu chạy được
Cho tới giờ bạn chỉ chạy gcc a.c b.c -o app. Nó vẫn đúng. Nhưng với dự án ba mươi tệp, cách đó dịch lại toàn bộ mỗi lần bạn sửa một dấu chấm phẩy, và đó là khác biệt giữa nửa giây và nửa phút cho mỗi lần thử.
#Một lệnh hay hai bước
/* Cach 1: mot lenh */
gcc -Wall -Wextra -Iinclude src/*.c -o app
/* Cach 2: bien dich rieng roi lien ket */
gcc -Wall -Wextra -Iinclude -c src/main.c -o build/main.o
gcc -Wall -Wextra -Iinclude -c src/sinhvien.c -o build/sinhvien.o
gcc -Wall -Wextra -Iinclude -c src/quanly.c -o build/quanly.o
gcc build/main.o build/sinhvien.o build/quanly.o -o app| Một lệnh | Hai bước | |
|---|---|---|
| Số lệnh phải gõ | 1 | n cộng 1 |
| Sửa một tệp thì dịch lại mấy tệp | Tất cả | Đúng một |
| Dịch song song nhiều tệp | Không | Được, mỗi tệp một tiến trình |
| Cần công cụ hỗ trợ | Không | Nên có Makefile |
| Hợp cho | Dự án dưới 5 tệp | Mọi dự án lớn hơn |
#Đơn vị dịch
Đơn vị dịch
Một tệp
.c sau khi preprocessor đã dán hết mọi header vào. Đây là thứ trình biên dịch thật sự nhìn thấy, và mỗi đơn vị dịch cho ra đúng một tệp .o.main.c + moi header no include -> mot don vi dich -> main.o
quanly.c + moi header no include -> mot don vi dich -> quanly.o
/* Hai don vi dich KHONG BIET GI VE NHAU.
Trinh bien dich dich quanly.c ma khong he nhin thay main.c.
Do la ly do:
- quanly.c phai include header de biet kieu SinhVien
- ham o main.c goi duoc tu quanly.c chi khi co nguyen mau
- loi khong khop chi lo ra o buoc lien ket */#Build lại từng phần
Lần đầu: dịch mọi tệp
terminalmakegcc -Wall -Wextra -Iinclude -c src/main.c -o build/main.o gcc -Wall -Wextra -Iinclude -c src/sinhvien.c -o build/sinhvien.o gcc -Wall -Wextra -Iinclude -c src/quanly.c -o build/quanly.o gcc -Wall -Wextra -Iinclude -c src/luutru.c -o build/luutru.o gcc -Wall -Wextra -Iinclude -c src/ui.c -o build/ui.o gcc build/main.o build/sinhvien.o build/quanly.o build/luutru.o build/ui.o -o build/app
Sửa một tệp .c: dịch lại một tệp
terminaltouch src/quanly.c && makegcc -Wall -Wextra -Iinclude -c src/quanly.c -o build/quanly.o gcc build/main.o build/sinhvien.o build/quanly.o build/luutru.o build/ui.o -o build/app
Sửa một header: dịch lại mọi tệp include nó
terminaltouch include/sinhvien.h && makegcc -Wall -Wextra -Iinclude -c src/main.c -o build/main.o gcc -Wall -Wextra -Iinclude -c src/sinhvien.c -o build/sinhvien.o gcc -Wall -Wextra -Iinclude -c src/quanly.c -o build/quanly.o gcc -Wall -Wextra -Iinclude -c src/luutru.c -o build/luutru.o gcc build/... -o build/app
Không sửa gì: không làm gì
terminalmakemake: 'build/app' is up to date.
#Makefile tối thiểu
Makefile
CC = gcc
CFLAGS = -std=c11 -Wall -Wextra -Wpedantic -g -Iinclude
LDFLAGS =
NGUON = $(wildcard src/*.c)
DOI = $(NGUON:src/%.c=build/%.o)
DICH = build/ql-sinhvien
all: $(DICH)
$(DICH): $(DOI)
$(CC) $(DOI) $(LDFLAGS) -o $@
build/%.o: src/%.c | build
$(CC) $(CFLAGS) -c $< -o $@
build:
mkdir -p build
clean:
rm -rf build
.PHONY: all clean| Ký hiệu | Nghĩa | Trong ví dụ trên |
|---|---|---|
| $@ | Tên mục tiêu | build/main.o |
| $< | Phụ thuộc đầu tiên | src/main.c |
| $^ | Mọi phụ thuộc | Toàn bộ danh sách .o |
| % | Ký tự đại diện trong quy tắc mẫu | Khớp main, quanly, ... |
| | | Phụ thuộc chỉ theo thứ tự | Thư mục build, không tính thời gian sửa |
#Cờ nên dùng
CFLAGS = -std=c11 -Wall -Wextra -Wpedantic -g -Iinclude| Cờ | Bắt được gì |
|---|---|
| -std=c11 | Từ chối phần mở rộng của trình biên dịch, mã khả chuyển hơn |
| -Wall | Khoảng bốn mươi cảnh báo cơ bản, không hề là tất cả |
| -Wextra | Thêm hai chục cảnh báo nữa, đáng bật |
| -Wpedantic | Cảnh báo mọi chỗ đi lệch khỏi chuẩn |
| -g | Thông tin gỡ lỗi, không ảnh hưởng tốc độ chạy |
| -Iinclude | Thêm thư mục tìm header |
Quy trình build hoàn chỉnh cho dự án Phần 9
terminal
make clean && make
gcc -std=c11 -Wall -Wextra -Wpedantic -g -Iinclude -c src/sinhvien.c -o build/sinhvien.o ... (bon tep nua) ... gcc build/*.o -o build/ql-sinhvien
# Kiểm không cảnh báo với bộ cờ nghiêm ngặt
make clean && make CFLAGS='-std=c11 -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Werror -Iinclude'
(khong co dong nao)
# Kiểm bộ nhớ
make asan && ./build/ql-sinhvien du-lieu.svdb < kich-ban.txt
(khong co dong nao)
make clean && make && valgrind --leak-check=full ./build/ql-sinhvien du-lieu.svdb < kich-ban.txt
==12345== All heap blocks were freed -- no leaks are possible ==12345== ERROR SUMMARY: 0 errors from 0 contexts
Tự làm thử
- Biên dịch dự án của bạn bằng một lệnh, rồi bằng hai bước, và so sánh các tệp sinh ra.
- Sửa một tệp
.crồi chạymake, đếm xem có mấy lệnhgccchạy. - Sửa một header rồi chạy
make, so sánh với lần trên. - Viết Makefile ở trên, cố ý dùng dấu cách thay tab, rồi đọc thông báo lỗi.
- Thêm mục tiêu
asanvà xác nhận cờ-fsanitizecó ở cả hai bước. - Đo thời gian
make -j1vàmake -j8trên dự án của bạn.
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
- Cờ
-cdừng lại trước bước liên kết và sinh ra tệp.o. Đó là toàn bộ khác biệt giữa một lệnh và hai bước. - Mỗi tệp
.ccùng mọi header của nó là một đơn vị dịch, và các đơn vị dịch không biết gì về nhau. - Sửa một tệp
.cthì chỉ dịch lại tệp đó, còn sửa header thì phải dịch lại mọi tệp include nó. - Makefile không theo dõi header sẽ để lại tệp
.ocũ và sinh ra lỗi rất khó tìm. Trong lúc chưa có, hãymake clean. - Cờ
-fsanitizephải có ở cả bước dịch lẫn bước liên kết, còn-lmchỉ cần ở bước liên kết.