Các kỹ thuật nâng cao sử dụng #define trong C và C++

Định nghĩa và hủy bỏ macro

#define PI 3.14    // Thay thế tại thời điểm biên dịch
#define T1 3+4     // Dễ gây nhầm lẫn
#define T2 (3+4)   // Thêm dấu ngoặc để rõ nghĩa

float r = 1.0;
float area = PI * r * r;    
int a = 2* T1;    // Sau khi thay thế: int a = 2*3+4, không đúng ý
int a = 2* T2;    // Sau khi thay thế: int a = 2*(3+4), đúng ý

#undef PI
float area = PI * r * r;     // Lỗi: 'PI' chưa được khai báo

// Macro trong chuỗi sẽ không bị thay thế
printf("%s:%f\n", "PI", PI);    // In ra PI: 3.14

// Tên macro phải là một định danh hợp lệ
#define 0x abcd    // Lỗi: Không thể bắt đầu bằng số

// Các dấu ngoặc kép và đơn phải xuất hiện thành cặp
#define TEST11 "Z    // Lỗi
#define TEST2 'Z     // Lỗi

Macro có tham số

// Định nghĩa macro max và min với tham số
#define MAX(a,b) (a>b ? a:b)
#define MIN(a,b) (a<b ? a:b)

// Sử dụng macro có tham số
int sum = MAX(1,2) + MIN(1,2);    // Sau khi thay thế: int sum = (1>2 ? 1:2) + (1<2 ? 1:2)

// Số lượng tham số phải khớp với số lượng tham số đã định nghĩa
MAX(1,2,3);    // Báo lỗi

#undef MAX    // Hủy định nghĩa MAX
MAX(1,2);    // Lỗi: 'MAX' chưa được khai báo
</pre>

Định nghĩa macro nhiều dòng

// Định nghĩa macro hoán đổi giá trị trên nhiều dòng, sử dụng dấu gạch chéo ngược
#define SWAP(a,b) do { \
    int t = 0;\
    t = a; \
    a = b; \
    b = t; \
} while(0)

Những ký hiệu đặc biệt: #, ##, #@

#define CONCAT(a,b) a##b
#define TO_CHAR(a) #@a
#define TO_STRING(a) #a

// a##b nối các tham số
int n = CONCAT(123, 456);                // Kết quả: n = 123456
char *str = CONCAT("abcd", "efg");       // Kết quả: str = "abcdefg"

//@#a bao quanh tham số a bằng dấu nháy đơn, trả về một ký tự
char ch1 = TO_CHAR(1);        // Kết quả: ch = '1'
char ch2 = TO_CHAR(123);      // Lỗi: Dấu nháy đơn chỉ dùng cho một ký tự

//#a bao quanh tham số a bằng dấu nháy kép, trả về một chuỗi
char *str1 = TO_STRING(123);    // str = "123"

Một số macro thông dụng

Ngăn chặn việc bao gồm header file nhiều lần

#ifndef BODYDEF_H 
#define BODYDEF_H 

// Nội dung của header file

#endif

Lấy giá trị byte hoặc word từ địa chỉ cụ thể

#include <stdio.h>

// B đại diện cho byte
#define MEM_B(x)  ( *( (unsigned char *) (x) ) )
// W đại diện cho word, tương đương với int
#define MEM_W(x)  ( *( (int *) (x) ) )

int main() {
    int bTest = 0x123456;

    unsigned char m = MEM_B(&bTest);    /*m=0x56*/
    int n = MEM_W(&bTest);              /*n=0x3456*/

    return 0;
}

Lấy offset của một trường trong struct

#define OFFSET(type, field) ( (size_t) &((type *) 0)->field )

Lấy kích thước của một trường trong struct

#define FIELD_SIZE(type, field) sizeof( ((type *) 0)->field )

Lấy địa chỉ của biến (độ rộng word)

#define BYTE_PTR(var) ( (unsigned char *) (void *) &(var) ) 
#define WORD_PTR(var) ( (int *) (void *) &(var) )

Chuyển đổi chữ cái thành chữ hoa

#define UPPERCASE(c) ( ((c) >= 'a' && (c) <= 'z') ? ((c) - 0x20) : (c) )

Kiểm tra xem ký tự có phải là số thập phân

#define IS_DECIMAL(c) ((c) >= '0' && (c) <= '9')

Kiểm tra xem ký tự có phải là số thập lục phân

#define IS_HEX(c) ( ((c) >= '0' && (c) <= '9') || ((c) >= 'A' && (c) <= 'F') || ((c) >= 'a' && (c) <= 'f') )

Phương pháp ngăn chặn tràn số

#define INC_SAT(val) (val = ((val)+1 > (val)) ? (val)+1 : (val))

Trả về số lượng phần tử trong mảng

#define ARRAY_SIZE(a) ( sizeof( (a) ) / sizeof( (a[0]) ) )

Sử dụng macro để theo dõi và gỡ lỗi

Trong quá trình gỡ lỗi, chúng ta có thể đặt macro __DEBUG, hoặc sử dụng tùy chọn -D trong Makefile.

#define __DEBUG

Cách sử dụng:

#ifdef __DEBUG
printf("%s", ...);
#endif

Ngoài ra, chuẩn ANSI C có một số macro được định nghĩa sẵn, thường được sử dụng trong các câu lệnh printf, sprintf, v.v.:

  • __func__: Chèn tên hàm hiện tại vào mã nguồn;
  • __LINE__: Chèn số dòng hiện tại vào mã nguồn;
  • __FILE__: Chèn tên file hiện tại vào mã nguồn;
  • __DATE__: Chèn ngày biên dịch vào mã nguồn;
  • __TIME__: Chèn thời gian biên dịch vào mã nguồn;
  • __STDC__: Được gán giá trị 1 nếu yêu cầu chương trình tuân thủ nghiêm ngặt chuẩn ANSI C;
  • __cplusplus: Được định nghĩa khi viết chương trình C++.

Ví dụ sử dụng __cplusplus trong header file:

#ifndef _ZX_FUNC_H
#define _ZX_FUNC_H

#ifdef __cplusplus
extern "C" {
#endif

/* functions */
char *strdup (const char *s);

#ifdef __cplusplus
}
#endif

#endif

extern "C" cho phép mã C++ gọi hàm C, đảm bảo rằng hàm được biên dịch theo cách của C.

Thẻ: C C++ macro #define preprocessing

Đăng vào ngày 4 tháng 6 lúc 16:26