TỪ NGÔN NGỮ LẬP TRÌNH ĐẾN MÃ MÁY (phần 1 – preprocessing)


Bạn đã biết C++ chưa? Đã học C#, Java, JavaScript, Python?Nghe nói Assembly khó lắm? Nếu Assembly khó thì mã máy thế nào?

Trong bài viết này tôi sẽ cố gắng mô tả quá trình từ một chương trình viết bằng ngôn ngữ bậc cao được dịch và thực thi như thế nào, hi vọng sau bài viết này các bạn sẽ có một cái nhìn tổng quan, hiểu được cơ bản và có thể tự mình giải quyết được các trục trặc thường gặp.Trong loạt bài này tôi sẽ chủ yếu minh họa bằng C, vì đây là ngôn ngữ có đầy đủ “đồ chơi” nhất, cũng như gần với các ngôn ngữ cấp thấp nhất.

❓Trình biên dịch làm thế nào để biến chương trình của bạn thành mã máy?

Sau khi viết xong chương trình, việc tiếp theo cần làm là dịch chương trình của bạn thành dạng có thể chạy được, các câu lệnh sẽ biến thành mã máy, đóng vào một file gọi là file thực thi (như file .exe trên Windows), tùy thuộc vào từng hệ điều hành mà file này sẽ có định dạng khác nhau. Để làm được điều này, quá trình biên dịch sẽ thực thi tuần tự các bước sau:

ℹ️ Xử lý các macro: Trong nhiều ngôn ngữ, người ta cho phép viết các đoạn lệnh đặc biệt, các lệnh này sẽ được xử lý trước khi biên dịch. Trình xử lý macro, hay còn gọi là trình tiền biên dịch chỉ đọc file mã nguồn, tìm các macro và xử lý chúng. Việc xử lý các macro thường tương đối đơn giản và gần như chỉ là tìm kiếm và thay thế (tất nhiên cũng phức tạp hơn một chút). Ví dụ trong ngôn ngữ C, ta sẽ thấy một số macro như sau (trong C một macro bắt đầu bằng ký hiệu #):

#include <stdio.h>
#include "header_file.h"
#define PI 3.1415

👉 Với macro #include, trình tiền biên dịch sẽ đi tìm một file có tên stdio.h, đọc toàn bộ nội dung file đó rồi chèn vào file mã nguồn ngay tại vị trí bạn định nghĩa macro. Nội dung đó sẽ được chèn vào, bất kể nó có hợp lệ trong C hay không. Hay với #define, một macro tên là PI sẽ được tạo ra, và từ đó kể về sau bất cứ khi nào bạn dùng đến cái tên đó, giá trị tương ứng sẽ được thay vào. Hãy xem chương trình sau:

#include <stdio.h>
#define PI 3.1415
int main()
{
    float radius, area;
    printf("Enter the radius: ");
    scanf("%f", &radius);
    // Notice, the use of PI
    area = PI*radius*radius;
    printf("Area=%.2f",area);
    return 0;
}

Khi trình tiền biên dịch xong việc, chương trình của bạn sẽ có nội dung như sau:

//chỗ này là nội dung file stdio.h
int main()
{
    float radius, area;
    printf("Enter the radius: ");
    scanf("%f", &radius);
    area = 3.1415*radius*radius;
    printf("Area=%.2f",area);
    return 0;
}

❗️Không phải ngôn ngữ nào cũng hỗ trợ macro!👉 Vậy giả sử bạn khai báo #include <stdio.h> 2 lần thì sao? Tất nhiên nội dung file stdio.h sẽ được chèn vào 2 lần. Thực tế điều này rất hay xảy ra, giả sử file header_file.h trong ví dụ trên cũng có #include <stdio.h>, vậy khi bạn chèn nội dung header_file.h cũng đồng nghĩa với việc chèn thêm stdio.h một lần nữa.Để giải quyết vấn đề này, ta sẽ sử dụng các macro điều kiện như #ifdef, file stdio.h sẽ được viết thế này:

#ifdef __STDIO_H_     
   // nội dung file stdio.h ở đây
#define __STDIO_H_
#endif

👉 Như vậy nội dung file stdio.h (đoạn nằm giữa #ifdef#endif) chỉ được chèn vào nếu chưa có macro __STDIO_H_, ngược lại sẽ bị bỏ qua. Vì bản thân __STDIO_H_ được định nghĩa ngay bên trong đó, nên kể từ sau lần chèn vào (đồng thời định nghĩa __STDIO_H_), nội dung của nó sẽ không được xử lý nữa.Sau khi hoàn thành quá trình tiền xử lý, các file mã nguồn của bạn đã sẵn sàng cho quá trình tiếp theo: Biên dịch.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s