[c] C에서 디버그 인쇄를위한 #define 매크로?

다음 의사 코드와 같이 DEBUG가 정의 될 때 인쇄 디버그 메시지에 사용할 수있는 매크로를 작성하려고합니다.

#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)

매크로로 어떻게이 작업을 수행 할 수 있습니까?



답변

C99 이상의 컴파일러를 사용하는 경우

#define debug_print(fmt, ...) \
            do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)

C99를 사용한다고 가정합니다 (이전 버전에서는 변수 인수 목록 표기법이 지원되지 않음). 그만큼do { ... } while (0)숙어 보장하는 코드는 문 (함수 호출)와 같은 역할을 것을. 무조건 코드를 사용하면 컴파일러가 항상 디버그 코드가 유효한지 확인하지만 DEBUG가 0 일 때 옵티마이 저가 코드를 제거합니다.

#ifdef DEBUG로 작업하려면 테스트 조건을 변경하십시오.

#ifdef DEBUG
#define DEBUG_TEST 1
#else
#define DEBUG_TEST 0
#endif

그런 다음 DEBUG를 사용한 DEBUG_TEST를 사용하십시오.

형식 문자열에 대해 문자열 리터럴을 고집하는 경우 (아마도 좋은 생각 일 것입니다) __FILE__, __LINE____func__출력을 소개 하여 진단을 향상시킬 수 있습니다.

#define debug_print(fmt, ...) \
        do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
                                __LINE__, __func__, __VA_ARGS__); } while (0)

이것은 문자열 연결에 의존하여 프로그래머가 쓰는 것보다 더 큰 형식의 문자열을 만듭니다.

C89 컴파일러를 사용하는 경우

C89가 붙어 있고 유용한 컴파일러 확장이 없다면 특별히 처리 할 수있는 방법이 없습니다. 내가 사용한 기술은 다음과 같습니다.

#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)

그런 다음 코드에서 다음을 작성하십시오.

TRACE(("message %d\n", var));

이중 괄호는 중요하며 매크로 확장에서 재미있는 표기법을 사용하는 이유입니다. 이전과 마찬가지로 컴파일러는 항상 코드의 구문 유효성 (좋은)을 검사하지만 DEBUG 매크로가 0이 아닌 것으로 평가되는 경우 옵티마이 저는 인쇄 기능 만 호출합니다.

여기에는 ‘stderr’과 같은 것을 처리하기위한 지원 함수 (예 : dbg_printf ())가 필요합니다. varargs 함수를 작성하는 방법을 알아야하지만 어렵지 않습니다.

#include <stdarg.h>
#include <stdio.h>

void dbg_printf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
}

물론 C99에서도이 기술을 사용할 수 있지만 __VA_ARGS__이중 괄호 해킹이 아닌 일반 함수 표기법을 사용하므로이 기술이 더 깔끔합니다.

컴파일러가 항상 디버그 코드를 보는 것이 중요한 이유는 무엇입니까?

[ 다른 답변에 대한 해시 코멘트. ]

위의 C99 및 C89 구현의 기본 개념 중 하나는 컴파일러에서 항상 printf와 같은 디버깅 문을 보는 것입니다. 이것은 장기 코드 (10 년 또는 2 년 지속되는 코드)에 중요합니다.

코드 조각이 수년 동안 대부분 휴면 상태 (안정적) 였지만 이제는 변경해야한다고 가정합니다. 디버깅 추적을 다시 활성화하십시오. 그러나 안정적인 유지 관리 기간 동안 이름이 바뀌거나 다시 입력 된 변수를 참조하기 때문에 디버깅 (추적) 코드를 디버깅해야하는 것은 실망 스럽습니다. 컴파일러 (사전 프로세서)가 항상 print 문을 보게되면 주변의 모든 변경 사항이 진단을 무효화하지 않았는지 확인합니다. 컴파일러에서 인쇄 문이 보이지 않으면 자신의 부주의 (또는 동료 또는 공동 작업자의 부주의)로부터 보호 할 수 없습니다. ‘참조 프로그래밍의 연습을 커니 핸과 파이크, 특히 제 8 장에 의해'(에도 위키 백과를 참조 TPOP ).

이것은 ‘그것이 거기에 있었고, 그 경험이 있습니다.’경험-필자는 비 디버그 빌드가 몇 년 동안 (10 년 이상) printf와 같은 문장을 보지 못하는 다른 답변에 설명 된 기술을 사용했습니다. 그러나 나는 TPOP (내 이전 의견 참조)에 대한 조언을 듣고 몇 년 후에 디버깅 코드를 활성화했으며 변경된 컨텍스트가 디버깅을 깨뜨리는 문제가 발생했습니다. 여러 번 인쇄를 항상 검증하면 나중에 문제를 피할 수있었습니다.

NDEBUG를 사용하여 어설 션 만 제어하고 별도의 매크로 (일반적으로 DEBUG)를 사용하여 디버그 추적이 프로그램에 내장되어 있는지 여부를 제어합니다. 디버그 추적이 내장되어 있어도 디버그 출력이 무조건 표시되는 것을 원하지 않기 때문에 출력의 표시 여부 (디버그 레벨 및 호출 대신)를 제어하는 ​​메커니즘이 있습니다.fprintf() 직접 조건부로만 인쇄하는 디버그 인쇄 기능을 호출 함). 동일한 코드 빌드가 프로그램 옵션에 따라 인쇄되거나 인쇄되지 않을 수 있습니다). 또한 더 큰 프로그램을위한 코드의 ‘다중 서브 시스템’버전을 가지고 있으므로 런타임 제어 하에서 다른 양의 추적을 생성하는 프로그램의 다른 섹션을 가질 수 있습니다.

모든 빌드에서 컴파일러가 진단 문을보아야한다고 주장합니다. 그러나 디버그가 사용 가능하지 않으면 컴파일러는 디버깅 추적 명령문에 대한 코드를 생성하지 않습니다. 기본적으로 이는 컴파일 할 때마다 릴리스 또는 디버깅 여부에 관계없이 컴파일러가 모든 코드를 검사 함을 의미합니다. 이것은 좋은 일입니다!

debug.h-버전 1.2 (1990-05-01)

/*
@(#)File:            $RCSfile: debug.h,v $
@(#)Version:         $Revision: 1.2 $
@(#)Last changed:    $Date: 1990/05/01 12:55:39 $
@(#)Purpose:         Definitions for the debugging system
@(#)Author:          J Leffler
*/

#ifndef DEBUG_H
#define DEBUG_H

/* -- Macro Definitions */

#ifdef DEBUG
#define TRACE(x)    db_print x
#else
#define TRACE(x)
#endif /* DEBUG */

/* -- Declarations */

#ifdef DEBUG
extern  int     debug;
#endif

#endif  /* DEBUG_H */

debug.h-버전 3.6 (2008-02-11)

/*
@(#)File:           $RCSfile: debug.h,v $
@(#)Version:        $Revision: 3.6 $
@(#)Last changed:   $Date: 2008/02/11 06:46:37 $
@(#)Purpose:        Definitions for the debugging system
@(#)Author:         J Leffler
@(#)Copyright:      (C) JLSS 1990-93,1997-99,2003,2005,2008
@(#)Product:        :PRODUCT:
*/

#ifndef DEBUG_H
#define DEBUG_H

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

/*
** Usage:  TRACE((level, fmt, ...))
** "level" is the debugging level which must be operational for the output
** to appear. "fmt" is a printf format string. "..." is whatever extra
** arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
** -- See chapter 8 of 'The Practice of Programming', by Kernighan and Pike.
*/
#ifdef DEBUG
#define TRACE(x)    db_print x
#else
#define TRACE(x)    do { if (0) db_print x; } while (0)
#endif /* DEBUG */

#ifndef lint
#ifdef DEBUG
/* This string can't be made extern - multiple definition in general */
static const char jlss_id_debug_enabled[] = "@(#)*** DEBUG ***";
#endif /* DEBUG */
#ifdef MAIN_PROGRAM
const char jlss_id_debug_h[] = "@(#)$Id: debug.h,v 3.6 2008/02/11 06:46:37 jleffler Exp $";
#endif /* MAIN_PROGRAM */
#endif /* lint */

#include <stdio.h>

extern int      db_getdebug(void);
extern int      db_newindent(void);
extern int      db_oldindent(void);
extern int      db_setdebug(int level);
extern int      db_setindent(int i);
extern void     db_print(int level, const char *fmt,...);
extern void     db_setfilename(const char *fn);
extern void     db_setfileptr(FILE *fp);
extern FILE    *db_getfileptr(void);

/* Semi-private function */
extern const char *db_indent(void);

/**************************************\
** MULTIPLE DEBUGGING SUBSYSTEMS CODE **
\**************************************/

/*
** Usage:  MDTRACE((subsys, level, fmt, ...))
** "subsys" is the debugging system to which this statement belongs.
** The significance of the subsystems is determined by the programmer,
** except that the functions such as db_print refer to subsystem 0.
** "level" is the debugging level which must be operational for the
** output to appear. "fmt" is a printf format string. "..." is
** whatever extra arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
*/
#ifdef DEBUG
#define MDTRACE(x)  db_mdprint x
#else
#define MDTRACE(x)  do { if (0) db_mdprint x; } while (0)
#endif /* DEBUG */

extern int      db_mdgetdebug(int subsys);
extern int      db_mdparsearg(char *arg);
extern int      db_mdsetdebug(int subsys, int level);
extern void     db_mdprint(int subsys, int level, const char *fmt,...);
extern void     db_mdsubsysnames(char const * const *names);

#endif /* DEBUG_H */

C99 이상의 단일 인수 변형

카일 브란트가 물었다.

어쨌든이 작업을 수행하면 debug_print인수가 없더라도 여전히 작동합니까? 예를 들면 다음과 같습니다.

    debug_print("Foo");

간단하고 구식 인 해킹이 있습니다.

debug_print("%s\n", "Foo");

아래에 표시된 GCC 전용 솔루션도이를 지원합니다.

그러나 다음을 사용하여 straight C99 시스템으로 수행 할 수 있습니다.

#define debug_print(...) \
            do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)

첫 번째 버전과 비교할 때 ‘fmt’인수가 필요한 제한된 검사를 잃어 버렸습니다. 즉, 인수없이 ‘debug_print ()’를 호출하려고 시도 할 수 있습니다 (그러나 인수 목록의 마지막 쉼표는 fprintf()컴파일에 실패합니다) . 점검 손실이 문제인지 여부는 논란의 여지가 있습니다.

단일 인수에 대한 GCC 관련 기술

일부 컴파일러는 매크로에서 가변 길이 인수 목록을 처리하는 다른 방법에 대한 확장을 제공 할 수 있습니다. 특히 Hugo Ideler 의 의견에서 처음 언급 한 것처럼 GCC를 사용하면 매크로에 대한 마지막 ‘고정 된’인수 뒤에 일반적으로 표시되는 쉼표를 생략 할 수 있습니다. 또한 ##__VA_ARGS__매크로 대체 텍스트에서 사용할 수 있습니다 . 이는 이전 토큰이 쉼표 인 경우에만 표기법 앞에 쉼표를 삭제합니다.

#define debug_print(fmt, ...) \
            do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)

이 솔루션은 형식 뒤에 옵션 인수를 허용하면서 형식 인수를 요구하는 이점을 유지합니다.

이 기술은 CCC 호환성을 위해 Clang 에서도 지원됩니다 .


왜 do-while 루프인가?

do while여기 의 목적은 무엇입니까 ?

매크로를 사용하여 함수 호출처럼 보이게하려면 세미콜론이옵니다. 따라서 매크로 본문을 맞게 패키지해야합니다. if주변없이 진술 을 사용하면 다음 do { ... } while (0)이 가능합니다.

/* BAD - BAD - BAD */
#define debug_print(...) \
            if (DEBUG) fprintf(stderr, __VA_ARGS__)

이제 다음과 같이 작성한다고 가정하십시오.

if (x > y)
    debug_print("x (%d) > y (%d)\n", x, y);
else
    do_something_useful(x, y);

불행하게도, 전처리 기는 이것과 동등한 코드를 생성하기 때문에 (들여 져서 실제 의미를 강조하기 위해 들여 쓰기와 괄호가 추가되어 있기 때문에) 들여 쓰기는 흐름의 실제 제어를 반영하지 않습니다.

if (x > y)
{
    if (DEBUG)
        fprintf(stderr, "x (%d) > y (%d)\n", x, y);
    else
        do_something_useful(x, y);
}

매크로에서 다음 시도는 다음과 같습니다.

/* BAD - BAD - BAD */
#define debug_print(...) \
            if (DEBUG) { fprintf(stderr, __VA_ARGS__); }

그리고 동일한 코드 조각은 이제 다음을 생성합니다.

if (x > y)
    if (DEBUG)
    {
        fprintf(stderr, "x (%d) > y (%d)\n", x, y);
    }
; // Null statement from semi-colon after macro
else
    do_something_useful(x, y);

그리고 else지금은 구문 오류입니다. do { ... } while(0)루프 피합니다 모두 이러한 문제.

작동하는 매크로를 작성하는 다른 방법이 있습니다.

/* BAD - BAD - BAD */
#define debug_print(...) \
            ((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))

이렇게하면 프로그램 조각이 유효한 것으로 남습니다. (void)캐스트는 값이 요구되는 상황에서 이용하지 못하도록 – 있지만 여기서 콤마 연산자의 왼쪽 피연산자로서 사용될 수 do { ... } while (0)버전 수 없다. 이러한 표현식에 디버그 코드를 포함시킬 수 있어야한다고 생각하면이 코드를 선호 할 수 있습니다. 디버그 인쇄가 전체 명령문으로 작동하도록하려면 do { ... } while (0)버전이 더 좋습니다. 매크로 본문에 세미콜론이 포함 된 경우 (대략 말하면) do { ... } while(0)표기법 만 사용할 수 있습니다 . 항상 작동합니다. 식 문 메커니즘을 적용하기가 더 어려울 수 있습니다. 컴파일러가 피하기 원하는 표현 형식으로 경고를받을 수도 있습니다. 컴파일러와 사용하는 플래그에 따라 다릅니다.



TPOP는 이전에 http://plan9.bell-labs.com/cm/cs/tpophttp://cm.bell-labs.com/cm/cs/tpop에 있었지만 현재는 둘 다 (2015-08-10) 부서진.


GitHub의 코드

있는 거 호기심 당신이, 당신이 내에서 GitHub의 코드를 볼 수 있습니다 SOQ 파일로 (스택 오버플로 질문) 저장소 debug.c, debug.h그리고 mddebug.c
SRC / libsoq
하위 디렉토리.


답변

나는 이와 같은 것을 사용한다 :

#ifdef DEBUG
 #define D if(1)
#else
 #define D if(0)
#endif

D를 접두사로 사용하는 것보다 :

D printf("x=%0.3f\n",x);

컴파일러는 디버그 코드를보고 쉼표 문제가 없으며 모든 곳에서 작동합니다. 또한 printf배열이 덤프되거나 프로그램 자체에 중복되는 진단 값을 계산해야 할 때 충분하지 않은 경우에도 작동 합니다.

편집 : 좋아, else근처에 어딘가에이 주입으로 가로 챌 수 있는 문제가 발생할 수 있습니다 if. 이것은 그것을 넘어서는 버전입니다.

#ifdef DEBUG
 #define D
#else
 #define D for(;0;)
#endif


답변

이식 가능한 (ISO C90) 구현의 경우 다음과 같이 이중 괄호를 사용할 수 있습니다.

#include <stdio.h>
#include <stdarg.h>

#ifndef NDEBUG
#  define debug_print(msg) stderr_printf msg
#else
#  define debug_print(msg) (void)0
#endif

void
stderr_printf(const char *fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  vfprintf(stderr, fmt, ap);
  va_end(ap);
}

int
main(int argc, char *argv[])
{
  debug_print(("argv[0] is %s, argc is %d\n", argv[0], argc));
  return 0;
}

또는 (hackish, 권장하지 않음)

#include <stdio.h>

#define _ ,
#ifndef NDEBUG
#  define debug_print(msg) fprintf(stderr, msg)
#else
#  define debug_print(msg) (void)0
#endif

int
main(int argc, char *argv[])
{
  debug_print("argv[0] is %s, argc is %d"_ argv[0] _ argc);
  return 0;
}


답변

내가 사용하는 버전은 다음과 같습니다.

#ifdef NDEBUG
#define Dprintf(FORMAT, ...) ((void)0)
#define Dputs(MSG) ((void)0)
#else
#define Dprintf(FORMAT, ...) \
    fprintf(stderr, "%s() in %s, line %i: " FORMAT "\n", \
        __func__, __FILE__, __LINE__, __VA_ARGS__)
#define Dputs(MSG) Dprintf("%s", MSG)
#endif


답변

나는 같은 것을 할 것입니다

#ifdef DEBUG
#define debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define debug_print(fmt, ...) do {} while (0)
#endif

나는 이것이 더 깨끗하다고 ​​생각합니다.


답변

http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html 에 따르면 ##이전 버전 이 있어야합니다 __VA_ARGS__.

그렇지 않으면, 매크로 #define dbg_print(format, ...) printf(format, __VA_ARGS__)는 다음 예제를 컴파일하지 않습니다 : dbg_print("hello world");.


답변

#define debug_print(FMT, ARGS...) do { \
    if (DEBUG) \
        fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
    } while (0)