Breaking News

THẾ NÀO LÀ NGÔN NGỮ LẬP TRÌNH BẬC THẤP, NGÔN NGỮ LẬP TRÌNH BẬC CAO?

 

THẾ NÀO LÀ NGÔN NGỮ LẬP TRÌNH BẬC THẤP?

THẾ NÀO LÀ NGÔN NGỮ LẬP TRÌNH BẬC CAO?

Ngôn ngữ lập trình là gì thì chắc ở đây ai cũng biết rồi, vậy nhưng tại sao người ta còn có bậc thấp và bậc cao?

Để ngắn gọn, ta có thể nhớ luôn Hợp ngữ (Assembly language) và ngôn ngữ máy là ngôn ngữ cấp thấp, còn tất cả các ngôn ngữ khác đều là bậc cao.

 

1. NGÔN NGỮ MÁY

Máy tính vốn không hiểu tiếng người, bộ nhớ của nó chỉ chứa duy nhất các bit 0 và 1, được gom lại thành từng byte. Việc đọc hay ghi luôn được thực hiện theo đơn vị byte, cũng như việc đánh địa chỉ cũng theo byte. Bạn không thể yêu cầu CPU hay các thiết bị ngoại vi: "Hãy lấy cho tôi 1 bit ở vị trí xyz nào đó". Muốn làm điều đó bạn phải tính toán xem bit đó thuộc byte nào (cứ chia cho 8 là được), đọc byte đó, rồi xem bit đó tương ứng với vị trí thứ mấy trong byte, dùng một toán tử bit nào đó (AND chẳng hạn) để kiểm tra xem nó bằng 1 hay bằng 0.

Mã máy cũng không ngoại lệ, chúng cũng chỉ là những con số, được ghi theo một quy tắc do CPU quy định. Ví dụ nếu nó quy định số 1 là phép toán cộng 2 biến a và b trong bộ nhớ, sau đó gán vào biến c, vì phép toán cộng (mã máy 1) này sẽ cần 3 tham số, nên 3 byte kế tiếp sẽ thuộc vào lệnh này luôn, ta sẽ có 4 byte trong bộ nhớ như sau (được viết dưới dạng hex):

 

0x01 0x20 0x21 0x40

 

(giả sử biến a nằm ở địa chỉ 0x20, biến b nằm ở 0x21 và c tại 0x40)

CPU biết rằng lệnh 0x01 luôn chiếm 4 byte, nên nó sẽ đọc và thực thi mã lệnh kế tiếp sau 4 byte này. Trong thực tế các lệnh sẽ phức tạp hơn, ví dụ địa chỉ sẽ lớn hơn (vì 1 byte chỉ có thể định vị được đến địa chỉ 255), cho phép làm việc với thanh ghi... tuy nhiên phương thức làm việc thì hoàn toàn giống như ví dụ trên.

Có thể mã máy nghe rất phức tạp, nhưng thực sự khi đã biết cách nó làm việc thì lại không quá khó hiểu. Tôi không rõ bây giờ các bạn học như thế nào, nhưng xưa tôi học là dùng các phần mềm hiển thị các file .exe dạng hex, sau đó in ra giấy (yes! in ra giấy) và ngồi dịch ngược lại bằng tay để xem nó làm gì. Tất nhiên các chương trình .exe này cũng là các chương trình nhỏ, đơn giản được tạo ra với mục đích học tập.

 

Nói qua quy tắc thì có thể đơn giản, nhưng viết chương trình bằng ngôn ngữ máy có nhiều nhược điểm:

- Rất khó nhớ mã lệnh, một CPU thời cách đây vài chục năm cũng đã có hàng trăm lệnh khác nhau, những tập lệnh được bổ sung sau đó cũng rất lớn, vậy nên việc nhớ hết là hoàn toàn không khả thi (và dễ nhầm lẫn).

- Khi bạn thay đổi một phần chương trình, bạn sẽ phải sửa lại rất nhiều thứ. Trong ví dụ trên, nếu ta chèn thêm một biến vào trước c thì sao? Vậy c sẽ phải dịch ra sau 1 byte, và địa chỉ của c sẽ là 0x41, câu lệnh c = a + b ở trên cũng phải sửa lại, nếu không kết quả phép cộng sẽ được gán cho biến mới chứ không phải là c nữa. Hay nếu như bạn muốn đổi kiểu dữ liệu của a, b và từ byte thành int (32 bit)? Vậy thì địa chỉ của b cũng phải đổi thành 0x23, và c sẽ là 0x 0x46. Bạn sẽ phải sửa lại tất cả các câu lệnh liên quan đến a, b, c, và thậm chí cả những câu lệnh liên quan đến các biến nằm phía sau chúng nữa.

Nói chung không cần phải giải thích thì bạn cũng đã thấy việc viết một chương trình bằng mã máy phức tạp và bất tiện thế nào, vì vậy người ta đã nghĩ ra Assembly language.

 

ASSEMBLY LANGUAGE

Để đơn giản hóa việc ghi nhớ, người ta sẽ tạo ra một bảng ánh xạ 1-1 giữa từ khóa và mã máy, ví dụ khi tôi ánh xạ 0x01 với add (lệnh cộng hai số), tôi có thể viết câu lệnh trên như sau:

 

add [0x20],[0x21],[0x40]

 

Trình dịch sẽ tự động chuyển chữ add đó thành số 0x01, ta đỡ phải ghi nhớ, mà dễ đọc hơn nhiều.

Đối với các biến, assembly language cũng cho phép chúng ta khai báo và sử dụng thông qua tên thay vì địa chỉ:

 

a db 0x99

b db 0x01

c db 0

 

db cho phép khai báo một biến kiểu byte, trình dịch sẽ tự động tính toán để biết a, b, c nằm ở địa chỉ nào, do vậy khi ta viết:

add a,b,c

Trình dịch sẽ tự động thay thế add với 0x01, vị trí a, b, c sẽ được thay thế bởi địa chỉ của các biến tương ứng. Nếu ta thay đổi kiểu dữ liệu, trình dịch sẽ tự động tính toán theo kiểu dữ liệu mới, ta không cần phải làm thêm gì nữa.

 

Assembly có rất nhiều ưu điểm, bạn có thể nhìn vào một câu lệnh và biết ngay nó làm gì, không còn phải dịch ngược từ những con số sang một cái gì có ý nghĩa để có thể hiểu. Có thể nói hiện tại có lẽ không ai còn phải viết trực tiếp bằng ngôn ngữ máy nữa, nhưng nó cũng có một số nhược điểm:

 

- Phụ thuộc vào kiến trúc hệ thống: mỗi một CPU có một tập lệnh riêng, bạn không thể đem chương trình viết cho CPU này sang một CPU khác.

- Chỉ thể hiện được đúng những gì CPU làm, tức là theo cách suy nghĩ của CPU, không phải của con người. Đoạn lệnh

 

if (a > b) {

c = 1;

}

else {

c = 0;

}

 

sẽ phải được viết là:

 

mov eax, DWORD PTR [a] ; gán biến a vào thanh ghi eax, CPU Intel không cho phép so sánh trực tiếp 2 giá trị trong bộ nhớ

cmp eax, DWORD PTR [b] ; so sánh eax với biến b

jle .L1 ; jle = jump if less than or equal, nếu b < eax thì nhảy 'goto' đến nhãn L1

mov DWORD PTR [c], 1 ; nếu không thì gán c = 1 rồi nhảy đến L2

jmp .L2

.L1:

mov DWORD PTR [c], 0

.L2:

 

Vẫn khá khó hiểu phải không? Vậy nên người ta phát minh ra ngôn ngữ lập trình bậc cao.

 

2. NGÔN NGỮ LẬP TRÌNH BẬC CAO

Ngoài 2 ngôn ngữ trên, tất cả những ngôn ngữ bạn học đều là bậc cao: C, C++, Java, JavaScript, PHP, C#... Ngôn ngữ bậc cao được tạo ra với những thành phần logic mang tư duy con người hơn: các cấu trúc điều khiển, vòng lặp, các lớp, hàm, các toán tử, biểu thức, các cấu trúc phức tạp hơn như async/await, virtual, class, lock, using, try/catch... Tất cả những thứ này sẽ được trình dịch dịch thành mã máy, một cấu trúc đơn giản ở bậc cao có thể được diễn dịch ra thành hàng chục, hàng trăm lệnh máy.

Các ngôn ngữ bậc cao có rất nhiều ưu điểm so với assembly language:

- Dễ đọc, dễ học, dễ nhớ... nói thật là ai cũng có thể học được nếu muốn, nên mấy bạn biết được vài ngôn ngữ, code được vài chương trình rồi lại nghĩ mình giỏi ơi là giỏi là hem phải nhé 😁.

- Cho phép triển khai nhiều khái niệm trừu tượng, phức tạp mà nếu chỉ sử dụng assembly thì không làm nổi (như OOP, async/await...).

- Cho phép dịch chương trình sang nhiều kiến trúc khác nhau mà không cần sửa code.

 

Với người lập trình ứng dụng, bạn chỉ cần thành thạo một ngôn ngữ nào đó là có thể đi làm được, bạn không cần hiểu sâu về những gì diễn ra đằng sau, bạn không cần biết kiến trúc máy tính thế nào (tất nhiên biết thì vẫn tốt hơn), chỉ cần viết, dịch và chạy. Thay vì phải mất vài năm, giờ chỉ cần vài tháng là đã có thể 'hành nghề' được rồi. Tất cả là nhờ các ngôn ngữ bậc cao.

 

Ấy! Nhưng sao nghe nói còn có ngôn ngữ bậc trung nữa???

Khái niệm ngôn ngữ lập trình bậc trung tôi cũng không rõ do ai nghĩ ra, nhưng có lẽ nó mang tính marketing nhiều hơn kỹ thuật. Nó được dùng để chỉ đến những ngôn ngữ bậc cao nhưng cho phép chúng ta kiểm soát sâu hơn về code được sinh ra. Ngoài C/C++, có lẽ chỉ có Rust (với nhiều tính năng undocumented) có thể được coi là ngôn ngữ bậc trung (nếu bạn biết thêm cái nào thì hãy kể giúp tôi nhé).

Vì sao ta cần kiểm soát sâu cách code được tạo ra? Với một ứng dụng bình thường, có lẽ bạn không quan tâm lắm, một chương trình .exe có kích thước 100KB, nay tăng lên thành 150KB cũng chẳng có vấn đề gì, tôi dám cá là 99% các bạn còn không quan tâm sau khi sửa code và dịch lại thì nó tăng hay giảm bao nhiêu byte. Tuy nhiên, khi làm việc ở cấp độ sâu hơn, hoặc viết chương trình cho các thiết bị có tài nguyên vô cùng hạn chế: bộ nhớ 4KB, 8KB chẳng hạn, hoặc làm việc với các cấu trúc dữ liệu của hệ điều hành, hoặc thậm chí khi viết các hệ điều hành, bạn sẽ phải tính toán từng byte một, bạn sẽ phải kiểm soát chính xác kích thước và vị trí của từng biến. Thậm chí có những câu lệnh mà chẳng ngôn ngữ lập trình nào cung cấp, như thay đổi chế độ hoạt động của CPU, hoặc thay đổi nội dung của một thanh ghi hệ thống... trong trường hợp đó bạn phải nhúng thêm một ít code assembly.

 

Hầu hết các ngôn ngữ không cho phép bạn làm điều đó, ngoại trừ một số rất ít kể ở trên. Điều đó cũng hoàn toàn bình thường, vì số người viết driver, code nhúng hay hệ điều hành ít hơn nhiều so với số người muốn viết các ứng dụng thông thường.

 

Nhưng nói chung là ngôn ngữ lập trình bậc trung vẫn là ngôn ngữ bậc cao!

 

Đọc thêm loạt bài về OOP: 

No comments