Quần Cam Blog

do {} while(0) loop

Gần đây khi bắt đầu đọc lại code C, tui thấy người ta thường wrap lại code macro trong một do {} while(0) loop. Ví dụ như:

#define LINK_MESSAGE_IMPL(p, first_msg, last_msg, num_msgs, where) do { \
    *(p)->where.last = (first_msg);                                 \
    (p)->where.last = (last_msg);                                   \
    (p)->where.len += (num_msgs);                                   \
} while(0)

Nếu bạn chưa biết macro (viết tắt của macroinstruction), nó là định nghĩa cách xuất ra một tập lệnh bằng cách thay thế một tập lệnh được viết sẵn. Giải thích một cách dân dã là dùng code để viết code. Trong C macros có thể được khai báo bằng cú pháp define.

Ví dụ ở đoạn code sau:

#define ANSI_COLOR_BLUE "\x1b[34m"
#define ANSI_COLOR_RESET "\x1b[0m"
#define LOG_DEBUG(message) printf(ANSI_COLOR_RED "line=%d %s" ANSI_COLOR_RESET, __LINE__, message)

int main() {
  LOG_DEBUG("YOLO!");
  return 0;
}

Khi C compiler đọc đoạn code này, preprocessor sẽ triển khai những đoạn mã dùng macro LOG_DEBUG bằng định nghĩa của nó. Chương trình của bạn sau khi qua giai đoạn preprocess sẽ là:

int main() {
  printf("\x1b[34mline=%d %s\x1b[0m", __LINE__, "YOLO!");
  return 0;
}

Macro có thể giúp code của bạn súc tích hơn, dễ đọc hơn, dễ maintain hơn, và đôi khi performant hơn. Một ví dụ điển hình là bật tắt debug log giữa production/development environment.

Tuy nhiên vì macros chỉ rewrite lại code của bạn trong giai đoạn preprocessing, nếu không đủ kinh nghiệm hoặc bất cẩn, bạn có thể viết một chương trình bị lỗi logic.

Giả sử bạn viết một macro như sau:

#define PRINT_HELLO_WORLD_IN_DIFFERENT_LANGUAGES() \
        printf("hello world"); \
        printf("xin chào");

Và bắt đầu đưa nó vào sử dụng.

if (1 < 0) PRINT_HELLO_WORLD_IN_DIFFERENT_LANGUAGES();

Đoạn code có thể sẽ không thực hiện đúng ý đồ của chúng ta, bởi vì if chỉ nhận một single-line statement xếp sau nó, mà macro lại bao gồm hai statement.

if (1 < 0)
  printf("hello world");
printf("xin chào");

Vì không biết macro có được sử dụng như single-line statement hay không, nên để đảm bảo an toàn, các C developers thường bọc các dòng lệnh vào một đoạn do {} while(0) loop.

#define PRINT_HELLO_WORLD_IN_DIFFERENT_LANGUAGES() do {\
        printf("hello world"); \
        printf("xin chào"); \
} while(0)

Sẽ được triển khai thành:

if (1 < 0)
  do {
    printf("hello world");
    printf("xin chào");
  } while(0);

Thủ thuật này sẽ đảm bảo các macro bao gồm nhiều dòng lệnh có thể được sử dụng ở bất kì đâu, cả ở những nơi dùng nó như dòng lệnh đơn. Đồng thời vì điều kiện của vòng lặp là một hằng số, compiler sẽ không sinh ra mã nào để chạy vòng lặp, nên thủ thuật này không tạo ra overhead.


NGUY HIỂM! KHU VỰC NHIỀU GIÓ!
Khuyến cáo giữ chặt bàn phím và lướt thật nhanh khi đi qua khu vực này.
Chức năng này hỗ trợ markdown và các thứ liên quan.
unrealhoang chém vào lúc

Hoặc bạn có thể dùng Rust để không cần phải hack như vậy

qcam chém vào lúc

quá nhanh quá nguy hiểm @unrealhoang ơi!

Bài viết cùng chủ đề

Randomart và thuật toán Ông giám mục say xỉn

IO data và Vectored IO

Bài viết giới thiệu về IO data, Vectored I/O và tối ưu hóa hệ thống dùng Elixir bằng cách tận dụng Vectored I/O.

Giả định và Suy nghĩ khoa học

Trong cuộc sống hằng ngày, ta hay đưa ra những giả định và cho rằng nó mặc định đúng mà không kiểm chứng, nhưng chúng sẽ khiến bạn trông không thông minh lắm. Bài viết giới thiệu về cách suy nghĩ khoa học để tránh đưa ra các giả định.