Công việc của một lập trình viên là viết ra những dòng code bằng ngôn ngữ lập trình, tuy nhiên bạn có biết làm sao để máy tính của chúng ta có thể hiểu và thực thi những dòng code đó không. Câu trả lời chính là việc sử dụng compiler. Vậy trình biên dịch compiler là gì, công việc cụ thể của một trình biên dịch bao gồm những bước nào. Bài viết hôm nay chúng ta cùng đi trả lời cho những câu hỏi đó nhé.
Compiler là gì?
Compiler hay trình biên dịch là một chương trình máy tính thực hiện việc chuyển đổi chuỗi các câu lệnh trong một ngôn ngữ lập trình cụ thể (source file) sang thành ngôn ngữ ở bậc thấp hơn mà máy tính có thể hiểu được (machine code). Như chúng ta đều biết, máy tính chỉ có thể hiểu được các chuỗi ký tự 0 và 1 được nhập vào để thực hiện công việc tính toán của nó; tuy nhiên một lập trình viên sẽ gặp nhiều khó khăn cũng như tốn rất nhiều công sức để có thể viết ra được chuỗi 0-1 này (cũng có thể nói là bất khả thi cho việc này). Và trình biên dịch compiler ra đời để giải quyết vấn đề trên.
Hiện nay để tạo ra các chương trình, chúng ta viết code bằng các ngôn ngữ lập trình bậc cao như C, C++, Java,… Sau đó, các ngôn ngữ này được trang bị các trình biên dịch (một số ngôn ngữ sử dụng trình thông dịch interpreter) để chuyển đổi source file sang hợp ngữ (Assembly), rồi thông qua Assembler để chuyển sang machine code. Quá trình này gọi là biên dịch source code cho chương trình.
Mỗi ngôn ngữ thường có nhiều trình biên dịch khác nhau, ví dụ như với ngôn ngữ lập trình C++, chúng ta có MinGW, Borland, Code Block, Clang, Dev C++,… Mỗi trình biên dịch thực chất là một chương trình chạy trên một hoặc nhiều nền tảng hệ điều hành riêng, vì thế bạn cần tìm hiểu rõ chúng trước khi lựa chọn công cụ biên dịch cho chương trình của mình.
Các giai đoạn thực hiện biên dịch
Trình biên dịch thông thường sẽ thực hiện việc chuyển đổi source file qua 2 giai đoạn chính là Giai đoạn phân tích (Analysis) và Giai đoạn tổng hợp (Synthesis). Trong đó, ở giai đoạn đầu, chương trình sẽ được chia thành các phần cơ bản để kiểm tra về mặt cú pháp, ngữ nghĩa, từ đó sinh ra được mã trung gian (Intermediate code). Ở giai đoạn sau, mã trung gian sẽ được tổng hợp lại và tối ưu hóa để cuối cùng tạo ra được mã máy, cũng ra đầu ra của công việc biên dịch.
Công việc cụ thể của một trình biên dịch trong các giai đoạn trên bao gồm:
- Phân tích từ vựng (Lexical Analysis): xác định, phân loại các đơn vị từ vựng trong source code, lược bỏ các comment và xếp chúng vào từng bảng khác nhau
- Phân tích cú pháp (Syntax Analysis): kiểm tra và báo lỗi biên dịch nếu có cú pháp sai, không tồn tại; từ đó xây dựng cấu trúc phân cấp cho từng đoạn code
- Phân tích ngữ nghĩa (Semantic Analysis): kiểm tra xem mã có tuân thủ hệ thống kiểu dữ liệu của ngôn ngữ lập trình hay không, các biến khai báo hay chưa hoặc lời gọi hàm chính xác chưa.
- Tạo mã trung gian (Intermediate Code Generation): giai đoạn này sẽ tạo ra một mã biểu diễn trung gian có thể dễ dàng dịch sang mã máy
- Tối ưu mã (Optimization): áp dụng các kỹ thuật tối ưu hóa khác nhau trên mã trung gian để cải thiện hiệu suất của mã máy được tạo ra
- Tạo mã (Code Generation): giai đoạn cuối cùng sinh ra mã máy thực tế để có thể được thực thi bởi phần cứng.
Phân biệt Assembler, Interpreter và Compiler
Assembler, Interpreter và Compiler là 3 khái niệm dễ gây nhầm lẫn, cả 3 đều là các chương trình dịch source code (Language Translator) có chức năng biến đổi từ ngôn ngữ lập trình thành mã máy.
Assembler
Assembly (hợp ngữ) là một ngôn ngữ lập trình gần với mã máy (machine code) nhất về mặt cấu trúc và cách vận hành, thao tác. Assembler là trình thông dịch của Assembly, giúp biên dịch trực tiếp sang machine code mà không cần thêm các bước xử lý trung gian nào khác.
Interpreter
Interpreter là trình thông dịch, thực hiện việc dịch source code cho các ngôn ngữ thông dịch như Python, JavaScript, PHP,… Trình thông dịch biến đổi trực tiếp các ngôn ngữ lập trình bậc cao sang thành mã máy, vì điều này nên nó có thể thực hiện từng dòng code trong chương trình mà không cần đọc tất cả các file trong source code.
Compiler
Compiler trình biên dịch sẽ thực hiện việc dịch toàn bộ source code chương trình sang một dạng ngôn ngữ bậc thấp hơn như Assembly, từ đó Assembler sẽ thực hiện tiếp việc chuyển sang mã máy và thực hiện chương trình.
Với 3 chương trình trên, Assembler có sự khác biệt rõ với 2 loại còn lại; và Compiler thường được so sánh với Interpreter. Điểm khác nhau cơ bản ở đây là việc compiler cần đọc và thực hiện dịch toàn bộ chương trình còn interpreter sẽ dịch theo từng câu lệnh và thực hiện luôn. Do đó, interpreter sẽ mất ít thời gian phân tích mã nguồn, tuy vậy thì thời gian thực thi tổng thể cả chương trình sẽ chậm hơn. Ngược lại compiler sẽ mất thời gian cho việc phân tích mã, dịch mã toàn bộ nhưng sau đó việc thực thi sẽ được diễn ra nhanh chóng.
Đặc điểm trên cũng giúp chúng ta phân biệt được mục tiêu hướng tới của các ngôn ngữ lập trình biên dịch hay thông dịch. Các ngôn ngữ biên dịch sử dụng compiler như C sẽ ưu tiên đến việc tối ưu chương trình chạy tổng thể. Ngược lại thì những ngôn ngữ thông dịch sử dụng interpreter như Python sẽ ưu tiên vào việc tính toán, thực hiện từng lệnh đơn lẻ nhiều hơn.
Kết bài
Qua bài viết này, chắc hẳn các bạn đã nắm rõ được tầm quan trọng của trình biên dịch trong công việc lập trình. Hiểu rõ được về cơ chế hoạt động, các giai đoạn thực hiện của compiler sẽ giúp biết được một cách cụ thể source code của mình chạy như thế nào, từ đó có sự tối ưu, chỉn chu hơn trong quá trình viết code. Hy vọng bài viết hữu ích dành cho bạn và hẹn gặp lại trong các bài viết tiếp theo của mình.
Tác giả: Phạm Minh Khoa
Xem thêm:
- Prettier là gì? Hướng dẫn định dạng code với Prettier trong dự án
- ESLint là gì và cách dùng ESLint để phát hiện, sửa chữa mã cho dự án Javascript
- Code ví dụ TypeScript Eslint (áp dụng eslint cho TypeScript)
Xem thêm Việc làm IT Jobs for Developer hấp dẫn trên TopDev