C – Bộ Tiền Xử Lý (Preprocessors)

Học Lập Trình C - Bộ Tiền Xử Lý (Preprocessors)

Bộ tiền xử lý C (C Preprocessor) không phải là một phần của trình biên dịch (compiler), nhưng là một bước riêng biệt trong quá trình biên dịch (compile). Nói một cách dễ hiểu, Bộ tiền xử lý C chỉ là một công cụ thay thế văn bản (text) và nó hướng dẫn compiler thực hiện xử lý cần thiết trước khi biên dịch thực sự. Chúng ta sẽ gọi tắt Bộ tiền xử lý C là CPP.

Tất cả các lệnh tiền xử lý bắt đầu bằng ký hiệu hash (#). Phần sau liệt kê tất cả chỉ thị tiền xử lý quan trọng.

Sr.No.Directive & Description
1#define
Substitutes a preprocessor macro.
2#include
Inserts a particular header from another file.
3#undef
Undefines a preprocessor macro.
4#ifdef
Returns true if this macro is defined.
5#ifndef
Returns true if this macro is not defined.
6#if
Tests if a compile time condition is true.
7#else
The alternative for #if.
8#elif
#else and #if in one statement.
9#endif
Ends preprocessor conditional.
10#error
Prints error message on stderr.
11#pragma
Issues special commands to the compiler, using a standardized method.

Ví Dụ Tiền Xử Lý

Chúng ta hãy phân tích những ví dụ sau để hiểu các loại chỉ thị (directive).

#define MAX_ARRAY_LENGTH 20

Chỉ thị này báo cho CPP thay thế MAX_ARRAY_LENGTH với 20. Sử dụng #define với hằng số giúp khả năng đọc dễ hơn.

#include <stdio.h>
#include “myheader.h”

Những chỉ thị này báo CPP lấy stdio.h từ Thư viện Hệ thống (System Libraries) và thêm text trong file stdio.h vào file source hiện tại. Dòng kế tiếp báo CPP lấy myheader.h từ thư mục hiện tại và thêm nội dung của file myheader.h vào file nguồn hiện tại.

#undef  FILE_SIZE
#define FILE_SIZE 42

Báo cho CPP undefine macro FILE_SIZE đang tồn tại và define nó thành 42

#ifndef MESSAGE
   #define MESSAGE "You wish!"
#endif

Nếu macro MESSAGE chưa được define thì define một macro MESSAGE

#ifdef DEBUG
   /* Your debugging statements here */
#endif

Dùng để yêu cầu CPP xử lý những lệnh bên trong, nếu macro DEBUG đã được define. Sẽ rất hữu dụng nếu bạn truyền cờ -DDEBUG cho bộ biên dịch gcc khi biên dịch. Việc này sẽ define DEBUG, do đó bạn có thể bật và tắt gỡ lỗi ngay lập tức trong quá trình biên dịch.

Macro Được Định Nghĩa Trước (Predefined Macros)

ANSI C có định nghĩa sẵn một số macro. Mặc dù chúng có sẵn để lập trình, tuy nhiên những macro này không nên bị thay đổi trực tiếp.

Sr.No.Macro & Description
1__DATE__
The current date as a character literal in “MMM DD YYYY” format.
2__TIME__
The current time as a character literal in “HH:MM:SS” format.
3__FILE__
This contains the current filename as a string literal.
4__LINE__
This contains the current line number as a decimal constant.
5__STDC__
Defined as 1 when the compiler complies with the ANSI standard.

Hãy thử ví dụ sau:

#include <stdio.h>

int main() {

   printf("File :%s\n", __FILE__ );
   printf("Date :%s\n", __DATE__ );
   printf("Time :%s\n", __TIME__ );
   printf("Line :%d\n", __LINE__ );
   printf("ANSI :%d\n", __STDC__ );

}

Khi đoạn code trên được compile và execute, nó sẽ cho kết quả như sau

File :test.c
Date :Jun 2 2012
Time :03:36:24
Line :8
ANSI :1

Toán Tử Tiền Xử Lý (Preprocessor Operators)

Bộ tiền xử lý C cung cấp các toán tử sau để giúp tạo macro.

Toán Tử Tiếp Tục Macro (\) | The Macro Continuation (\) Operator

Một macro thường được giới hạn trong một dòng. Toán tử tiếp tục macro (\) được sử dụng để tiếp tục một macro quá dài đối với một dòng. Ví dụ:

#define  message_for(a, b)  \
   printf(#a " and " #b ": We love you!\n")

Toán Tử Chuỗi Kí Tự Hóa (#)

Toán tử chuỗi ký tự hóa (stringize) là kí tự number-sign (‘#’), khi được sử dụng trong một định nghĩa macro, sẽ chuyển đổi một tham số macro thành một hằng chuỗi (string constant). Toán tử này chỉ có thể được sử dụng trong một macro có đối số (argument) hoặc danh sách tham số (parameter lest) cụ thể. Ví dụ.

#include <stdio.h>

#define  message_for(a, b)  \
   printf(#a " and " #b ": We love you!\n")

int main(void) {
   message_for(Carole, Debra);
   return 0;
}

Biên dịch và chạy đoạn code trên, ta nhận được kết quả sau.

Carole and Debra: We love you!

Toán Tử Dán Token (##)

ví dụ học lập trình C, quá trình tiền xử lý

Toán tử dán token (token-pasting) ‘##’ cùng với một định nghĩa macro kết hợp hai đối số. Nó cho phép hai token riêng biệt trong định nghĩa macro được tham gia thành một token duy nhất. Ví dụ:

#include <stdio.h>

#define tokenpaster(n) printf ("token" #n " = %d", token##n)

int main(void) {
   int token34 = 40;
   tokenpaster(34);
   return 0;
}

Đoạn code ví dụ trên sẽ cho kết quả sau.

token34 = 40

Có được kết quả là do output từ bộ tiền xử lý tạo ra.

printf ("token34 = %d", token34);

Ví dụ này cho thấy cách chuyển đổi token##n thành token34 và ở đây chúng ta đã sử dụng cả chuỗi ký tự hóa (stringize)dán token (token-pasting).

Toán Tử defined()

Toán tử tiền xử lý defined được sử dụng trong các biểu thức hằng (constant expression) để kiểm tra xem một định danh (identifier) có được định nghĩa (define) bằng cách sử dụng #define hay không. Nếu định danh này đã được định nghĩa, thì giá trị là true (non-zero). Nếu không được định nghĩa thì giá trị là false (zero). Toán tử defined có dạng như sau.

#include <stdio.h>

#if !defined (MESSAGE)
   #define MESSAGE "You wish!"
#endif

int main(void) {
   printf("Here is the message: %s\n", MESSAGE);  
   return 0;
}

Đoạn code trên sau khi compile và chạy sẽ cho kết quả sau.

Here is the message: You wish!

Macro Được Tham Số Hóa (Parameterized Macros)

Một trong những chức năng ưu việt của CPP là khả năng mô phỏng hảm (function) bằng cách sử dụng macro được tham số hóa (parameterized macro). Ví dụ: chúng ta có thể code một hàm bình phương một số như sau.

int square(int x) {
   return x * x;
}

Chúng ta có thể viết lại hàm trên bằng cách sử dụng macro như sau.

#define square(x) ((x) * (x))

Macro với đối số square(x) phải được định nghĩa bằng dẫn xuất (directive) #define trước khi được sử dụng.

Danh sách đối số phải được đặt trong dấu ngoặc đơn và ngay sau tên macro. Lưu ý, không được phép có khoảng cách (space) ở giữa tên macro và dấu mở ngoặc đơn. Ví dụ.

#include <stdio.h>

#define MAX(x,y) ((x) > (y) ? (x) : (y))

int main(void) {
   printf("Max between 20 and 10 is %d\n", MAX(10, 20));  
   return 0;
}

Sau khi biên dịch và chạy đoạn code trên, ta được kết quả như sau.

Max between 20 and 10 is 20	

Xem thêm Macro Arguments của GNU.

Đôi khi người ta có thể dùng macro để giảm số dòng code, chi tiết có thể đọc thêm ở trang Stackoverflow How to define function as macro?

(Tiếp theo)

Icons made by Freepik from www.flaticon.com