Bộ tiền xử lý C: Làm chủ #define, Macro và biên dịch có điều kiện

banner

Tóm tắt kiến thức

C Preprocessor là giai đoạn đầu tiên trong quá trình biên dịch, cho phép thao tác mã nguồn trước khi compile. Hiểu rõ preprocessor sẽ giúp bạn tạo ra code linh hoạt, có thể tái sử dụng và dễ bảo trì.

C Preprocessor là một công cụ mạnh mẽ trong ngôn ngữ lập trình C, thực hiện các thao tác trên mã nguồn trước khi trình biên dịch xử lý. Nó bao gồm các directive như #define, #include, #ifdef và nhiều tính năng khác giúp tạo ra mã linh hoạt, có thể tái sử dụng và dễ bảo trì.

Quảng cáo giúp chúng tôi duy trì trang web này

Tổng quan về C Preprocessor

Khái niệm Preprocessor

Preprocessor là giai đoạn đầu tiên trong quá trình biên dịch C. Nó xử lý các directive (chỉ thị) bắt đầu bằng dấu # trước khi trình biên dịch thực sự biên dịch mã.

Các directive chính

DirectiveMô tả
#includeBao gồm file header
#defineĐịnh nghĩa macro
#undefHủy định nghĩa macro
Tại sao Preprocessor quan trọng?
  • Code reuse: Cho phép tái sử dụng code thông qua include
  • Conditional compilation: Biên dịch khác nhau cho các platform
  • Macro efficiency: Thay thế text để tối ưu hiệu suất
  • Debugging: Có thể bật/tắt debug code

#include Directive

Cách sử dụng #include

#include <header_file.h>    // Tìm trong thư mục hệ thống
#include "header_file.h"    // Tìm trong thư mục hiện tại

Ví dụ:

#include <stdio.h>    // Thư viện chuẩn
#include <stdlib.h>   // Thư viện chuẩn
#include "myheader.h" // Header file tự tạo

int main() {
    printf("Hello, World!\n");
    return 0;
}

Tạo header file tùy chỉnh

myheader.h:

#ifndef MYHEADER_H
#define MYHEADER_H

#define PI 3.14159
#define MAX_SIZE 100

int add(int a, int b);
void printMessage(char *msg);

#endif

myheader.c:

#include "myheader.h"
#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

void printMessage(char *msg) {
    printf("Message: %s\n", msg);
}

#define Directive

Định nghĩa hằng số

#include <stdio.h>

#define PI 3.14159
#define MAX_STUDENTS 50
#define PROGRAM_NAME "Student Management System"

int main() {
    printf("Chuong trinh: %s\n", PROGRAM_NAME);
    printf("So sinh vien toi da: %d\n", MAX_STUDENTS);
    printf("Gia tri PI: %.5f\n", PI);

    return 0;
}

Macro đơn giản

#include <stdio.h>

#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))

int main() {
    int num = 5;

    printf("Binh phuong cua %d: %d\n", num, SQUARE(num));
    printf("Max cua 10 va 20: %d\n", MAX(10, 20));
    printf("Min cua 10 va 20: %d\n", MIN(10, 20));

    return 0;
}

Macro phức tạp

#include <stdio.h>

#define SWAP(a, b) do { \
    int temp = (a); \
    (a) = (b); \
    (b) = temp; \
} while(0)

#define DEBUG_PRINT(fmt, ...) \
    printf("[DEBUG] " fmt "\n", ##__VA_ARGS__)

int main() {
    int x = 10, y = 20;

    printf("Truoc khi swap: x = %d, y = %d\n", x, y);
    SWAP(x, y);
    printf("Sau khi swap: x = %d, y = %d\n", x, y);

    DEBUG_PRINT("Gia tri x: %d", x);
    DEBUG_PRINT("Gia tri y: %d", y);

    return 0;
}

Conditional Compilation

#ifdef và #ifndef

#include <stdio.h>

#define DEBUG_MODE

int main() {
    #ifdef DEBUG_MODE
        printf("Che do debug dang bat\n");
        printf("Thong tin chi tiet ve chuong trinh\n");
    #endif

    #ifndef RELEASE_MODE
        printf("Chuong trinh dang trong che do phat trien\n");
    #endif

    return 0;
}

#if với điều kiện

#include <stdio.h>

#define VERSION_MAJOR 2
#define VERSION_MINOR 1

int main() {
    #if VERSION_MAJOR > 1
        printf("Phien ban chinh: %d\n", VERSION_MAJOR);
    #elif VERSION_MAJOR == 1
        printf("Phien ban dau tien\n");
    #else
        printf("Phien ban phien ban thu nghiem\n");
    #endif

    #if VERSION_MINOR >= 1
        printf("Phien ban phu: %d\n", VERSION_MINOR);
    #endif

    return 0;
}

Ví dụ thực tế: Cross-platform code

#include <stdio.h>

// Giả sử các macro này được định nghĩa bởi trình biên dịch
// #define WINDOWS
// #define LINUX
// #define MACOS

void printOSInfo() {
    #if defined(WINDOWS)
        printf("He dieu hanh: Windows\n");
        printf("Duong dan ngan cach: \\\n");
    #elif defined(LINUX)
        printf("He dieu hanh: Linux\n");
        printf("Duong dan ngan cach: /\n");
    #elif defined(MACOS)
        printf("He dieu hanh: macOS\n");
        printf("Duong dan ngan cach: /\n");
    #else
        printf("He dieu hanh: Khong xac dinh\n");
    #endif
}

int main() {
    printOSInfo();
    return 0;
}

Function-like Macros

Macro với tham số

#include <stdio.h>

#define ADD(a, b) ((a) + (b))
#define MULTIPLY(a, b) ((a) * (b))
#define POWER(base, exp) (exp == 0 ? 1 : base * POWER(base, exp - 1))

int main() {
    int x = 5, y = 3;

    printf("ADD(%d, %d) = %d\n", x, y, ADD(x, y));
    printf("MULTIPLY(%d, %d) = %d\n", x, y, MULTIPLY(x, y));

    return 0;
}

Macro với nhiều tham số

#include <stdio.h>

#define PRINT_VALUES(a, b, c) \
    printf("a = %d, b = %d, c = %d\n", (a), (b), (c))

#define CREATE_ARRAY(name, size, type) \
    type name[size]

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

int main() {
    int x = 10, y = 20, z = 30;
    PRINT_VALUES(x, y, z);

    CREATE_ARRAY(numbers, 5, int);
    numbers[0] = 1;
    numbers[1] = 2;
    numbers[2] = 3;
    numbers[3] = 4;
    numbers[4] = 5;

    printf("Kich thuoc mang: %zu\n", ARRAY_SIZE(numbers));

    return 0;
}

Macro với ## (Token Pasting)

#include <stdio.h>

#define DECLARE_VARIABLE(type, name) \
    type var_##name

#define GET_VARIABLE(name) \
    var_##name

#define CREATE_FUNCTION(name, type) \
    type get_##name() { \
        return var_##name; \
    } \
    void set_##name(type value) { \
        var_##name = value; \
    }

CREATE_FUNCTION(count, int)
CREATE_FUNCTION(price, float)

int main() {
    DECLARE_VARIABLE(int, count);
    DECLARE_VARIABLE(float, price);

    set_count(100);
    set_price(25.5);

    printf("Count: %d\n", get_count());
    printf("Price: %.2f\n", get_price());

    return 0;
}

Variadic Macros

Macro với số lượng tham số không xác định

#include <stdio.h>

#define PRINT(...) printf(__VA_ARGS__)
#define DEBUG_LOG(level, ...) \
    printf("[%s] ", level); \
    printf(__VA_ARGS__); \
    printf("\n")

#define MAX_VARARGS(...) \
    max_varargs_impl(__VA_ARGS__)

int max_varargs_impl(int count, ...) {
    // Implementation for finding max
    // This is simplified version
    return count;
}

int main() {
    PRINT("Xin chao %s, ban co %d diem!\n", "Anh", 85);

    DEBUG_LOG("INFO", "Chuong trinh dang chay");
    DEBUG_LOG("ERROR", "Co loi xay ra tai dong %d", 42);
    DEBUG_LOG("WARNING", "Canh bao: %s", "Du lieu khong hop le");

    return 0;
}

Header Guards

Tránh include nhiều lần

math_utils.h:

#ifndef MATH_UTILS_H
#define MATH_UTILS_H

#define PI 3.14159
#define E 2.71828

int add(int a, int b);
int multiply(int a, int b);
double circle_area(double radius);

#endif

math_utils.c:

#include "math_utils.h"

int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

double circle_area(double radius) {
    return PI * radius * radius;
}

main.c:

#include <stdio.h>
#include "math_utils.h"  // Lần 1
#include "math_utils.h"  // Lần 2 - sẽ bị bỏ qua

int main() {
    printf("PI = %.5f\n", PI);
    printf("Tong 5 + 3 = %d\n", add(5, 3));
    printf("Dien tich hinh tron ban kinh 5: %.2f\n", circle_area(5.0));

    return 0;
}

#pragma Directive

Các #pragma phổ biến

#include <stdio.h>

// Bỏ qua cảnh báo
#pragma GCC diagnostic ignored "-Wunused-variable"

// Pack struct để tiết kiệm bộ nhớ
#pragma pack(push, 1)
struct PackedStruct {
    char a;
    int b;
    char c;
};
#pragma pack(pop)

// Struct bình thường
struct NormalStruct {
    char a;
    int b;
    char c;
};

int main() {
    printf("Kich thuoc PackedStruct: %zu bytes\n", sizeof(struct PackedStruct));
    printf("Kich thuoc NormalStruct: %zu bytes\n", sizeof(struct NormalStruct));

    return 0;
}

Macro Debugging

Macro để debug

#include <stdio.h>

#ifdef DEBUG
    #define DBG_PRINT(fmt, ...) \
        fprintf(stderr, "[DEBUG %s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)

    #define DBG_ASSERT(condition) \
        if (!(condition)) { \
            fprintf(stderr, "[ASSERT FAILED %s:%d] %s\n", __FILE__, __LINE__, #condition); \
            exit(1); \
        }
#else
    #define DBG_PRINT(fmt, ...)
    #define DBG_ASSERT(condition)
#endif

void processArray(int *arr, int size) {
    DBG_PRINT("Bat dau xu ly mang voi %d phan tu", size);

    DBG_ASSERT(arr != NULL);
    DBG_ASSERT(size > 0);

    for (int i = 0; i < size; i++) {
        DBG_PRINT("arr[%d] = %d", i, arr[i]);
        arr[i] *= 2;
    }

    DBG_PRINT("Hoan thanh xu ly mang");
}

int main() {
    int numbers[] = {1, 2, 3, 4, 5};
    int size = sizeof(numbers) / sizeof(numbers[0]);

    processArray(numbers, size);

    printf("Mang sau khi xu ly: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", numbers[i]);
    }
    printf("\n");

    return 0;
}

Ví dụ thực tế: Configuration System

config.h:

#ifndef CONFIG_H
#define CONFIG_H

// Application configuration
#define APP_NAME "My Application"
#define APP_VERSION "1.0.0"

// Feature flags
#define ENABLE_LOGGING
#define ENABLE_DEBUG_MODE
// #define ENABLE_PRODUCTION_MODE

// Platform detection
#if defined(_WIN32)
    #define PLATFORM_WINDOWS
#elif defined(__linux__)
    #define PLATFORM_LINUX
#elif defined(__APPLE__)
    #define PLATFORM_MACOS
#endif

// Conditional compilation based on features
#ifdef ENABLE_LOGGING
    #define LOG_INFO(msg) printf("[INFO] %s\n", msg)
    #define LOG_ERROR(msg) printf("[ERROR] %s\n", msg)
#else
    #define LOG_INFO(msg)
    #define LOG_ERROR(msg)
#endif

#ifdef ENABLE_DEBUG_MODE
    #define DEBUG_PRINT(fmt, ...) printf("[DEBUG] " fmt "\n", ##__VA_ARGS__)
#else
    #define DEBUG_PRINT(fmt, ...)
#endif

#endif

main.c:

#include <stdio.h>
#include "config.h"

void initializeApplication() {
    LOG_INFO("Initializing application");
    DEBUG_PRINT("Application name: %s", APP_NAME);
    DEBUG_PRINT("Application version: %s", APP_VERSION);

    #ifdef PLATFORM_WINDOWS
        LOG_INFO("Running on Windows platform");
    #elif defined(PLATFORM_LINUX)
        LOG_INFO("Running on Linux platform");
    #elif defined(PLATFORM_MACOS)
        LOG_INFO("Running on macOS platform");
    #else
        LOG_ERROR("Unknown platform");
    #endif
}

int main() {
    initializeApplication();

    #ifdef ENABLE_PRODUCTION_MODE
        printf("Running in production mode\n");
    #else
        printf("Running in development mode\n");
    #endif

    return 0;
}

Ví dụ thực hành

1. Tạo macro để đo thời gian thực thi

#include <stdio.h>
#include <time.h>

#define START_TIMER() clock_t start_time = clock()
#define END_TIMER() \
    do { \
        clock_t end_time = clock(); \
        double cpu_time = ((double)(end_time - start_time)) / CLOCKS_PER_SEC; \
        printf("Thoi gian thuc thi: %.6f giay\n", cpu_time); \
    } while(0)

void bubbleSort(int arr[], int n) {
    for (int i = 0; i < n-1; i++) {
        for (int j = 0; j < n-i-1; j++) {
            if (arr[j] > arr[j+1]) {
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

int main() {
    int arr[1000];

    // Khởi tạo mảng với giá trị ngẫu nhiên
    for (int i = 0; i < 1000; i++) {
        arr[i] = 1000 - i;
    }

    START_TIMER();
    bubbleSort(arr, 1000);
    END_TIMER();

    return 0;
}

2. Macro để tạo enum với string conversion

#include <stdio.h>

#define DECLARE_ENUM(name, ...) \
    typedef enum { __VA_ARGS__ } name; \
    const char* name##_strings[] = { #__VA_ARGS__ }; \
    const char* name##_to_string(name value) { \
        return name##_strings[value]; \
    }

DECLARE_ENUM(Color, RED, GREEN, BLUE, YELLOW, PURPLE)

int main() {
    Color myColor = BLUE;
    printf("Mau cua toi: %s\n", Color_to_string(myColor));

    return 0;
}

Tổng kết

C Preprocessor là công cụ mạnh mẽ giúp tạo ra mã linh hoạt, có thể tái sử dụng và dễ bảo trì.

Lưu ý quan trọng về Preprocessor
  • Macro side effects: Cẩn thận với side effects trong macro
  • Operator precedence: Sử dụng dấu ngoặc đơn để tránh lỗi precedence
  • Type safety: Macro không có type checking như function
  • Debugging: Macro có thể khó debug hơn function
Best Practices
  • Sử dụng header guards để tránh multiple inclusion
  • Đặt tên macro bằng chữ hoa để phân biệt với variables
  • Sử dụng do-while(0) cho macro multi-statement
  • Tránh macro quá phức tạp, ưu tiên inline functions

Với những kiến thức này, bạn đã sẵn sàng để tạo ra các chương trình C linh hoạt, có thể tái sử dụng và dễ bảo trì!

Last updated on