Tại sao sử dụng các câu lệnh do-while và if-other rõ ràng vô nghĩa trong macro?


788

Trong nhiều macro C / C ++, tôi thấy mã của macro được bao bọc trong một do whilevòng lặp dường như vô nghĩa . Dưới đây là ví dụ.

#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else

Tôi không thể thấy những gì do whileđang làm. Tại sao không chỉ viết này mà không có nó?

#define FOO(X) f(X); g(X)

2
Đối với ví dụ với cái khác, tôi sẽ thêm một biểu thức voidloại ở cuối ... như ((void) 0) .
Phil1970

1
Nhắc nhở rằng do whilecấu trúc không tương thích với các câu lệnh trả về, do đó, if (1) { ... } else ((void)0)cấu trúc có các cách sử dụng tương thích hơn trong Tiêu chuẩn C. Và trong GNU C, bạn sẽ thích cấu trúc được mô tả trong câu trả lời của tôi.
Cœur

Câu trả lời:


829

Các do ... whileif ... elseđang có để làm cho nó để dấu chấm phẩy sau macro của bạn luôn luôn có nghĩa là điều tương tự. Giả sử bạn có một cái gì đó giống như macro thứ hai của bạn.

#define BAR(X) f(x); g(x)

Bây giờ nếu bạn sử dụng BAR(X);trong một if ... elsecâu lệnh, trong đó phần thân của câu lệnh if không được bọc trong dấu ngoặc nhọn, bạn sẽ gặp một bất ngờ xấu.

if (corge)
  BAR(corge);
else
  gralt();

Đoạn mã trên sẽ mở rộng thành

if (corge)
  f(corge); g(corge);
else
  gralt();

đó là cú pháp không chính xác, vì cái khác không còn liên quan đến if. Nó không giúp bọc mọi thứ trong các dấu ngoặc nhọn trong macro, bởi vì dấu chấm phẩy sau khi niềng răng về mặt cú pháp là không chính xác.

if (corge)
  {f(corge); g(corge);};
else
  gralt();

Có hai cách khắc phục vấn đề. Đầu tiên là sử dụng dấu phẩy để sắp xếp các câu lệnh trong macro mà không cướp đi khả năng của nó để hành động như một biểu thức.

#define BAR(X) f(X), g(X)

Phiên bản trên của thanh BARmở rộng mã ở trên thành những gì sau đây, về mặt cú pháp là chính xác.

if (corge)
  f(corge), g(corge);
else
  gralt();

Điều này không hoạt động nếu thay vì f(X)bạn có một bộ mã phức tạp hơn cần phải đi trong khối riêng của nó, ví dụ như để khai báo các biến cục bộ. Trong trường hợp chung nhất, giải pháp là sử dụng một cái gì đó giống như do ... whilelàm cho macro trở thành một câu lệnh duy nhất có dấu chấm phẩy mà không bị nhầm lẫn.

#define BAR(X) do { \
  int i = f(X); \
  if (i > 4) g(i); \
} while (0)

Bạn không cần phải sử dụng do ... while, bạn cũng có thể nấu một thứ gì đó if ... else, mặc dù khi if ... elsemở rộng bên trong if ... elsenó sẽ dẫn đến một "mối nguy hiểm khác ", điều này có thể làm cho một vấn đề nguy hiểm khác hiện hữu thậm chí còn khó tìm hơn, như trong đoạn mã sau .

if (corge)
  if (1) { f(corge); g(corge); } else;
else
  gralt();

Vấn đề là sử dụng hết dấu chấm phẩy trong các bối cảnh trong đó một dấu chấm phẩy lơ lửng là sai lầm. Tất nhiên, vào thời điểm này, có thể (và có lẽ nên) lập luận rằng sẽ tốt hơn nếu khai báo BARlà một hàm thực tế, không phải là một macro.

Tóm lại, do ... whilecó để khắc phục những thiếu sót của bộ tiền xử lý C. Khi những hướng dẫn kiểu C đó bảo bạn bỏ bộ tiền xử lý C, đây là điều họ lo lắng.


18
Đây không phải là một lập luận mạnh mẽ để luôn luôn sử dụng niềng răng trong, trong khi và cho các câu lệnh sao? Nếu bạn đưa ra quan điểm luôn luôn làm điều này (ví dụ như được yêu cầu cho MISRA-C), thì vấn đề được mô tả ở trên sẽ biến mất.
Steve Melnikoff

17
Ví dụ dấu phẩy nên được #define BAR(X) (f(X), g(X))ưu tiên bởi toán tử có thể làm rối loạn ngữ nghĩa.
Stewart

20
@DawidFerenczy: mặc dù cả bạn và tôi từ bốn năm rưỡi trước đều có một điểm tốt, chúng ta phải sống trong thế giới thực. Trừ khi chúng tôi có thể đảm bảo rằng tất cả các ifcâu lệnh, v.v., trong mã của chúng tôi đều sử dụng dấu ngoặc nhọn, sau đó gói macro như thế này là một cách đơn giản để tránh các vấn đề.
Steve Melnikoff

8
Lưu ý: if(1) {...} else void(0)biểu mẫu an toàn hơn so với các do {...} while(0)macro có tham số là mã được bao gồm trong bản mở rộng macro, vì nó không làm thay đổi hành vi của việc ngắt hoặc tiếp tục từ khóa. Ví dụ: for (int i = 0; i < max; ++i) { MYMACRO( SomeFunc(i)==true, {break;} ) }gây ra hành vi không mong muốn khi MYMACROđược xác định là #define MYMACRO(X, CODE) do { if (X) { cout << #X << endl; {CODE}; } } while (0)do ngắt ảnh hưởng đến vòng lặp while của macro chứ không phải vòng lặp for tại trang web gọi macro.
Chris Kline

5
@ace void(0)là một lỗi đánh máy, ý tôi là (void)0. Và tôi tin rằng điều này không giải quyết được vấn đề "lơ lửng khác": chú ý không có dấu chấm phẩy sau (void)0. Một treo lủng lẳng khác trong trường hợp đó (ví dụ if (cond) if (1) foo() else (void)0 else { /* dangling else body */ }) gây ra lỗi biên dịch. Dưới đây là một ví dụ trực tiếp chứng minh điều đó
Chris Kline

153

Macro là bản sao / dán các đoạn văn bản mà bộ xử lý trước sẽ đặt vào mã chính hãng; tác giả của macro hy vọng sự thay thế sẽ tạo ra mã hợp lệ.

Có ba "mẹo" tốt để thành công trong đó:

Giúp macro hoạt động như mã chính hãng

Mã bình thường thường được kết thúc bằng dấu chấm phẩy. Nếu người dùng xem mã không cần ...

doSomething(1) ;
DO_SOMETHING_ELSE(2)  // <== Hey? What's this?
doSomethingElseAgain(3) ;

Điều này có nghĩa là người dùng hy vọng trình biên dịch sẽ tạo ra lỗi nếu không có dấu chấm phẩy.

Nhưng lý do thực sự tốt là vào một lúc nào đó, tác giả của macro có thể sẽ cần thay thế macro bằng một hàm chính hãng (có thể được nội tuyến). Vì vậy, vĩ mô nên thực sự hành xử như một.

Vì vậy, chúng ta nên có một macro cần bán đại tràng.

Tạo mã hợp lệ

Như được hiển thị trong câu trả lời của jfm3, đôi khi macro chứa nhiều hơn một lệnh. Và nếu macro được sử dụng bên trong câu lệnh if, điều này sẽ có vấn đề:

if(bIsOk)
   MY_MACRO(42) ;

Macro này có thể được mở rộng như:

#define MY_MACRO(x) f(x) ; g(x)

if(bIsOk)
   f(42) ; g(42) ; // was MY_MACRO(42) ;

Các gchức năng sẽ được thực hiện không phụ thuộc vào giá trị của bIsOk.

Điều này có nghĩa là chúng ta phải thêm một phạm vi vào macro:

#define MY_MACRO(x) { f(x) ; g(x) ; }

if(bIsOk)
   { f(42) ; g(42) ; } ; // was MY_MACRO(42) ;

Tạo mã hợp lệ 2

Nếu macro là một cái gì đó như:

#define MY_MACRO(x) int i = x + 1 ; f(i) ;

Chúng ta có thể có một vấn đề khác trong đoạn mã sau:

void doSomething()
{
    int i = 25 ;
    MY_MACRO(32) ;
}

Bởi vì nó sẽ mở rộng như:

void doSomething()
{
    int i = 25 ;
    int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}

Mã này sẽ không được biên dịch, tất nhiên. Vì vậy, một lần nữa, giải pháp đang sử dụng một phạm vi:

#define MY_MACRO(x) { int i = x + 1 ; f(i) ; }

void doSomething()
{
    int i = 25 ;
    { int i = 32 + 1 ; f(i) ; } ; // was MY_MACRO(32) ;
}

Mã hoạt động chính xác một lần nữa.

Kết hợp hiệu ứng bán đại tràng + phạm vi?

Có một thành ngữ C / C ++ tạo ra hiệu ứng này: Vòng lặp do / while:

do
{
    // code
}
while(false) ;

Do / while có thể tạo ra một phạm vi, do đó gói gọn mã của macro và cần một dấu chấm phẩy cuối cùng, do đó mở rộng thành mã cần một mã.

Tiền thưởng?

Trình biên dịch C ++ sẽ tối ưu hóa vòng lặp do / while, vì thực tế hậu điều kiện của nó là sai được biết tại thời điểm biên dịch. Điều này có nghĩa là một macro như:

#define MY_MACRO(x)                                  \
do                                                   \
{                                                    \
    const int i = x + 1 ;                            \
    f(i) ; g(i) ;                                    \
}                                                    \
while(false)

void doSomething(bool bIsOk)
{
   int i = 25 ;

   if(bIsOk)
      MY_MACRO(42) ;

   // Etc.
}

sẽ mở rộng chính xác như

void doSomething(bool bIsOk)
{
   int i = 25 ;

   if(bIsOk)
      do
      {
         const int i = 42 + 1 ; // was MY_MACRO(42) ;
         f(i) ; g(i) ;
      }
      while(false) ;

   // Etc.
}

và sau đó được biên dịch và tối ưu hóa đi như

void doSomething(bool bIsOk)
{
   int i = 25 ;

   if(bIsOk)
   {
      f(43) ; g(43) ;
   }

   // Etc.
}

6
Lưu ý rằng việc thay đổi macro thành hàm nội tuyến sẽ thay đổi một số macro được xác định trước tiêu chuẩn, ví dụ mã sau đây cho thấy sự thay đổi trong FUNCTIONLINE : #include <stdio.h> #define Fmacro () printf ("% s% d \ n", FUNCTION , LINE ) void void Finline () {printf ("% s% d \ n", FUNCTION , LINE ); } int main () {Fmacro (); Chung kết (); trả về 0; } (các thuật ngữ in đậm nên được bao quanh bởi dấu gạch dưới kép - định dạng xấu!)
Gnubie

6
Có một số vấn đề nhỏ nhưng không hoàn toàn không quan trọng với câu trả lời này. Ví dụ: void doSomething() { int i = 25 ; { int i = x + 1 ; f(i) ; } ; // was MY_MACRO(32) ; }không phải là sự mở rộng chính xác; những xtrong việc mở rộng nên có 32. Một vấn đề phức tạp hơn là việc mở rộng là gì MY_MACRO(i+7). Và một cái khác là mở rộng MY_MACRO(0x07 << 6). Có rất nhiều điều tốt, nhưng có một số tôi không bị phát hiện và không bị che giấu.
Jonathan Leffler

@Gnubie: Tôi trường hợp bạn vẫn ở đây và bạn chưa figured này ra bây giờ: bạn có thể thoát khỏi dấu hoa thị và dấu gạch chân trong ý kiến với gạch chéo ngược, vì vậy nếu bạn gõ \_\_LINE\_\_nó ám như __LINE__. IMHO, tốt hơn là chỉ sử dụng định dạng mã cho mã; ví dụ: __LINE__(không yêu cầu xử lý đặc biệt). Tái bút: Tôi không biết liệu điều này có đúng vào năm 2012 hay không; họ đã thực hiện một vài cải tiến cho động cơ kể từ đó.
Scott

1
Đánh giá cao rằng nhận xét của tôi trễ sáu năm, nhưng hầu hết các trình biên dịch C không thực sự có inlinechức năng nội tuyến (như được cho phép bởi tiêu chuẩn)
Andrew

53

@ jfm3 - Bạn có một câu trả lời hay cho câu hỏi. Bạn cũng có thể muốn thêm rằng thành ngữ macro cũng ngăn chặn hành vi vô ý có thể nguy hiểm hơn (vì không có lỗi) với các câu lệnh 'if' đơn giản:

#define FOO(x)  f(x); g(x)

if (test) FOO( baz);

mở rộng tới:

if (test) f(baz); g(baz);

đúng về mặt cú pháp nên không có lỗi trình biên dịch, nhưng có thể xảy ra hậu quả không lường trước là g () sẽ luôn được gọi.


22
"Có lẽ ngoài ý muốn"? Tôi đã có thể nói "chắc chắn ngoài ý muốn", nếu không thì lập trình viên cần phải được đưa ra ngoài và bắn (trái ngược với bị trừng phạt tròn trịa bằng roi da).
Lawrence Dol

4
Hoặc họ có thể được tăng lương, nếu họ làm việc cho một công ty gồm ba chữ cái và lén lút chèn mã đó vào một chương trình nguồn mở được sử dụng rộng rãi ... :-)
R .. GitHub DỪNG GIÚP ICE

2
Và nhận xét này chỉ khiến tôi nhớ đến dòng goto fail trong lỗi xác minh chứng chỉ SSL gần đây được tìm thấy trong Hệ điều hành Apple
Gerard Sexton

23

Các câu trả lời trên giải thích ý nghĩa của các cấu trúc này, nhưng có một sự khác biệt đáng kể giữa hai cấu trúc không được đề cập. Trong thực tế, có một lý do để thích do ... whilevới if ... elsecấu trúc.

Vấn đề của if ... elsecấu trúc là nó không bắt buộc bạn phải đặt dấu chấm phẩy. Giống như trong mã này:

FOO(1)
printf("abc");

Mặc dù chúng tôi đã bỏ dấu chấm phẩy (do nhầm lẫn), mã sẽ mở rộng thành

if (1) { f(X); g(X); } else
printf("abc");

và sẽ âm thầm biên dịch (mặc dù một số trình biên dịch có thể đưa ra cảnh báo cho mã không thể truy cập được). Nhưng printftuyên bố sẽ không bao giờ được thực thi.

do ... whilecấu trúc không có vấn đề như vậy, vì mã thông báo hợp lệ duy nhất sau while(0)dấu chấm phẩy.


2
@RichardHansen: Vẫn không tốt bằng, vì nhìn từ lời gọi macro, bạn không biết liệu nó có mở rộng thành một câu lệnh hay thành một biểu thức hay không. Nếu ai đó giả định sau này, cô ấy có thể viết FOO(1),x++;một lần nữa sẽ cho chúng tôi một dương tính giả. Chỉ cần sử dụng do ... whilevà đó là nó.
Yakov Galka

1
Tài liệu vĩ mô để tránh sự hiểu lầm nên đủ. Tôi đồng ý rằng điều đó do ... while (0)là tốt hơn, nhưng nó có một nhược điểm: A breakhoặc continuesẽ điều khiển do ... while (0)vòng lặp, không phải vòng lặp chứa lời gọi macro. Vì vậy, ifmẹo vẫn có giá trị.
Richard Hansen

2
Tôi không thấy nơi bạn có thể đặt một breakhoặc một continuecái sẽ được nhìn thấy như bên trong do {...} while(0)vòng lặp giả macro của bạn . Ngay cả trong tham số macro, nó sẽ gây ra lỗi cú pháp.
Patrick Schlüter

5
Một lý do khác để sử dụng do { ... } while(0)thay vì if whateverxây dựng, là bản chất thành ngữ của nó. Cấu do {...} while(0)trúc được phổ biến rộng rãi, được nhiều lập trình viên biết đến và sử dụng rất nhiều. Cơ sở lý luận và tài liệu của nó là dễ dàng được biết đến. Không phải như vậy cho việc ifxây dựng. Do đó, cần ít nỗ lực hơn để tìm kiếm khi xem xét mã.
Patrick Schlüter

1
@tristopia: Tôi đã thấy mọi người viết các macro lấy các khối mã làm đối số (mà tôi không nhất thiết phải đề xuất). Ví dụ : #define CHECK(call, onerr) if (0 != (call)) { onerr } else (void)0. Nó có thể được sử dụng như CHECK(system("foo"), break;);, trong đó break;dự định đề cập đến vòng lặp kèm theo CHECK()lời gọi.
Richard Hansen

16

Mặc dù các trình biên dịch dự kiến ​​sẽ tối ưu hóa các do { ... } while(false);vòng lặp, nhưng có một giải pháp khác không yêu cầu cấu trúc đó. Giải pháp là sử dụng toán tử dấu phẩy:

#define FOO(X) (f(X),g(X))

hoặc thậm chí xa hơn:

#define FOO(X) g((f(X),(X)))

Mặc dù điều này sẽ hoạt động tốt với các hướng dẫn riêng biệt, nhưng nó sẽ không hoạt động với các trường hợp các biến được xây dựng và sử dụng như một phần của #define:

#define FOO(X) (int s=5,f((X)+s),g((X)+s))

Với điều này, người ta sẽ buộc phải sử dụng cấu trúc do / while.


cảm ơn, vì toán tử dấu phẩy không đảm bảo lệnh thực thi, việc lồng này là một cách để thực thi điều đó.
Marius

15
@Marius: Sai; các nhà điều hành dấu phẩy là một điểm trình tự và do đó không đảm bảo thực hiện theo thứ tự. Tôi nghi ngờ bạn nhầm lẫn nó với dấu phẩy trong danh sách đối số chức năng.
R .. GitHub DỪNG GIÚP ICE

2
Gợi ý kỳ lạ thứ hai làm cho ngày của tôi.
Spidey

Chỉ muốn thêm rằng các trình biên dịch buộc phải duy trì hành vi có thể quan sát được của chương trình, vì vậy tối ưu hóa việc làm / trong khi đi không phải là vấn đề lớn (giả sử tối ưu hóa trình biên dịch là chính xác).
Marco A.

@MarcoA. Trong khi bạn nói đúng, tôi đã phát hiện ra rằng tối ưu hóa trình biên dịch, trong khi vẫn bảo toàn chính xác chức năng của mã, nhưng bằng cách thay đổi xung quanh các dòng dường như không làm gì trong ngữ cảnh đơn lẻ, sẽ phá vỡ các thuật toán đa luồng. Trường hợp tại điểm Peterson's Algorithm.
Marius

11

Thư viện tiền xử lý P99 của Jens Gustyt (vâng, thực tế là một thứ như vậy tồn tại đã thổi vào tâm trí tôi!) Cải thiện if(1) { ... } elsecấu trúc theo một cách nhỏ nhưng có ý nghĩa bằng cách xác định như sau:

#define P99_NOP ((void)0)
#define P99_PREFER(...) if (1) { __VA_ARGS__ } else
#define P99_BLOCK(...) P99_PREFER(__VA_ARGS__) P99_NOP

Lý do cho điều này là, không giống như do { ... } while(0)cấu trúc, breakcontinuevẫn hoạt động bên trong khối đã cho, nhưng ((void)0)sẽ tạo ra lỗi cú pháp nếu dấu chấm phẩy bị bỏ qua sau lệnh gọi macro, nếu không sẽ bỏ qua khối tiếp theo. (Thực sự không có vấn đề "lơ lửng nào khác" ở đây, vì các elseliên kết gần nhất if, là vấn đề trong macro.)

Nếu bạn quan tâm đến các loại điều có thể được thực hiện ít nhiều an toàn với bộ tiền xử lý C, hãy kiểm tra thư viện đó.


Mặc dù rất thông minh, điều này khiến người ta bị bắn phá với các cảnh báo của trình biên dịch về nguy cơ tiềm ẩn khác.
Phân đoạn

1
Bạn thường sử dụng macro để tạo môi trường chứa, nghĩa là bạn không bao giờ sử dụng break(hoặc continue) bên trong macro để kiểm soát vòng lặp bắt đầu / kết thúc bên ngoài, đó chỉ là phong cách xấu và che giấu các điểm thoát tiềm năng.
mirabilos

Ngoài ra còn có một thư viện tiền xử lý trong Boost. Tâm trí về nó là gì?
Rene

Rủi ro else ((void)0)là ai đó có thể đang viết YOUR_MACRO(), f();và nó sẽ có giá trị về mặt cú pháp, nhưng không bao giờ gọi f(). Với do whileđó là một lỗi cú pháp.
melpomene

@melpomene vậy thì else do; while (0)sao?
Carl Lei

8

Vì một số lý do tôi không thể bình luận về câu trả lời đầu tiên ...

Một số bạn đã hiển thị macro với các biến cục bộ, nhưng không ai đề cập rằng bạn không thể sử dụng bất kỳ tên nào trong macro! Nó sẽ cắn người dùng một ngày nào đó! Tại sao? Bởi vì các đối số đầu vào được thay thế vào mẫu macro của bạn. Và trong các ví dụ vĩ mô của bạn, bạn đã sử dụng tên i có thể được sử dụng phổ biến nhất .

Ví dụ khi macro sau

#define FOO(X) do { int i; for (i = 0; i < (X); ++i) do_something(i); } while (0)

được sử dụng trong các chức năng sau

void some_func(void) {
    int i;
    for (i = 0; i < 10; ++i)
        FOO(i);
}

macro sẽ không sử dụng biến dự định i, được khai báo ở đầu some_func, nhưng biến cục bộ, được khai báo trong vòng lặp do ... while của macro.

Vì vậy, không bao giờ sử dụng tên biến phổ biến trong một macro!


Mẫu thông thường là thêm dấu gạch dưới trong tên biến trong macro - chẳng hạn int __i;.
Blaisorblade

8
@Blaisorblade: Thật ra đó là C không chính xác và bất hợp pháp; dấu gạch dưới hàng đầu được dành riêng để sử dụng khi thực hiện. Lý do bạn đã thấy "mẫu thông thường" này là từ các tiêu đề hệ thống đọc ("triển khai") phải tự giới hạn trong không gian tên dành riêng này. Đối với các ứng dụng / thư viện, bạn nên chọn các tên tối nghĩa, không có khả năng va chạm của riêng bạn mà không có dấu gạch dưới, ví dụ mylib_internal___ihoặc tương tự.
R .. GitHub DỪNG GIÚP ICE

2
@R .. Bạn nói đúng - Tôi thực sự đã đọc cái này trong '' ứng dụng '', hạt nhân Linux, nhưng dù sao nó cũng không phải là ngoại lệ vì nó không sử dụng thư viện chuẩn (về mặt kỹ thuật, thay vào đó là triển khai '' tự do '' của một '' được lưu trữ '' một).
Blaisorblade

3
@R .. điều này không hoàn toàn chính xác: hàng đầu gạch dưới tiếp theo là một chữ hoa hoặc dấu gạch dưới thứ hai được dành riêng cho việc thực hiện trong tất cả các bối cảnh. Các dấu gạch dưới hàng đầu theo sau bởi một cái gì đó khác không được bảo lưu trong phạm vi địa phương.
Leushenko

@Leushenko: Vâng, nhưng sự khác biệt đủ tinh tế mà tôi thấy tốt nhất là nói với mọi người rằng đừng sử dụng những cái tên như vậy. Những người hiểu được sự tinh tế có lẽ đã biết rằng tôi đang xem qua các chi tiết. :-)
R .. GitHub DỪNG GIÚP ICE

7

Giải trình

do {} while (0)if (1) {} elsephải đảm bảo rằng macro được mở rộng thành chỉ 1 lệnh. Nếu không thì:

if (something)
  FOO(X); 

sẽ mở rộng sang:

if (something)
  f(X); g(X); 

g(X)sẽ được thực hiện bên ngoài iftuyên bố kiểm soát. Điều này được tránh khi sử dụng do {} while (0)if (1) {} else.


Thay thế tốt hơn

Với biểu thức câu lệnh GNU (không phải là một phần của tiêu chuẩn C), bạn có cách tốt hơn do {} while (0)if (1) {} elseđể giải quyết vấn đề này, chỉ bằng cách sử dụng ({}):

#define FOO(X) ({f(X); g(X);})

Và cú pháp này tương thích với các giá trị trả về (lưu ý do {} while (0)là không), như trong:

return FOO("X");

3
việc sử dụng kẹp khối {} trong macro sẽ đủ để đóng gói mã macro để tất cả được thực thi cho cùng một đường dẫn if-condition. do-while xung quanh được sử dụng để thực thi dấu chấm phẩy tại các vị trí mà macro được sử dụng. do đó, macro được thực thi hành vi nhiều chức năng như nhau. điều này bao gồm các yêu cầu cho dấu chấm phẩy khi sử dụng.
Alexander Stohr

2

Tôi không nghĩ rằng nó đã được đề cập vì vậy hãy xem xét điều này

while(i<100)
  FOO(i++);

sẽ được dịch sang

while(i<100)
  do { f(i++); g(i++); } while (0)

chú ý cách i++đánh giá hai lần bởi macro. Điều này có thể dẫn đến một số lỗi thú vị.


15
Điều này không liên quan gì đến cấu trúc do ... while (0).
Trent

2
Thật. Nhưng có liên quan đến chủ đề của macro so với các hàm và cách viết một macro hoạt động như một hàm ...
John Nilsson

2
Tương tự như trên, đây không phải là một câu trả lời mà là một nhận xét. Về chủ đề: đó là lý do tại sao bạn chỉ sử dụng công cụ một lần:do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)
mirabilos
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.