IT이야기

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

cyworld 2022. 7. 5. 22:22
반응형

매크로에서 의미 없는 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 ... elseif 스테이트먼트의 본문이 곱슬 괄호로 둘러싸여 있지 않은 스테이트먼트에서는, 나쁜 서프라이즈를 얻을 수 있습니다.

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

위의 코드는 다음과 같이 확장됩니다.

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

이는 구문적으로 올바르지 않습니다.다른 것은 if와 관련지어지지 않기 때문입니다.괄호 뒤의 세미콜론은 구문적으로 올바르지 않기 때문에 매크로 내에서 괄호로 묶는 것은 도움이 되지 않습니다.

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

문제를 해결하는 방법은 두 가지가 있습니다.첫 번째 방법은 표현식처럼 행동하는 매크로의 능력을 잃지 않고 매크로 내의 스테이트먼트의 시퀀스에 콤마를 사용하는 것입니다.-

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

위의 버전의 막대BARJIN.Ping.1996년 메이EP01.01

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) 그렇지 않은 문제를 발견하기가 더욱 어렵게 만들 수 있습니다.

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

요점은 매달리는 세미콜론이 잘못된 컨텍스트에서 세미콜론을 모두 사용하는 것입니다.물론, 이 시점에서 선언하는 것이 더 낫다고 주장할 수 있다(그리고 아마도 그래야 할 것이다.BAR매크로가 아닌 실제 기능으로 사용할 수 있습니다.

요약하면do ... whileC 프리프로세서의 단점을 회피하기 위한 것입니다.그 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++ 관용어가1개 있어요do/while 루프:

do
{
    // code
}
while(false) ;

do/while은 스코프를 생성하여 매크로의 코드를 캡슐화할 수 있으며, 최종적으로 세미콜론이 필요하므로 세미콜론이 필요한 코드로 확장됩니다.

보너스?

C++ 컴파일러는 do/while 루프를 최적화하여 제거할 것입니다.이는 컴파일 시 포스트 조건이 false라는 사실이 알려지기 때문입니다.즉, 다음과 같은 매크로가 있습니다.

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

올바르게 확장됩니다.

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

컴파일 및 최적화되어 있습니다.

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

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

   // Etc.
}

설명.

do {} while (0)그리고.if (1) {} else매크로가 1개의 명령만으로 확장되도록 합니다.그렇지 않은 경우:

if (something)
  FOO(X); 

다음과 같이 확장됩니다.

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

그리고.g(X)외부에서 실행되다ifcontrol 스테이트먼트이것은, 사용시에 회피됩니다.do {} while (0)그리고.if (1) {} else.


더 나은 대안

GNU 스테이트먼트 표현식(표준 C의 일부가 아님)을 사용하면,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");

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

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.

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))

이 경우 do/while 구성을 사용해야 합니다.

어떤 이유로 첫 번째 답변에 대해 언급할 수 없습니다.

어떤 분들은 지역 변수가 있는 매크로를 보여주셨지만, 매크로에서 아무 이름이나 사용할 수 없다는 건 아무도 언급하지 않았습니다!언젠가 사용자를 공격하게 될 것입니다.왜냐하면 입력 인수가 매크로 템플릿으로 대체되기 때문입니다.매크로 예에서는 가장 일반적으로 사용되는 가변 이름 i를 사용하고 있습니다.

예를 들어 다음 매크로가

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

다음 기능에서 사용됩니다.

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

매크로에서는 some_func의 선두에 선언된 의도된 변수 i가 사용되지 않지만 do에서 선언된 로컬 변수가 사용됩니다.macro의 loop을 실행합니다.

따라서 매크로에서 공통 변수 이름을 사용하지 마십시오.

언급이 안 된 것 같으니 잘 생각해 보세요.

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

로 번역될 것이다.

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

에 주목하다i++는 매크로에 의해 2회 평가됩니다.이로 인해 몇 가지 흥미로운 오류가 발생할 수 있습니다.

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

반응형