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


787

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

2
else가있는 예제의 void경우 끝에 ((void) 0) 과 같은 형식 표현식을 추가합니다 .
Phil1970

1
do while구문은 return 문과 호환되지 않으므로 if (1) { ... } else ((void)0)표준 C에서 구문이 더 호환되는 사용법을 고려해야합니다. GNU C에서는 내 대답에 설명 된 구문을 선호합니다.
Cœur December

답변:


829

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위의 코드를 다음과 같이 확장하여 구문 적으로 정확합니다.

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그것이 "로 연결 매달려 다른 다음 코드와 같이 기존 매달려 다른 문제가 훨씬 더 힘들어 찾을 수 있도록 수있는" .

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

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

요약하면, do ... whileC 전 처리기의 단점을 해결할 수 있습니다. C 스타일 가이드가 C 프리 프로세서를 설치하라고 지시하면 이것이 걱정되는 것입니다.


18
if, while 및 for 문에서 항상 중괄호를 사용하는 것이 강력한 주장이 아닙니까? 예를 들어 MISRA-C에 필요한 것처럼 항상 이렇게하는 경우 위에서 설명한 문제는 사라집니다.
Steve Melnikoff 2009

17
쉼표 예제는 #define BAR(X) (f(X), g(X))그렇지 않으면 연산자 우선 순위가 의미를 엉망으로 만들 수 있습니다.
스튜어트

20
@DawidFerenczy : 비록 당신과 나로부터 4 년 반 동안 좋은 지적이기는하지만, 우리는 현실 세계에 살아야합니다. if코드의 모든 문장 등이 중괄호를 사용 한다고 보장 할 수 없다면 , 이와 같이 매크로를 래핑하는 것은 문제를 피하는 간단한 방법입니다.
Steve Melnikoff

8
참고 : if(1) {...} else void(0)형식은 do {...} while(0)매크로 확장에 포함 된 매개 변수가 코드 인 매크로의 경우보다 안전 합니다. 이는 break 또는 continue 키워드의 동작을 변경하지 않기 때문입니다. 예를 들어 , 매크로 호출 사이트에서 for 루프가 아니라 매크로의 while 루프에 영향을 미치기 때문에 정의 된 for (int i = 0; i < max; ++i) { MYMACRO( SomeFunc(i)==true, {break;} ) }경우 예기치 않은 동작이 발생 합니다. MYMACRO#define MYMACRO(X, CODE) do { if (X) { cout << #X << endl; {CODE}; } } while (0)
크리스 클라인

5
@ace void(0)는 오타였습니다 (void)0. 그리고 나는이 생각 않는 세미콜론이 후가 없습니다주의 사항 : "다른 매달려"문제를 해결 (void)0. 이 경우에 매달려 있으면 (예 if (cond) if (1) foo() else (void)0 else { /* dangling else body */ }:) 컴파일 오류가 발생합니다. 다음은 이를 보여주는 실제 예입니다.
Chris Kline

153

매크로는 전처리 기가 진짜 코드에 넣을 복사 / 붙여 넣기 텍스트입니다. 매크로의 저자는 교체가 유효한 코드를 생성하기를 희망합니다.

성공하려면 세 가지 좋은 "팁"이 있습니다.

매크로가 진짜 코드처럼 동작하도록 도와주세요

보통 코드는 일반적으로 세미콜론으로 끝납니다. 사용자가 코드를 필요로하지 않으면 ...

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은 범위를 생성하여 매크로의 코드를 캡슐화하고 끝에 세미콜론이 필요하므로 필요한 코드로 확장됩니다.

보너스?

C ++ 컴파일러는 사후 조건이 거짓이라는 사실이 컴파일 시간에 알려져 있기 때문에 do / while 루프를 최적화합니다. 이것은 다음과 같은 매크로를 의미합니다.

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

6
인라인 함수로 매크로를 변경하면 미리 정의 된 일부 표준 매크로가 변경됩니다. 예를 들어 다음 코드는 FUNCTIONLINE 의 변경을 보여줍니다 . #include <stdio.h> #define Fmacro () printf ( "% s % d \ n", FUNCTION , LINE ) 인라인 void Finline () {printf ( "% s % d \ n", FUNCTION , LINE ); } int main () {Fmacro (); 핀 라인 (); 리턴 0; } (굵은 글씨는 이중 밑줄로 묶어야합니다 — 잘못된 포맷터입니다!)
Gnubie

6
이 답변에는 몇 가지 사소하지만 완전히 중요하지 않은 문제가 있습니다. 예를 들어 : void doSomething() { int i = 25 ; { int i = x + 1 ; f(i) ; } ; // was MY_MACRO(32) ; }올바른 확장이 아닙니다. x확장에은 (32) 더 복잡한 문제의 확장입니다 무엇을해야합니다 MY_MACRO(i+7). 또 다른 하나는의 확장입니다 MY_MACRO(0x07 << 6). 좋은 것이 많이 있지만 점이 찍히지 않은 점과 교차하지 않은 점이 있습니다.
Jonathan Leffler

@Gnubie : 나는 여전히 여기에 있고 지금까지 이것을 이해하지 못했다 : 백 슬래시가있는 주석에서 별표와 밑줄을 피할 수 있으므로 입력 \_\_LINE\_\_하면 __LINE__로 렌더링됩니다. IMHO, 코드에 코드 형식을 사용하는 것이 좋습니다. 예를 들어, __LINE__특별한 처리가 필요하지 않습니다. 추신 : 2012 년에 이것이 사실인지 모르겠습니다. 그 이후로 엔진을 상당히 개선했습니다.
Scott

1
내 의견이 6 년 늦었지만, 대부분의 C 컴파일러는 실제로 inline표준에 의해 허용되는 함수를 인라인하지 않습니다
Andrew

53

@ jfm3-당신은 질문에 대한 좋은 답변이 있습니다. 또한 매크로 관용구는 간단한 'if'문으로 의도하지 않은 동작 (오류가 없기 때문에)이 더 위험하지 않도록 방지 할 수 있습니다.

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

if (test) FOO( baz);

로 확장 :

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

문법적으로 정확하므로 컴파일러 오류는 없지만 g ()이 항상 호출되는 의도하지 않은 결과가 발생합니다.


22
"아마도 의도하지 않은"? 나는 "의도하지 않은 의도"라고 말했을 것이다. 그렇지 않으면 프로그래머가 꺼내 져 총을 쏘아 야한다.
Lawrence Dol

4
또는 3 글자로 된 대행사에서 일하면서 널리 사용되는 오픈 소스 프로그램에 해당 코드를 삽입하는 경우 인상을 줄 수도 있습니다 ... :-)
R .. GitHub STOP HELPING ICE

2
그리고이 의견은 Apple OS에서 발견 된 최근 SSL 인증서 확인 버그에서 goto fail line을 상기시켜줍니다.
Gerard Sexton

23

위의 답변은 이러한 구성의 의미를 설명하지만 언급되지 않은 두 구성 사이에는 큰 차이가 있습니다. 사실,을 선호하는 이유가 do ... while받는 if ... else구조.

if ... else구문 의 문제 는 세미콜론을 넣 도록 강요 하지 않는다는 것입니다. 이 코드에서와 같이 :

FOO(1)
printf("abc");

실수로 세미콜론을 생략했지만 코드는

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

자동 컴파일됩니다 (일부 컴파일러는 도달 할 수없는 코드에 대한 경고를 표시 할 수 있음). 그러나 그 printf진술은 결코 실행되지 않을 것입니다.

do ... while구문 뒤에는 유일하게 유효한 토큰 while(0)이 세미콜론 이므로 구문에는 이러한 문제가 없습니다 .


2
@RichardHansen : 매크로 호출을 살펴보면 명령문 또는 표현식으로 확장되는지 알 수 없기 때문에 여전히 좋지 않습니다. 누군가가 나중에 가정한다면, 그녀 FOO(1),x++;는 다시 우리에게 거짓 긍정을 줄 것이라고 쓸 수 있습니다 . 그냥 사용 do ... while하면됩니다.
Yakov Galka

1
오해를 피하기 위해 매크로를 문서화하면 충분합니다. 나는 do ... while (0)그것이 바람직 하다는 것에 동의 하지만, 하나의 단점이 있습니다 : A break또는 매크로 호출을 포함하는 루프가 아닌 루프 continue를 제어합니다 do ... while (0). 따라서 if트릭은 여전히 ​​가치가 있습니다.
Richard Hansen

2
매크로 의사 루프 내부에서 볼 수 break있는 a 또는 a continue를 어디에 넣을 수 있는지 알 수 없습니다 do {...} while(0). 매크로 매개 변수에서도 구문 오류가 발생합니다.
Patrick Schlüter

5
구성 do { ... } while(0)대신 사용해야 하는 또 다른 이유 if whatever는 그 관용적 특성 때문입니다. 이 do {...} while(0)구조는 널리 퍼져 있으며 잘 알려져 있으며 많은 프로그래머들이 많이 사용합니다. 그 근거와 문서는 쉽게 알려져 있습니다. 구성에는 그렇지 않습니다 if. 따라서 코드 검토를 수행하는 데 드는 노력이 줄어 듭니다.
Patrick Schlüter

1
@tristopia : 사람들이 코드 블록을 인수로 사용하는 매크로를 작성하는 것을 보았습니다 (필수 권장하지는 않음). 예를 들면 다음과 같습니다 #define CHECK(call, onerr) if (0 != (call)) { onerr } else (void)0.. 그것은처럼 사용할 수 CHECK(system("foo"), break;);가 어디 break;를 둘러싸는 루프를 의미한다 CHECK()호출을.
Richard Hansen

16

컴파일러가 do { ... } while(false);루프를 최적화 할 것으로 예상되지만 해당 구성을 필요로하지 않는 다른 솔루션이 있습니다. 해결책은 쉼표 연산자를 사용하는 것입니다.

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

또는 더 이국적으로 :

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

이것은 별도의 명령으로 잘 작동하지만 변수가 다음의 일부로 구성되고 사용되는 경우에는 작동하지 않습니다 #define.

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

이것으로 do / while 구문을 사용해야합니다.


덕분에 쉼표 연산자는 실행 순서를 보장하지 않으므로이 중첩은이를 강제하는 방법입니다.
Marius

15
@ 마리우스 : 거짓; 콤마 연산자 시퀀스 점이다하여 수행 보장 실행 순서. 함수 인수 목록에서 쉼표와 혼동 한 것 같습니다.
R .. GitHub 중지 지원 얼음

2
두 번째 이국적인 제안은 내 하루를 만들었습니다.
Spidey 2016 년

컴파일러가 프로그램에서 관찰 할 수있는 동작을 유지하도록 강요하고 있기 때문에 수행 / 중단 최적화는 큰 문제가되지 않습니다 (컴파일러 최적화가 정확하다고 가정).
Marco A.

@MarcoA. 당신이 올바른 동안, 과거에는 컴파일러 최적화가 코드의 기능을 정확하게 유지하면서도 단수의 맥락에서 아무것도하지 않는 것처럼 보이는 줄을 바꾸면 멀티 스레드 알고리즘을 깨뜨릴 수 있음을 발견했습니다. 적절한 사례입니다 Peterson's Algorithm.
Marius

11

Jens Gustedt의 P99 전 처리기 라이브러리 (예, 그런 것이 존재한다는 사실도 저의 마음을 날려 버렸습니다!) if(1) { ... } else는 다음을 정의하여 작지만 중요한 방식으로 구문을 향상시킵니다 .

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

이에 대한 이론적 근거는 달리, 그 인 do { ... } while(0)구조, breakcontinue여전히 지정된 블록 내부 작동되지만는 ((void)0)세미콜론 달리 다음 블록을 스킵 할 매크로 호출 이후 생략되면 구문 오류를 생성한다. ( 매크로에서 else가장 가까운에 바인드가 바인딩 되기 때문에 실제로 "dangling else"문제는 없습니다 if.)

C 전처리기를 사용하여보다 안전하게 수행 할 수있는 작업에 관심이 있다면 해당 라이브러리를 확인하십시오.


매우 영리하지만 이로 인해 잠재적으로 매달린 잠재적 인 컴파일러 경고가 발생합니다.
세그먼트

1
일반적으로 매크로를 사용하여 포함 된 환경을 만듭니다. 즉, 매크로 내부에서 break(또는 continue)를 사용하여 외부에서 시작 / 종료 된 루프를 제어 하지 않습니다 . 이는 잘못된 스타일 일뿐 아니라 잠재적 종료점을 숨 깁니다.
mirabilos

Boost에는 전 처리기 라이브러리도 있습니다. 그것에 대해 생각이 무엇입니까?
르네

위험 else ((void)0)은 누군가가 글을 쓰고 YOUR_MACRO(), f();있고 구문 상 유효하지만 결코 전화하지 않는 것 f()입니다. 로 do while그 구문 오류입니다.
melpomene 2016 년

@melpomene 그래서 else do; while (0)어때?
Carl Lei

8

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

여러분 중 일부는 지역 변수가있는 매크로를 보여 주었지만 아무도 매크로에 이름을 사용할 수 있다고 언급 한 사람은 없습니다! 언젠가는 사용자를 물 것입니다! 왜? 입력 인수가 매크로 템플리트로 대체되기 때문입니다. 그리고 매크로 예제에서 가장 일반적으로 사용되는 변수 이름 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 ... while 루프에 선언 된 로컬 변수를 사용합니다.

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


일반적인 패턴은 매크로에서 변수 이름에 밑줄을 추가하는 것입니다 (예 : 매크로) int __i;.
Blaisorblade

8
@Blaisorblade : 사실 그것은 부정확하고 불법적 인 C입니다. 선행 밑줄은 구현에서 사용하도록 예약되어 있습니다. 이 "일반적인 패턴"을 본 이유는 시스템 헤더 ( "구현")를 읽어서 예약 된 네임 스페이스로 제한해야하기 때문입니다. 응용 프로그램 / 라이브러리의 경우 밑줄없이 (예 : mylib_internal___i이와 유사한) 모호하지 않은 고유 한 이름을 선택해야합니다 .
R .. GitHub 중지 지원 얼음

2
@R .. 당신이 옳다-나는 실제로 리눅스 커널 인``애플리케이션 ''에서 이것을 읽었지만 표준 라이브러리 (기술적으로는``자유로운 ''C 구현을 사용하지 않기 때문에 어쨌든 예외이다) ``호스트 된 ''것).
Blaisorblade

3
@R .. 이것은 정확하지 않습니다 : 모든 밑줄에서 대문자와 두 번째 밑줄뒤 따르는 부분 은 구현을 위해 예약되어 있습니다. 밑줄과 그 뒤에 다른 항목은 지역 범위에서 예약되지 않습니다.
Leushenko

@Leushenko : 그렇습니다. 그러나 구별이 충분히 미묘해서 사람들에게 그런 이름을 전혀 쓰지 말라고 말하는 것이 가장 좋습니다. 미묘함을 이해하는 사람들은 아마도 내가 세부 사항을 고집하고 있다는 것을 이미 알고 있습니다. :-)
R .. GitHub의 STOP 돕기 ICE

7

설명

do {} while (0)그리고 if (1) {} else매크로는 한 명령으로 확장되어 있는지 확인한다. 그렇지 않으면:

if (something)
  FOO(X); 

다음과 같이 확장됩니다.

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

그리고 제어문 g(X)밖에서 실행될 것 if입니다. do {} while (0)및을 사용할 때는 사용하지 마십시오 if (1) {} else.


더 나은 대안

는 GNU와 함께 문 식 (하지 표준 C의 일부), 당신은 더 좋은 방법보다는이 do {} while (0)if (1) {} else간단하게 사용하여,이 문제를 해결하기를 ({}):

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

그리고이 구문은 다음과 같이 반환 값과 호환됩니다 ( do {} while (0)그렇지 않습니다).

return FOO("X");

3
매크로에서 블록 클램핑 {}을 사용하면 매크로 코드를 번들링하기에 충분하므로 모든 조건이 동일한 if 조건 경로에 대해 실행됩니다. do-while around는 매크로가 사용되는 곳에서 세미콜론을 적용하는 데 사용됩니다. 따라서 매크로는 더 많은 기능을 수행하도록 강제됩니다. 여기에는 사용되는 후미 세미콜론에 대한 요구 사항이 포함됩니다.
Alexander Stohr

2

나는 그것이 언급되었다고 생각하지 않으므로 이것을 고려하십시오

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

로 번역 될 것이다

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

i++매크로에 의해 두 번 평가되는 방법 에 주목 하십시오. 이로 인해 몇 가지 흥미로운 오류가 발생할 수 있습니다.


15
이것은 do ... while (0) 구문과 관련이 없습니다.
Trent

2
진실. 그러나 매크로 대 함수의 주제와 함수로서 동작하는 매크로를 작성하는 방법과 관련하여 ...
John Nilsson

2
위의 것과 유사하게 이것은 대답이 아니라 의견입니다. 주제 : 그 이유는 한 번만 물건을 사용하는 이유입니다.do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)
mirabilos
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.