Biên dịch chương trình C bằng Make: Những điều tôi ước mình đã biết

Table of Contents
Lần đầu tiên tôi thử biên dịch một cái gì đó từ mã nguồn, tôi đã dành ba giờ vào tối thứ Sáu để Google các thông báo lỗi. Ba giờ đồng hồ. Cho một chương trình đáng lẽ chỉ mất năm phút.
Tôi đến từ thế giới Python và JavaScript, nơi pip install và npm install chỉ cần chạy là được. Giải quyết dependency, lockfile, thông báo lỗi thân thiện — tất cả đều được xử lý. Sau đó tôi cần một thư viện C không có trên PyPI, và đột nhiên tôi phải đọc output của linker như thể đó là chữ tượng hình.
Nó không cần phải đau đớn đến thế. Đây là những gì thực sự hiệu quả.
Mô hình configure && make && make install
Hầu hết các dự án C bạn sẽ gặp đều tuân theo cùng một nghi thức ba bước:
./configure
make -j$(nproc)
sudo make install
Script configure sẽ dò xét hệ thống của bạn, tìm ra những thư viện bạn có, và viết một Makefile dành riêng cho máy của bạn. Sau đó make thực hiện việc biên dịch thực tế. Cuối cùng, make install sao chép binary đã hoàn thành đến một nơi mà shell của bạn có thể tìm thấy.
Không phải mọi dự án đều sử dụng autotools. Một số chỉ có Makefile viết tay hoặc sử dụng CMake. Nhưng đủ nhiều dự án sử dụng mô hình configure/make để bạn sẽ gặp nó liên tục. Khi nghi ngờ, hãy kiểm tra file README hoặc INSTALL — chúng thường sẽ cho bạn biết chính xác những gì cần chạy.
Không chắc một dự án sử dụng hệ thống build nào? Hãy tìm configure, CMakeLists.txt, Makefile, hoặc meson.build trong thư mục gốc. Đó là manh mối đầu tiên của bạn.
Phần Không Ai Cảnh Báo Bạn: Dependencies
Đây là nơi mọi thứ thường đổ vỡ. Bạn chạy ./configure và nó trả về một cái gì đó như:
checking for libqpdf... no
configure: error: Package requirements (libqpdf >= 8.0) were not met
Lỗi này đã kết thúc nhiều phiên biên dịch hơn tôi có thể đếm. Chương trình cần một thư viện, và bạn không cài đặt nó.
Trên Ubuntu hoặc Debian, bạn sẽ muốn phiên bản -dev của gói. Gói thông thường cung cấp cho bạn thư viện runtime, nhưng bạn cần các header để thực sự biên dịch bất cứ thứ gì:
sudo apt install -y libqpdf-dev
Trên macOS với Homebrew, mọi thứ đơn giản hơn. Homebrew không chia gói thành các biến thể dev và runtime:
brew install qpdf
Một mẹo nữa cho macOS: nếu bạn đã cài đặt một thư viện bằng Homebrew và configure vẫn không tìm thấy nó, các đường dẫn có thể chưa được thiết lập đúng cách. Tôi sẽ đề cập đến điều này trong phần tiếp theo.
Vấn đề đường dẫn Homebrew trên macOS
Tôi dùng MacBook để làm việc. Homebrew rất tuyệt, nhưng nó cài đặt mọi thứ ở các vị trí không chuẩn. Trên Apple Silicon, các thư viện đi đến /opt/homebrew/. Trên Intel Mac, chúng đi đến /usr/local/.
Trình biên dịch không tự động biết tìm ở đó. Vì vậy, ngay cả khi bạn đã cài đặt một cái gì đó bằng Homebrew, script configure của bạn vẫn có thể phàn nàn rằng nó không tìm thấy.
Khi bạn thấy ld: library not found, bạn cần cho trình biên dịch biết nơi để tìm:
CPPFLAGS="-I/opt/homebrew/include" \
LDFLAGS="-L/opt/homebrew/lib" \
./configure
CPPFLAGS thêm các thư mục include. LDFLAGS thêm các thư mục thư viện. Nếu không có những thứ này, configure sẽ không tìm thấy các header hoặc thư viện nằm ngoài các đường dẫn hệ thống tiêu chuẩn.
Đôi khi bạn cần làm điều này cho make trực tiếp, khi một dự án không sử dụng configure:
CPPFLAGS="-I/opt/homebrew/include" LDLIBS="-L/opt/homebrew/lib -liconv" make
Dự án khác, cú pháp hơi khác. Tuy nhiên, nguyên tắc vẫn giống nhau: chỉ cho trình biên dịch biết nơi Homebrew thực sự đặt các tệp.
Làm cho nó nhanh: Cờ -j
Cái này tôi đã mất một thời gian đáng xấu hổ để khám phá.
Theo mặc định, make biên dịch với một job — một lõi CPU, hoạt động hết công suất, trong khi phần còn lại của máy bạn ngồi không. Trên một chiếc laptop 8 lõi hiện đại, đó là sử dụng 12.5% tiềm năng của bạn.
Bạn làm gì thay vào đó?
make -j$(nproc) # Linux
make -j$(sysctl -n hw.ncpu) # macOS
Cờ -j chạy nhiều công việc biên dịch song song. $(nproc) chỉ hỏi Linux có bao nhiêu lõi CPU. Trên macOS, sysctl -n hw.ncpu làm điều tương tự.
Đối với các dự án nhỏ, bạn sẽ không nhận thấy nhiều khác biệt. Đối với bất cứ thứ gì đáng kể — như biên dịch một cái gì đó với hàng ngàn tệp nguồn — điều này thay đổi mọi thứ. Cái gì mất năm phút có thể mất bốn mươi lăm giây. Đối với các dự án thực sự lớn, nó có thể là sự khác biệt giữa một giờ giải lao uống cà phê và một bữa trưa.
Bạn thực sự có bao nhiêu lõi? Hầu hết các laptop hiện đại có 4-8 lõi. Chạy nproc hoặc sysctl -n hw.ncpu và xem bạn nhận được gì. Sau đó sử dụng số đó với -j.
Khi nó vẫn không hoạt động
Đôi khi bạn đã làm mọi thứ đúng nhưng mọi thứ vẫn thất bại. Dưới đây là những lỗi phổ biến nhất mà tôi đã gặp:
Không tìm thấy ký hiệu khi liên kết — Thường có nghĩa là phiên bản thư viện không khớp. Chương trình mong đợi một phiên bản mới hơn của một cái gì đó so với những gì bạn có, hoặc có thể là một phiên bản cũ hơn với API khác. Kiểm tra tài liệu của dự án để biết phiên bản chính xác mà nó cần.
Quyền bị từ chối trên configure — Đôi khi script configure chưa có quyền thực thi. Chỉ cần chạy chmod +x configure và thử lại.
Không muốn sử dụng sudo? — Điều đó hợp lý. Cài đặt toàn hệ thống yêu cầu quyền root, nhưng bạn có thể cài đặt vào thư mục home của mình thay thế:
./configure --prefix=$HOME/.local
make -j$(nproc)
make install
Bây giờ chương trình cài đặt vào ~/.local/bin/ thay vì /usr/local/bin/. Bạn sẽ muốn thêm ~/.local/bin vào PATH của mình nếu nó chưa có.
Những điều tôi ước mình biết sớm hơn
Nhìn lại phiên gỡ lỗi ba giờ vào tối thứ Sáu đó, tất cả các vấn đề đều có thể giải quyết được. Tôi chỉ không biết những gì mình không biết.
Một vài điều lẽ ra đã giúp tôi tiết kiệm rất nhiều thời gian:
Hậu tố gói -dev rất quan trọng trên Ubuntu. Nếu không có nó, bạn có thư viện nhưng không có các header. Configure không thể build dựa trên một thư viện mà nó không thể nhìn thấy.
Homebrew đặt mọi thứ ở các vị trí không chuẩn. Thiết lập CPPFLAGS và LDFLAGS không phải là tùy chọn trên macOS — đó là cách bạn cho hệ thống build biết nơi tìm thấy những gì bạn đã cài đặt.
Cờ -j không phải là một tối ưu hóa. Nó là một yêu cầu. Một lõi biên dịch chậm. Tất cả các lõi của bạn biên dịch nhanh.
Và cuối cùng, khi configure thất bại, thông báo lỗi thường cho bạn biết chính xác những gì còn thiếu. Đọc nó trước khi bạn Google. "Package requirements (libqpdf >= 8.0) were not met" không phải là khó hiểu — đó là một mục trong danh sách kiểm tra. Cài đặt gói đó, và tiếp tục.
Biên dịch từ mã nguồn không đáng sợ như vẻ ngoài của nó. Các hệ thống build cũ hơn và thông báo lỗi ít thân thiện hơn, nhưng quá trình này thực sự khá dễ đoán một khi bạn đã thực hiện vài lần. Hãy thử lại. Lần biên dịch thành công đầu tiên từ mã nguồn sẽ mang lại cảm giác rất tốt.
Bạn muốn tôi hướng dẫn biên dịch một cái gì đó cụ thể? Hầu hết các lần biên dịch gần đây của tôi đều gặp lỗi vì tôi đã bỏ qua một trong các bước này. Rất vui được giúp bạn gỡ lỗi bất cứ điều gì bạn đang làm.
Bạn Cũng Có Thể Thích
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

eBPF trong Môi Trường Production: Quan Sát Hệ Thống Linux Với Overhead Cực Thấp, Tracing và Profiling Nhân Hệ Điều Hành
Triển khai quan sát nhân Linux (kernel observability) với overhead gần như bằng không bằng eBPF. Đo độ trễ system call, theo dõi cấp phát bộ nhớ và giám sát socket mạng mà không cần sidecar.
Read more
Bảo mật Ubuntu Server: Danh sách kiểm tra tăng cường bảo mật Linux thực tế
Hướng dẫn tăng cường bảo mật máy chủ Ubuntu trong môi trường sản xuất: thực thi khóa SSH, tường lửa UFW, ngăn chặn xâm nhập Fail2ban, nâng cấp tự động và ghi nhật ký auditd.
Read more
VPN và Proxy: Khác biệt, Fingerprinting, Rò rỉ và Kiểm tra quyền riêng tư
Tôi đã dành nhiều tháng để kiểm tra VPN và proxy trên Linux, phát hiện rò rỉ IPv6 trong thiết lập của riêng mình, làm hỏng dấu vân tay trình duyệt với quá nhiều tiện ích mở rộng và tìm hiểu những gì thực sự hiệu quả cho quyền riêng tư.
Read more