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!

TinhHuynh chém vào lúc

#define LOGDEBUG(message) printf(ANSICOLOR_RED "line=%d %s" ANSI_COLOR_RESET, __LINE, message) ANSI_COLOR_RED anh viết nhầm với ANSI_COLOR_BLUE hay sao vậy ạ ?

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

Timing attack

Timing attack là gì và vì sao một kỹ sư phần mềm như bạn lại phải quan tâm.

Một số kĩ thuật caching với HTTP/1.1

Giới thiệu RFC7234 và một số kỹ thuật tăng tốc web với HTTP/1.1 Caching.

Vô chiêu thắng hữu chiêu