your programing

매크로에서 무의미한 do-while 및 if-else 문을 사용하는 이유는 무엇입니까?

lovepro 2020. 9. 29. 08:19
반응형

매크로에서 무의미한 do-while 및 if-else 문을 사용하는 이유는 무엇입니까?


많은 C / C ++ 매크로에서 무의미한 do while루프 처럼 보이는 매크로 코드가 래핑되어 있습니다. 여기에 예가 있습니다.

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

나는 무엇을하고 있는지 볼 수 없다 do while. 왜 그것없이 이것을 쓰지 않습니까?

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

do ... while과는 if ... else너무 매크로 후 세미콜론은 항상 같은 일을 의미있다 할 수 있습니다. 두 번째 매크로와 같은 것이 있다고 가정 해 보겠습니다.

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

사용한다면 이제 BAR(X);if ... else은 if 문의 몸은 중괄호에 싸여되지 않은 문, 당신은 나쁜 놀라움을받을 것입니다.

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

위의 코드는

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

else는 더 이상 if와 연관되지 않기 때문에 구문 적으로 올바르지 않습니다. 중괄호 뒤의 세미콜론이 구문 적으로 올바르지 않기 때문에 매크로 내에서 중괄호로 항목을 감싸는 것은 도움이되지 않습니다.

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

문제를 해결하는 방법에는 두 가지가 있습니다. 첫 번째는 쉼표를 사용하여 매크로 내에서 표현식처럼 작동하는 능력을 빼앗기지 않고 명령문을 시퀀스하는 것입니다.

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

위 버전의 bar BAR는 위의 코드를 구문 적으로 올바른 다음으로 확장합니다.

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

f(X)예를 들어 지역 변수를 선언하기 위해 자체 블록으로 이동해야하는 더 복잡한 코드 본문이있는 경우에는 작동하지 않습니다 . 가장 일반적인 경우 해결책은 do ... while매크로가 혼동없이 세미콜론을 사용하는 단일 명령문 이되도록하는 것과 같은 것을 사용하는 것 입니다.

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

를 사용할 필요가 없습니다.을 사용 do ... while하여 무언가를 만들 수도 if ... else있습니다.하지만 if ... else내부에서 확장 if ... else하면 " dangling else "가 발생하여 기존 dangling else 문제를 찾기가 더 어려워 질 수 있습니다. 다음 코드에서와 같이 .

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

요점은 매달린 세미콜론이 잘못된 컨텍스트에서 세미콜론을 사용하는 것입니다. 물론,이 시점 BAR에서 매크로가 아닌 실제 함수 로 선언 하는 것이 더 낫다고 주장 할 수 있습니다 .

요약 do ... while하면 C 전 처리기의 단점을 해결할 수있는 방법이 있습니다. 그 C 스타일 가이드가 C 전처리기를 해체하라고 말할 때, 이것이 그들이 걱정하는 종류입니다.


매크로는 전처리 기가 정품 코드에 넣을 텍스트의 복사 / 붙여 넣기 조각입니다. 매크로 작성자는 대체가 유효한 코드를 생성하기를 바랍니다.

성공하기위한 세 가지 좋은 "팁"이 있습니다.

매크로가 정품 코드처럼 작동하도록 지원

일반 코드는 일반적으로 세미콜론으로 끝납니다. 사용자가 코드가 필요하지 않은 경우 ...

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

이는 사용자가 세미콜론이없는 경우 컴파일러가 오류를 생성 할 것으로 예상 함을 의미합니다.

그러나 진짜 좋은 이유는 때때로 매크로 작성자가 매크로를 실제 함수 (아마도 인라인)로 대체해야하기 때문입니다. 따라서 매크로는 실제로 하나처럼 작동 해야 합니다.

따라서 세미콜론이 필요한 매크로가 있어야합니다.

유효한 코드 생성

jfm3의 답변에서 볼 수 있듯이 때로는 매크로에 둘 이상의 명령이 포함됩니다. 매크로가 if 문 내에서 사용되면 문제가됩니다.

if(bIsOk)
   MY_MACRO(42) ;

이 매크로는 다음과 같이 확장 할 수 있습니다.

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

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

g값에 관계없이 함수가 실행됩니다 bIsOk.

즉, 매크로에 범위를 추가해야합니다.

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

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

유효한 코드 생성 2

매크로가 다음과 같은 경우 :

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

다음 코드에서 또 다른 문제가있을 수 있습니다.

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

다음과 같이 확장되기 때문입니다.

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

물론이 코드는 컴파일되지 않습니다. 따라서 솔루션은 범위를 사용합니다.

#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) ;
}

코드가 다시 올바르게 작동합니다.

세미콜론 + 범위 효과를 결합 하시겠습니까?

이 효과를 생성하는 C / C ++ 관용구가 하나 있습니다. do / while 루프 :

do
{
    // code
}
while(false) ;

do / while은 범위를 만들 수 있으므로 매크로의 코드를 캡슐화하고 끝에 세미콜론이 필요하므로 필요한 코드로 확장됩니다.

보너스?

The C++ compiler will optimize away the do/while loop, as the fact its post-condition is false is known at compile time. This means that a macro like:

#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.
}

will expand correctly as

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.
}

and is then compiled and optimized away as

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

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

   // Etc.
}

@jfm3 - You have a nice answer to the question. You might also want to add that the macro idiom also prevents the possibly more dangerous (because there's no error) unintended behavior with simple 'if' statements:

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

if (test) FOO( baz);

expands to:

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

which is syntactically correct so there's no compiler error, but has the probably unintended consequence that g() will always be called.


The above answers explain the meaning of these constructs, but there is a significant difference between the two that was not mentioned. In fact, there is a reason to prefer the do ... while to the if ... else construct.

The problem of the if ... else construct is that it does not force you to put the semicolon. Like in this code:

FOO(1)
printf("abc");

Although we left out the semicolon (by mistake), the code will expand to

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

and will silently compile (although some compilers may issue a warning for unreachable code). But the printf statement will never be executed.

do ... while construct does not have such problem, since the only valid token after the while(0) is a semicolon.


While it is expected that compilers optimize away the do { ... } while(false); loops, there is another solution which would not require that construct. The solution is to use the comma operator:

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

or even more exotically:

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

While this will work well with separate instructions, it will not work with cases where variables are constructed and used as part of the #define :

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

With this one would be forced to use the do/while construct.


Jens Gustedt's P99 preprocessor library (yes, the fact that such a thing exists blew my mind too!) improves on the if(1) { ... } else construct in a small but significant way by defining the following:

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

The rationale for this is that, unlike the do { ... } while(0) construct, break and continue still work inside the given block, but the ((void)0) creates a syntax error if the semicolon is omitted after the macro call, which would otherwise skip the next block. (There isn't actually a "dangling else" problem here, since the else binds to the nearest if, which is the one in the macro.)

If you are interested in the sorts of things that can be done more-or-less safely with the C preprocessor, check out that library.


For some reasons I can't comment on the first answer...

Some of you showed macros with local variables, but nobody mentioned that you can't just use any name in a macro! It will bite the user some day! Why? Because the input arguments are substituted into your macro template. And in your macro examples you've use the probably most commonly used variabled name i.

For example when the following macro

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

is used in the following function

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

the macro will not use the intended variable i, that is declared at the beginning of some_func, but the local variable, that is declared in the do ... while loop of the macro.

Thus, never use common variable names in a macro!


Explanation

do {} while (0) and if (1) {} else are to make sure that the macro is expanded to only 1 instruction. Otherwise:

if (something)
  FOO(X); 

would expand to:

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

And g(X) would be executed outside the if control statement. This is avoided when using do {} while (0) and if (1) {} else.


Better alternative

With a GNU statement expression (not a part of standard C), you have a better way than do {} while (0) and if (1) {} else to solve this, by simply using ({}):

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

And this syntax is compatible with return values (note that do {} while (0) isn't), as in:

return FOO("X");

I don't think it was mentioned so consider this

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

would be translated into

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

notice how i++ is evaluated twice by the macro. This can lead to some interesting errors.


I have found this trick very helpful is in situations where you have to sequentially process a particular value. At each level of processing, if some error or invalid condition occurs, you can avoid further processing and break out early. e.g.

#define CALL_AND_RETURN(x)  if ( x() == false) break;
do {
     CALL_AND_RETURN(process_first);
     CALL_AND_RETURN(process_second);
     CALL_AND_RETURN(process_third);
     //(simply add other calls here)
} while (0);

참고URL : https://stackoverflow.com/questions/154136/why-use-apparently-meaningless-do-while-and-if-else-statements-in-macros

반응형