Cùng tìm hiểu về Compiler #1: Làm quen với compiler
Giới thiệu
Trước khi đi vào một dãy kiến thức dày đặc phía sau thì hãy bắt đầu với vài cái đơn giản về thứ mà bạn sắp đọc trước. Đây là một series mà mình, sẽ cùng các bạn tìm hiểu về compiler, dựa trên chuỗi video "Compiler Design" của Neso Academy trên youtube. Mục đích và mục tiêu của series này là học, không phải dạy, nên nếu mình có mắc 1 hay 1 vài sai lầm nhỏ to gì đấy, thì mong các bạn giúp mình sửa chữa, coi như là cùng nhau học.
1. Dẫn nhập
Nếu bạn là một người lập trình, cho dù hoàn toàn chưa dùng đến những ngôn ngữ "biên dịch", chắc hẳn cũng ít nhất là một lần từng nghe đến từ "Compiler". Vậy cụ thể, nó là gì?
Máy tính là một loại "Máy điện"(Electronic machine), nên nó chỉ có thể hiểu được 0 và 1, hay cụ thể hơn là dòng điện có hay không chạy qua bóng bán dẫn của CPU. Và rõ ràng việc nhập từng số 0 và 1 vào máy tính là một việc chẳng thú vị gì. Nếu bạn vẫn chưa hình dung ra thì đây là "Hello World" trong hệ nhị phân(theo bảng mã ascii): 01001000 01000101 01001100 01001100 01001111 00100000 01010111 01001111 01010010 01001100 01000100
Nó dài như vậy là vì mỗi ký tự được chứa trong 1 byte, và 1 byte chứa tới 8 bit(1 bit là 0 hoặc 1).
Vì sự dài dòng và bất tiện đó nên ta mới cần Compiler, thứ mà sẽ dịch những chương trình C, C++,... sang hợp ngữ(Assembly) và từ Assembly sang mã máy nhờ Assembler.
2. Punched card(thẻ đục lỗ)
Nhưng trước khi Assembler và Compiler ra đời, các lập trình viên vẫn phải làm việc với những số 0 và 1. Và nếu bạn thắc mắc rằng "Khiên và Giáo" của họ trông như thế nào thì.
Punched card(Thẻ đục lỗ):
Và Punched tape(Băng đục lỗ):
Những cái lỗ ấy sẽ tượng trưng cho 1, còn lại là 0.
3. Language Translator
Tất nhiên là chả ai thích việc ngồi bấm mấy cái lỗ lên giấy cả ngày. Thế nên sự khó khăn thôi thúc con người vươn lên(dù chính họ tạo ra sự khó khăn này).
Kết quả của sự vươn lên ấy là việc những Language Translator lần lượt ra đời. Về cơ bản, Language Translator là chương trình sẽ dịch source code của bạn, thứ được viết bằng ngôn ngữ không phải mã máy thành 1 ngôn ngữ khác hoặc trực tiếp sang mã máy. Language Translator sơ khai nhất Assembler dành cho Assembly. Trên nền tảng Assembly, các ngôn ngữ bậc cao hơn cũng như Compiler và Interpreter ra đời.
4. Assembler, Interpreter và Compiler?
1. Assembler
Assembler là trình thông dịch của hợp ngữ(Assembly). Assembly là ngôn ngữ lập trình gần với mã máy nhất, về cả cấu trúc chương trình và việc nó thao tác trực tiếp với CPU. Vì sự gần gũi ấy nên Assembly chỉ cần 1 Assembler để biên dịch trực tiếp sang mã máy mà không cần các bước xử lí trung gian.
2. Interpreter
Interpreter là tên gọi chung của các trình thông dịch(nói chung là chương trình để chạy) của các ngôn ngữ thông dịch(interpet). Source của các ngôn ngữ thông dịch không cần dịch sang Assembly, nên có vẻ nó sẽ tự do hơn trong cú pháp, nhưng vấn đề lớn nhất của ngôn ngữ thông dịch là tốc độ... cực kì rùa. Tiêu biểu trong giới này là: Python, Javascript, PHP,...
3. Compiler
Compiler cũng giống như Interpreter, là tên gọi chung của trình biên dịch, chương trình để biến đống chữ của bạn thành thứ máy tính hiểu được. Nhưng không như Interpreter chạy luôn, Compiler sẽ qua 1 vài bước trung gian để chuyển source thành Assembly, rồi đùn đẩy cho Assembler. Tiêu biểu nhất trong giới biên dịch là C, là nền tảng của rất nhiều ngôn ngữ sau này, tiếp theo là C++, Erlang,...
5. Language Translator(Compile) Internal Architecture (Kiến trúc bên trong của một Language Translator)
Chú ý: Kiến trúc của Language Translator ở đây là của C Compiler và những hình ảnh sau sẽ được lấy từ video của Neso Academy
Language Translator có 4 giai đoạn khác nhau:
1. Preprocessor(Bộ tiền xử lí)
Preprocesser sẽ xử lí để code của bạn "sạch" đủ để Compiler làm việc. Ví dụ như xóa những comment, cũng như là làm việc với những thằng include.
2. Compiler
Ở giải đoạn này, code đã được xử lí ở Preprocesser(Pure High Level Language) sẽ được biên dịch sang Assembly.
3. Assembler
Kế đến là công việc của Assembler, từ code Assembly ở trên, Assembler sẽ generate ra Relocatable Machine code(mã máy nhưng những địa chỉ ô nhớ có thể được đặt lại) bởi vì chúng ta sẽ không thể biết lúc chạy chương trình thì sẽ có những ô nhớ nào trống.
Và bạn cũng có thể thấy rằng trong hình, những địa chỉ i+0, i+1, i+2 là 1 dãy liên tiếp(sequence).
4. Linker/loader
Cuối cùng là Linker/loader, công việc của nó là generate ra Absolute Machine Code(Mã máy tuyệt đối) hay cụ thể hơn là thêm địa chỉ vào Relocatable MC. Absolute MC là loại mã máy thực sự chạy được, và cơ bản là thứ sẽ được tải vào RAM.
6. Compiler Internal Architecture (Kiến trúc của một Compiler)
Nhân vật chính của chúng ta, Compiler có 6 giai đoạn chính. 3 giai đoạn đầu được gọi chung là giai đoạn phân tích(Analysis Phase), 3 giai đoạn cuối là giai đoạn tổng hợp(Synthetic Phase). Tuy nhiên, theo góc độ của phần mềm thì 4 giai đoạn đầu được gọi là Front-end, còn lại là Back-end. Bởi vì, nếu bây giờ bạn đang có một C Compiler cho Window và muốn làm 1 cái cho anh bạn MacOS thì việc bạn cần làm là thay đổi Back-end và tadaa, bạn đó có 1 MacOS C Compiler.
Ngoài ra còn có 2 phần tử khác nữa. Đó là Symbol Table Manager và Error Handle: Trong đó Symbol Table Manager sẽ lấy thông tin ở Analysis Phase và được sử dụng ở Synthetic Phase. Còn Error Handler với công việc chơi với bọ sẽ được sử dụng ở cả 6 phase.
7. Kết
Gần như toàn bộ kiến thức trong bài viết đều lấy từ video của Neso Academy, nên mong bạn sẽ ủng hộ video gốc ấy. Và việc diễn đạt nó bằng tiếng việt thực sự khó. Thế nên nếu bạn có cách giải thích nào hay hơn hoặc có bất kì câu hỏi nào, đừng ngại bình luận. Xin cảm ơn!
All rights reserved