Mục lục
- 1. Môi trường dịch
- 1.1. Tiền xử lý
- 1.2. Biên dịch
- 1.3. Tạo mã máy
- 1.4. Liên kết
- 2. Môi trường thực thi
1. Môi trường dịch
Môi trường dịch bao gồm hai giai đoạn chính là
biên dịch và
liên kết. Trong đó, quá trình biên dịch lại chia thành ba bước nhỏ:
tiền xử lý,
biên dịch và
tạo mã máy.
Các tập tin .c được biên dịch độc lập để tạo ra các tệp mục tiêu (.obj trên Windows, .o trên Linux). Các tệp mục tiêu này cùng với thư viện liên kết sẽ được đưa vào quá trình liên kết để tạo ra chương trình thực thi cuối cùng. Thư viện liên kết bao gồm thư viện hệ thống (hỗ trợ cơ bản cho chương trình chạy) và thư viện bên thứ ba.
1.1. Tiền xử lý
Giai đoạn tiền xử lý sẽ chuyển đổi tệp nguồn và các tệp tiêu đề thành tệp .i. Để xem tệp .i sau khi tiền xử lý bằng GCC, sử dụng lệnh:
gcc -E test.c -o test.i
Giai đoạn này xử lý các chỉ thị tiền xử lý bắt đầu bằng ký tự # như:
- Xóa và mở rộng tất cả định nghĩa #define
- Xử lý các chỉ thị điều kiện #if, #ifdef, #elif, #else, #endif
- Xử lý #include bằng cách chèn nội dung tệp tiêu đề vào vị trí tương ứng (quá trình đệ quy)
- Xóa tất cả chú thích
- Thêm thông tin dòng và tên tệp để hỗ trợ gỡ lỗi
- Giữ nguyên các chỉ thị #pragma để compiler sử dụng sau này
1.2. Biên dịch
Quá trình biên dịch thực hiện phân tích từ vựng, cú pháp, ngữ nghĩa và tối ưu hóa để tạo ra tệp mã hợp ngữ (.s). Lệnh biên dịch:
gcc -S test.i -o test.s
Ví dụ với đoạn mã:
danh_sach[vi_tri] = (vi_tri+4)*(2+6);
- Phân tích từ vựng: Tách chuỗi thành các token như từ khóa, biến, hằng số...
- Phân tích cú pháp: Xây dựng cây cú pháp biểu diễn cấu trúc chương trình
- Phân tích ngữ nghĩa: Kiểm tra tính hợp lệ về kiểu dữ liệu và báo lỗi cú pháp
1.3. Tạo mã máy
Hợp ngữ được chuyển đổi thành mã máy bằng công cụ hợp ngữ. Mỗi lệnh hợp ngữ tương ứng với một chỉ lệnh máy. Lệnh thực hiện:
gcc -c test.s -o test.o
1.4. Liên kết
Giai đoạn liên kết kết hợp các tệp mục tiêu để tạo chương trình thực thi. Các bước chính:
- Phân bổ địa chỉ và không gian bộ nhớ
- Xác định tên gọi (symbol resolution)
- Định vị lại (relocation)
Ví dụ với hai tệp:
// tinh_tong.c
#include <stdio.h>
extern int Cong(int x, int y); // Khai báo hàm bên ngoài
extern int gia_tri; // Khai báo biến toàn cục
int main() {
int so1 = 10;
int so2 = 20;
int tong = Cong(so1, so2);
printf("%d\n", tong);
return 0;
}
// ham_cong.c
int gia_tri = 2022;
int Cong(int x, int y) {
return x + y;
}
Trong quá trình biên dịch, từng tệp tạo ra tệp mục tiêu (tinh_tong.o và ham_cong.o). Khi liên kết, các tham chiếu đến hàm Cong và biến gia_tri trong tinh_tong.o sẽ được thay thế bằng địa chỉ thực tế từ ham_cong.o. Quá trình này gọi là
định vị lại.
2. Môi trường thực thi
- Chương trình cần được tải vào bộ nhớ. Hệ điều hành thực hiện việc này trong môi trường có hệ điều hành. Trong môi trường độc lập, cần sắp xếp thủ công hoặc đặt mã vào RAM chỉ đọc
- Bắt đầu thực thi bằng cách gọi hàm main()
- Sử dụng ngăn xếp (stack) lưu trữ biến cục bộ và địa chỉ trả về
- Biến tĩnh (static) được lưu trữ trong vùng nhớ tĩnh, giữ giá trị suốt quá trình chạy
- Kết thúc chương trình qua việc thoát khỏi hàm main() hoặc lỗi bất ngờ