Lập trình C/Cơ bản về biên dịch
Giống như mọi ngôn ngữ lập trình khác (trừ ngôn ngữ máy), bộ vi xử lí không thể hiểu được trực tiếp ngôn ngữ C. Mục đích của nó là cung cấp một cách trực quan cho con người để mô tả các chỉ lệnh có thể dễ dàng chuyển đổi thành mã máy mà bộ vi xử lí có thể hiểu được. Trình biên dịch (compiler) là chương trình nhận vào mã nguồn ban đầu và chuyển nó thành mã máy.
Với những ai mới lập trình, điều này có vẻ khá đơn giản. Một trình dịch đơn giản sẽ đọc tất cả các tệp nguồn, dịch mọi thứ thành mã máy và tạo ra tệp thực thi được. Nhưng cách này có những vấn đề nghiêm trọng. Trước hết, với những dự án lớn, máy tính có thể không có đủ bộ nhớ để có thể dịch mọi tệp cùng một lúc. Thứ hai, nếu bạn sửa đổi một tệp mã nguồn duy nhất, bạn sẽ phải biên dịch lại toàn bộ ứng dụng.
Để xử lí vấn đề này, trình dịch (compiler) chia công việc của nó thành nhiều bước; đối với mỗi tệp nguồn (mỗi tệp .c
), trình biên dịch sẽ đọc tệp cũng như các tệp khác mà nó tham chiếu tới (được thể hiện qua chỉ thị #include
trong tệp nguồn), Và dịch nó thành mã máy. Kết quả của quá trình này là các "tệp đối tượng" (.o
). Đến đây một trình dịch "theo đúng nghĩa của nó" đã hoàn tất công việc của mình.
Sau khi mọi tệp đối tượng cần thiết đã được tạo, một trình nối (linker) thu thập tất cả các tệp đối tượng và tạo ra chương trình thực. Bằng cách này, nếu bạn sửa đổi một tệp nguồn, chỉ có tệp đó cần phải dịch lại và các tệp đối tượng sẽ được nối lại.
Chúng ta sẽ không đi sâu vào chi tiết vấn đề này, nhưng hiểu biết cơ bản về quá trình biên dịch thực sự đem lại lợi ích.
Bộ tiền xử lí
sửaBộ tiền xử lí cung cấp khả năng gộp một tệp tiêu đề vào tệp nguồn, khai triển các macro, biên dịch có điều kiện và điều khiển dòng. Nhiều khi bạn cần phải đưa ra các chỉ thị đăc biệt đối với trình dịch, điều này được thực hiện bằng cách chèn vào mã nguồn các chỉ thị tiền xử lí. Khi bạn bắt đầu dịch mã nguồn, một chương trình đặc biệt gọi là bộ tiền xử lí sẽ quét tệp nguồn và tiến hành các phép thay thế các "từ" trong các câu lệnh theo các quy tắc định trước. Bộ tiền xử lí không phải là một phần của ngôn ngữ C.
Trong ngôn ngữ C, tất cả các chỉ thị tiền xử lí đều bắt đầu bằng kí tự #. Bạn có thể thấy một chỉ thị tiền xử lí trong chương trình Hello world giới thiệu ở Chương trình C đơn giản đầu tiên:
Ví dụ:
#include <stdio.h>
Chỉ thị này làm cho tệp tiêu đề stdio.h được gộp vào chương trình (tức là toàn bộ nội dung của tệp này sẽ được thay vào vị trí tương ứng của chỉ thị trong mã nguồn). Kết quả của giai đoạn tiền xử lí là các xâu văn bản. Bạn có thể hiểu bộ tiền xử lí giống như một trình soạn thảo không có tính tương tác chuyên thực hiện các thao tác tìm kiếm và thay thế để chuẩn bị mã nguồn cho trình dịch.
Kiểm tra cú pháp
sửaBước này đảm bảo tính hợp lệ về mặt cú pháp của chương trình nguồn để bắt đầu dịch sang mã máy. Với hầu hết các trình dịch, bạn sẽ nhận được các thông báo hoăc cảnh báo chỉ ra các vấn đề tiềm tàng của chương trình (chẳng hạn một biểu thức điều kiện luôn luôn đúng, ...). Do đó các lỗi xảy ra trong quá trình dịch thường là các lỗi cú pháp.
Khi lỗi cú pháp được phát hiện, thông thường trình dịch sẽ thông báo tệp và dòng chứa lỗi.
- include <stdio.h>;
int main(void); printf("hello minh\n");
- include;
return 0;
Mã đối tượng
sửaTrình dịch tạo ra mã máy tương đương với mã nguồn trong các tệp đối tượng có thể nối lại với nhau để tạo ra chương trình cuối cùng. Bản thân mã này không có khả năng thực thi, nó trước hết phải được nối.
Một chú ý quan trong là quá trình dịch là một con đường một chiều. Dịch mã nguồn thành mã máy diễn ra dễ dàng, nhưng dịch ngược mã máy thành mã nguồn không như vậy. Các trình dịch ngược cho ngôn ngữ C có tồn tại, nhưng hiếm khi tạo được các mã C hữu dụng.
Nối
sửaQuá trình nối kết hợp các tệp đối tượng riêng rẽ thành một chương trình hoàn thiện, bằng cách tích hợp các thư viện và mã vào một tệp thực thi được hoặc một thư viện (tập các mã có thể được dùng lại ở nhiều phần mềm). Nó được thực hiện bởi trình nối. Trình nối khác với một trình dịch theo nghĩa này nhưng trên thực tế các chương trình được gọi là "trình dịch" luôn bao gồm cả trình nối. Cũng vì thế, khi nói "dịch" một chương trình, ta có thể hiểu là "tạo tệp đối tượng" hoặc "tạo tệp thực thi/thư viện", tuỳ vào ngữ cảnh.
Các lỗi xảy ra trong quá trình nối thường là do thiếu định nghĩa hoặc định nghĩa nhiều lần một đối tượng nào đó.
Tự động hoá
sửaTrong các dự án lớn, nhiều lập trình viên chọn cách dịch tự động để giảm bớt việc phải tương tác và tăng tốc quá trình dịch bằng cách chỉ dịch lại các tệp bị thay đổi.
Hầu hết các IDE có các cơ chế quản lí dự án, làm cho tự động hoá trở nên dễ dàng. Trên các hệ thống giống UNIX, make và Makefiles thường được sử dụng để đạt được mục đích tương tự.