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


209

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

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

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


컴파일러 (gcc)가 프로덕션 코드에서 DEBUG 매크로가 0으로 설정된 경우 if (DEBUG) {...} out과 같은 명령문을 최적화합니까? 컴파일러가 디버그 문을 볼 수있는 좋은 이유가 있지만 나쁜 느낌이 남아 있음을 이해합니다. -Pat

답변:


410

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.cSRC / libsoq 하위 디렉토리.


1
gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html 의 GCC ##-approach 는 "Single argument C99 variant"라는 제목 아래 언급 할 가치 가 있다고 생각합니다 .
Hugo Ideler 2016 년

2
몇 년 후,이 답변은 printk의 별칭을 지정하는 방법에 대한 모든 인터넷에서 여전히 가장 유용합니다! stdio를 사용할 수 없으므로 vfprintf가 커널 공간에서 작동하지 않습니다. 감사합니다! #define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
kevinf

6
키워드와 예에서 __FILE__, __LINE__, __func__, __VA_ARGS__당신은 즉, 더 printf와 매개 변수를가없는 경우 그냥 호출하는 경우는 컴파일되지 않습니다 debug_print("Some msg\n"); 당신은 사용하여이 문제를 해결할 수 fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__); ## __ VA_ARGS__ 함수에 매개 변수를 전달하지 허용합니다.
mc_electron

1
@LogicTom : 차가 사이 #define debug_print(fmt, ...)#define debug_print(...). 이들 중 첫 번째는 하나 이상의 인수, 형식 문자열 ( fmt) 및 0 개 이상의 다른 인수가 필요합니다. 두 번째는 총 0 개 이상의 인수가 필요합니다. debug_print()첫 번째와 함께 사용 하면 전 처리기에서 매크로 오용에 대한 오류가 발생하지만 두 번째는 그렇지 않습니다. 그러나 대체 텍스트가 유효하지 않기 때문에 여전히 컴파일 오류가 발생합니다. 따라서 실제로 큰 차이는 없습니다. 따라서 '제한된 검사'라는 용어를 사용합니다.
Jonathan Leffler 2016 년

1
위에서 설명한 @ St.Antario 변형은 전체 응용 프로그램에서 단일 활성 디버깅 수준을 사용하며 일반적으로 프로그램 실행시 디버깅 수준을 설정할 수 있도록 명령 줄 옵션을 사용합니다. 또한 여러 개의 서로 다른 하위 시스템을 인식하는 변형이 있으며 각 시스템에는 이름과 자체 디버깅 수준이 주어 지므로 -D input=4,macros=9,rules=2입력 시스템의 디버그 수준을 4로 설정하고 매크로 시스템을 9로 설정하는 데 사용할 수 있습니다 (강렬한 조사 진행 중) ) 및 2에 대한 규칙 시스템. 주제에는 끝없는 변형이 있습니다. 자신에게 맞는 것을 사용하십시오.
Jonathan Leffler

28

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

#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

3
에 관해서는 for(;0;)당신이 뭔가를 쓸 때, 그것은 문제를 생성 할 수 있습니다 D continue;또는 D break;.
ACcreator

1
저를 얻었다; 그러나 사고로 발생할 가능성은 거의 없습니다.
mbq

11

이식 가능한 (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;
}

3
@LB : 전처리기를`생각 '하기 위해 단 하나의 인수가있을뿐 아니라, _를 이후 단계에서 확장시킬 수 있습니다.
Marcin Koziuk

10

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

#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

9

나는 같은 것을 할 것입니다

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

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


테스트 내부에서 매크로를 플래그로 사용한다는 아이디어가 마음에 들지 않습니다. 디버그 인쇄를 항상 확인해야하는 이유를 설명해 주시겠습니까?
LB40

1
@Jonathan : 코드가 디버그 모드에서만 실행되면 왜 디버그가 아닌 모드로 컴파일해야합니까? assert()stdlib에서 동일한 방식으로 작동하며 일반적으로 NDEBUG매크로를 내 디버깅 코드에 재사용 합니다.
Christoph

테스트에서 DEBUG를 사용하여 누군가 제어되지 않은 undef DEBUG를 수행하면 코드가 더 이상 컴파일되지 않습니다. 권리 ?
LB40

4
디버깅을 활성화 한 다음 이름을 바꾸거나 다시 입력 한 변수 등을 참조하기 때문에 디버깅 코드를 디버깅해야하는 것은 실망 스럽습니다. 컴파일러 (사전 프로세서)가 항상 print 문을 보게되면 주변의 모든 변경 사항이 진단을 무효화하지 않았습니다. 컴파일러에서 인쇄 문이 보이지 않으면 자신의 부주의 (또는 동료 또는 공동 작업자의 부주의)로부터 보호 할 수 없습니다. Kernighan 및 Pike의 '프로그래밍 실습'( plan9.bell-labs.com/cm/cs/tpop)을 참조하십시오 .
Jonathan Leffler

1
@Christoph : 글쎄, 일종의 ... 어설 션 만 제어하기 위해 NDEBUG를 사용하고 디버그 추적을 제어하기 위해 별도의 매크로 (일반적으로 DEBUG)를 사용합니다. 디버그 출력이 무조건적으로 표시되는 것을 원하지 않기 때문에 출력이 나타나는지 여부를 제어하는 ​​메커니즘이 있습니다 (디버그 레벨, fprintf ()를 직접 호출하는 대신 조건부로만 인쇄하는 디버그 인쇄 함수를 호출합니다. 코드는 프로그램 옵션에 따라 인쇄하거나 인쇄 할 수 없습니다). 모든 빌드에서 컴파일러가 진단 문을보아야한다고 주장합니다. 그러나 디버그가 활성화되어 있지 않으면 코드가 생성되지 않습니다.
Jonathan Leffler

8

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

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


1
스택 오버플로에 오신 것을 환영합니다. GCC에 사용자가 참조하는 비표준 확장자가있는 것이 맞습니다. 현재 허용되는 답변에는 실제로 제공하는 참조 URL을 포함하여 실제로 언급되어 있습니다.
Jonathan Leffler

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

이 표기법을 지원하는 C 버전은 무엇입니까? 그리고 그것이 효과가 있다면, 그와 같은 모든 인수를 붙여 넣는 토큰은 형식 문자열에 대해 매우 제한된 옵션 세트 만 가지고 있다는 것을 의미합니다.
Jonathan Leffler

@Jonathan : gcc (데비안 4.3.3-13) 4.3.3
eyalm

1
확인-동의 함 : 이것은 오래된 GNU 확장 (GCC 4.4.1 매뉴얼의 섹션 5.17)으로 문서화되어 있습니다. 그러나 GCC에서만 작동한다는 점을 문서화해야합니다. 또는 이러한 의견에서 우리 사이에서 수행했을 수도 있습니다.
Jonathan Leffler

1
내 의도는 args를 사용하는 또 다른 스타일을 보여주고 주로 FUNCTIONLINE
었습니다

2

이것이 내가 사용하는 것입니다 :

#if DBG
#include <stdio.h>
#define DBGPRINT printf
#else
#define DBGPRINT(...) /**/  
#endif

추가 인수 없이도 printf를 올바르게 처리하면 좋은 이점이 있습니다. DBG == 0 인 경우 dumbest 컴파일러조차도 씹을 것이 없으므로 코드가 생성되지 않습니다.


컴파일러가 항상 디버그 코드를 확인하도록하는 것이 좋습니다.
Jonathan Leffler

1

아래에서 내가 가장 좋아하는 것은입니다 var_dump.

var_dump("%d", count);

다음과 같은 출력을 생성합니다.

patch.c:150:main(): count = 0

@ "Jonathan Leffler"에게 감사드립니다. 모두 C89- 행복합니다 :

암호

#define DEBUG 1
#include <stdarg.h>
#include <stdio.h>
void debug_vprintf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
}

/* Call as: (DOUBLE PARENTHESES ARE MANDATORY) */
/* var_debug(("outfd = %d, somefailed = %d\n", outfd, somefailed)); */
#define var_debug(x) do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): ", \
    __FILE__,  __LINE__, __func__); debug_vprintf x; }} while (0)

/* var_dump("%s" variable_name); */
#define var_dump(fmt, var) do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): ", \
    __FILE__,  __LINE__, __func__); debug_vprintf ("%s = " fmt, #var, var); }} while (0)

#define DEBUG_HERE do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): HERE\n", \
    __FILE__,  __LINE__, __func__); }} while (0)

1

따라서 gcc를 사용할 때 나는 다음을 좋아합니다.

#define DBGI(expr) ({int g2rE3=expr; fprintf(stderr, "%s:%d:%s(): ""%s->%i\n", __FILE__,  __LINE__, __func__, #expr, g2rE3); g2rE3;})

코드에 삽입 할 수 있기 때문입니다.

디버그하려고한다고 가정하십시오.

printf("%i\n", (1*2*3*4*5*6));

720

그런 다음 다음과 같이 변경할 수 있습니다.

printf("%i\n", DBGI(1*2*3*4*5*6));

hello.c:86:main(): 1*2*3*4*5*6->720
720

그리고 어떤 표현이 무엇으로 평가되었는지 분석 할 수 있습니다.

이중 평가 문제로부터 보호되지만 gensyms가 없으면 이름 충돌에 노출됩니다.

그러나 그것은 중첩합니다 :

DBGI(printf("%i\n", DBGI(1*2*3*4*5*6)));

hello.c:86:main(): 1*2*3*4*5*6->720
720
hello.c:86:main(): printf("%i\n", DBGI(1*2*3*4*5*6))->4

따라서 g2rE3을 변수 이름으로 사용하지 않는 한 괜찮습니다.

확실히 나는 그것을 (그리고 문자열에 대한 연합 버전과 디버그 레벨에 대한 버전 등) 귀중한 것을 발견했습니다.


1

나는 몇 년 동안 이것을하는 방법을 연구 해 왔으며 마침내 해결책을 제시했습니다. 그러나 이미 다른 솔루션이 있다는 것을 몰랐습니다. 첫째, Leffler의 답변 과는 달리 디버그 인쇄가 항상 컴파일되어야한다는 그의 주장을 보지 못했습니다. 테스트 할 필요가 있고 최적화되지 않을 수있는 경우 필요하지 않은 경우 프로젝트에서 불필요한 코드를 실행하지 않아도됩니다.

매번 컴파일하지 않으면 실제보다 더 나쁘게 들릴 수 있습니다. 때로는 컴파일되지 않는 디버그 프린트로 마무리하지만 프로젝트를 마무리하기 전에 컴파일하고 테스트하는 것은 그리 어렵지 않습니다. 이 시스템을 사용하면 세 가지 레벨의 디버그를 사용하는 경우 디버그 메시지 레벨 3에 놓고 컴파일 오류를 수정하고 다른 코드가 있는지 확인한 후에 코드를 완성하십시오. (물론 디버그 명령문을 컴파일한다고해서 여전히 의도 한대로 작동한다는 보장은 없습니다.)

내 솔루션은 디버그 세부 수준도 제공합니다. 가장 높은 수준으로 설정하면 모두 컴파일됩니다. 최근에 높은 디버그 세부 수준을 사용했다면 모두 당시에 컴파일 할 수있었습니다. 최종 업데이트는 매우 쉬워야합니다. 나는 3 개 이상의 레벨을 필요로하지 않았지만 Jonathan은 9를 사용했다고 말합니다. Leffler와 같은이 방법은 여러 레벨로 확장 될 수 있습니다. 내 방법의 사용법이 더 간단 할 수 있습니다. 코드에서 사용될 때 두 개의 문장 만 필요합니다. 그러나 CLOSE 매크로도 코딩하고 있지만 아무것도하지 않습니다. 파일로 보내는 중일 수 있습니다.

비용에 대비하여 테스트 전에 추가 테스트 단계를 수행하여 배포 전에 컴파일되는지 확인합니다.

  1. 최적화 수준을 높이려면이를 신뢰해야합니다. 최적화 수준이 충분하면 반드시 발생해야합니다.
  2. 또한 테스트 목적으로 최적화 기능을 해제 한 상태에서 릴리스 컴파일을 만들면 아마 그렇지 않을 것입니다. 그리고 디버그 중에는 거의 확실하지 않으므로 런타임에 수십 또는 수백 개의 "if (DEBUG)"문을 실행합니다. 따라서 실행 속도를 늦추고 (저의 주요 이의 제기) 덜 중요하게 실행 파일 또는 dll 크기를 늘리십시오. 따라서 실행 및 컴파일 시간. 그러나 Jonathan은 자신의 방법으로 문장을 전혀 컴파일하지 않을 수 있다고 알려줍니다.

브랜치는 현대 프리 페칭 프로세서에서 실제로 상당히 비쌉니다. 앱이 시간이 중요한 앱이 아닌 경우 큰 문제는 아닙니다. 그러나 성능이 문제라면, 다소 빠른 실행 디버그 코드 (그리고 드문 경우이지만, 언급 된 바와 같이 더 빠른 릴리스)를 선택하는 것을 선호 할만큼 충분히 큰 거래입니다.

그래서 내가 원했던 것은 인쇄하지 않으면 컴파일되지 않지만 그렇지 않으면 컴파일되는 디버그 인쇄 매크로입니다. 또한 디버깅 수준을 원했기 때문에 코드의 성능에 중요한 부분을 인쇄하지 않고 다른 시간에 인쇄하려는 경우 디버그 수준을 설정하고 추가 디버그 인쇄를 시작할 수있었습니다. 인쇄가 컴파일되었는지 여부를 결정하는 디버그 레벨을 구현하는 방법을 발견했습니다. 나는 이런 식으로 그것을 달성했다 :

DebugLog.h :

// FILE: DebugLog.h
// REMARKS: This is a generic pair of files useful for debugging.  It provides three levels of 
// debug logging, currently; in addition to disabling it.  Level 3 is the most information.
// Levels 2 and 1 have progressively more.  Thus, you can write: 
//     DEBUGLOG_LOG(1, "a number=%d", 7);
// and it will be seen if DEBUG is anything other than undefined or zero.  If you write
//     DEBUGLOG_LOG(3, "another number=%d", 15);
// it will only be seen if DEBUG is 3.  When not being displayed, these routines compile
// to NOTHING.  I reject the argument that debug code needs to always be compiled so as to 
// keep it current.  I would rather have a leaner and faster app, and just not be lazy, and 
// maintain debugs as needed.  I don't know if this works with the C preprocessor or not, 
// but the rest of the code is fully C compliant also if it is.

#define DEBUG 1

#ifdef DEBUG
#define DEBUGLOG_INIT(filename) debuglog_init(filename)
#else
#define debuglog_init(...)
#endif

#ifdef DEBUG
#define DEBUGLOG_CLOSE debuglog_close
#else
#define debuglog_close(...)
#endif

#define DEBUGLOG_LOG(level, fmt, ...) DEBUGLOG_LOG ## level (fmt, ##__VA_ARGS__)

#if DEBUG == 0
#define DEBUGLOG_LOG0(...)
#endif

#if DEBUG >= 1
#define DEBUGLOG_LOG1(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG1(...)
#endif

#if DEBUG >= 2
#define DEBUGLOG_LOG2(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG2(...)
#endif

#if DEBUG == 3
#define DEBUGLOG_LOG3(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG3(...)
#endif

void debuglog_init(char *filename);
void debuglog_close(void);
void debuglog_log(char* format, ...);

DebugLog.cpp :

// FILE: DebugLog.h
// REMARKS: This is a generic pair of files useful for debugging.  It provides three levels of 
// debug logging, currently; in addition to disabling it.  See DebugLog.h's remarks for more 
// info.

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

#include "DebugLog.h"

FILE *hndl;
char *savedFilename;

void debuglog_init(char *filename)
{
    savedFilename = filename;
    hndl = fopen(savedFilename, "wt");
    fclose(hndl);
}

void debuglog_close(void)
{
    //fclose(hndl);
}

void debuglog_log(char* format, ...)
{
    hndl = fopen(savedFilename,"at");
    va_list argptr;
    va_start(argptr, format);
    vfprintf(hndl, format, argptr);
    va_end(argptr);
    fputc('\n',hndl);
    fclose(hndl);
}

매크로 사용

사용하려면 다음을 수행하십시오.

DEBUGLOG_INIT("afile.log");

로그 파일에 쓰려면 다음을 수행하십시오.

DEBUGLOG_LOG(1, "the value is: %d", anint);

닫으려면 다음을 수행하십시오.

DEBUGLOG_CLOSE();

현재로서는 기술적으로 말하면 아무것도하지 않기 때문에 필요하지 않습니다. 그러나 여전히 작동 방식에 대한 내 생각이 바뀌고 로깅 문 사이에 파일을 열어 두려는 경우를 대비하여 여전히 CLOSE를 사용하고 있습니다.

그런 다음 디버그 인쇄를 켜려면 헤더 파일에서 첫 번째 #define을 편집하십시오.

#define DEBUG 1

로깅 명령문을 컴파일하지 않으려면

#define DEBUG 0

자주 실행되는 코드 조각 (예 : 높은 수준의 세부 정보)에서 정보가 필요한 경우 다음과 같이 작성할 수 있습니다.

 DEBUGLOG_LOG(3, "the value is: %d", anint);

DEBUG를 3으로 정의하면 로깅 레벨 1, 2 및 3이 컴파일됩니다. 2로 설정하면 로깅 레벨 1 & 2가 표시됩니다. 1로 설정하면 로깅 레벨 1 명령문 만 표시됩니다.

do-while 루프에 대해서는 if 문 대신 단일 함수로 평가되거나 아무것도 계산하지 않기 때문에 루프가 필요하지 않습니다. 좋아, C ++ IO 대신 C를 사용하여 나를 캐스트하십시오 (그리고 Qt의 QString :: arg ()는 Qt에서도 변수를 포맷하는 더 안전한 방법입니다. 꽤 매끄럽지 만 더 많은 코드가 필요하며 서식 문서는 구성되어 있지 않습니다) 그럴 수도 있지만 여전히 바람직한 경우를 찾았지만 원하는 .cpp 파일에 코드를 넣을 수 있습니다. 또한 클래스 일 수도 있지만 인스턴스화하여 최신 상태로 유지하거나 new ()를 수행하여 저장해야합니다. 이런 식으로 #include, init 및 close 문을 소스에 드롭하면 바로 사용할 수 있습니다. 그러나 당신이 그렇게 기울어지면 좋은 수업을 만들 것입니다.

나는 이전에 많은 해결책을 보았지만 이것뿐만 아니라 내 기준에 적합한 것은 없습니다.

  1. 원하는 수준으로 확장 할 수 있습니다.
  2. 인쇄하지 않으면 아무것도 컴파일하지 않습니다.
  3. 편집하기 쉬운 장소에 IO를 집중시킵니다.
  4. printf 형식을 사용하여 유연합니다.
  5. 다시 말해 디버그 실행 속도가 느려지지 않지만 항상 컴파일되는 디버그 인쇄는 항상 디버그 모드에서 실행됩니다. 컴퓨터 과학을하고 있고 정보 처리를 작성하기 쉽지 않은 경우 CPU 소비 시뮬레이터를 실행하여 디버거가 벡터의 범위를 벗어난 인덱스로 중지하는 위치를 확인할 수 있습니다. 이들은 이미 디버그 모드에서 매우 느리게 실행됩니다. 수백 개의 디버그 인쇄를 강제로 실행하면 이러한 실행 속도가 더 느려질 수 있습니다. 나를 위해, 그러한 달리기는 드문 일이 아닙니다.

대단히 중요하지는 않지만 추가로 :

  1. 인수없이 인쇄하려면 해킹이 필요하지 않습니다 (예 :) DEBUGLOG_LOG(3, "got here!");. 따라서 Qt의 안전한 .arg () 형식과 같은 사용을 허용합니다. MSVC에서 작동하므로 아마도 gcc입니다. Leffler가 지적했듯이 비표준 인 s ##에서 사용 #define되지만 널리 지원됩니다. ( ##필요한 경우 사용하지 않도록 코드를 다시 작성할 수 있지만, 제공 한 것과 같은 핵을 사용해야합니다.)

경고 : 로깅 수준 인수를 제공하지 않은 경우 MSVC는 식별자가 정의되어 있지 않다고 주장합니다.

일부 소스에서도 해당 기호를 정의하기 때문에 DEBUG 이외의 전 처리기 기호 이름을 사용할 수도 있습니다 (예 : ./configure빌드 준비 명령을 사용하는 progs ). 내가 그것을 개발했을 때 그것은 자연스럽게 보였다. DLL을 다른 용도로 사용하는 응용 프로그램에서 개발했으며 로그 인쇄를 파일로 보내는 것이 더 수월합니다. 그러나 vprintf ()로 변경하면 잘 작동합니다.

이것이 디버그 로깅을 수행하는 가장 좋은 방법을 찾는 데 많은 슬픔을 덜어주기를 바랍니다. 또는 당신이 선호하는 것을 보여줍니다. 나는 이것을 진심으로 수십 년 동안 알아 내려고 노력했습니다. MSVC 2012 및 2015에서 작동하므로 아마도 gcc에서 작동합니다. 아마도 다른 많은 사람들과 함께 일할 것이지만, 나는 그것들을 테스트하지 않았습니다.

이것도 언젠가는 스트리밍 버전을 만드는 것을 의미합니다.

참고 : Leffler에게 감사의 말을 전합니다. Leffler는 StackOverflow를 위해 내 메시지를 더 잘 형식화하도록 도와주었습니다.


2
" if (DEBUG)런타임에서 수십 또는 수백 개의 문장을 실행하는데, 최적화되지 않습니다"- 풍차에서 기울어지고 있습니다. 필자가 설명한 시스템의 요점은 컴파일러가 코드를 검사하지만 (중요하고 자동-특별한 빌드가 필요하지 않음) 디버그 코드 최적화되어 있기 때문에 전혀 생성되지 않으므로 런타임에 아무런 영향을 미치지 않습니다. 코드가 런타임에 존재하지 않기 때문에 코드 크기 또는 성능).
Jonathan Leffler

Jonathan Leffler : 오해를 지적 해 주셔서 감사합니다. 나는 내 생각이 손가락보다 더 빨리 경주하게해서이 일을 마치게되어 기쁘다. 나는 "... 1)로 내 이의 제기를 수정했습니다. 1) 최적화 수준을 높이려면 신뢰해야합니다. 충분한 최적화 수준이있는 경우에는 반드시 발생해야합니다. 2) 또한 최적화를 사용하여 릴리스 컴파일을 수행하면 그렇지 않습니다 테스트 목적으로 꺼졌으며 디버그 중에 전혀 실행되지 않을 것입니다. 런타임에 수십 또는 수백 개의 'if (DEBUG)'문을 실행하여 실행 파일 또는 dll 크기와 실행 시간을 늘립니다. "
CodeLurker

광산에서 수행하는 다른 중요한 작업을 수행하려면 디버그 수준이 있어야합니다. 나는 많은 것들을 필요로하지 않지만, 몇몇 애플리케이션은 간단한 "#define DEBUG 3"을 사용하여 시간이 중요한 루프에 대한 세부적인 정보를 얻을 수 있다는 이점이 있습니다. "#define DEBUG 1"을 사용하면 정보가 훨씬 적습니다. 필자는 3 개 이상의 레벨을 필요로하지 않았으므로 적어도 약 1/3의 디버그가 이미 릴리스 시점에서 컴파일됩니다. 최근에 레벨 3을 사용했다면 모두 가능할 것입니다.
CodeLurker 18시 09 분

YMMV. 내가 보여준 최신 시스템은 디버그 수준의 동적 (런타임) 설정을 지원하므로 런타임에 생성되는 디버그 양을 프로그래밍 방식으로 결정할 수 있습니다. 나는 일반적으로 레벨 1-9를 사용했지만, 상한은 없습니다 (또는 하한; 기본 레벨은 0이며 일반적으로 꺼져 있지만 적극적 개발 중에는 명시 적으로 요청할 수 있습니다-장기 작업에는 적합하지 않습니다). 나는 기본 레벨 3을 선택했다. 조정될 수 있습니다. 이것은 나에게 많은 통제권을 준다. 비활성 상태에서 디버그 코드를 테스트하지 않으려면 대체 코드를 ((void)0)쉽게 변경하십시오 .
Jonathan Leffler 2018

1
아 전체 내용을 읽는 데 도움이되었을 것입니다. 다소 긴 게시물입니다. 나는 그것이 지금까지 필수 포인트를 가지고 있다고 생각합니다. 내 것과 마찬가지로 모든 디버그 인쇄를 컴파일하거나 컴파일하지 않고 레벨을 지원할 수 있습니다. 물론, 당신은 당신이 사용하지 않는 레벨을 디버깅 할 때 비용으로 컴파일 할 수 있습니다.
CodeLurker

0

테마의 이러한 변형은 카테고리마다 별도의 매크로 이름을 가질 필요없이 디버그 카테고리를 제공한다고 생각합니다.

프로그램 공간이 32K로 제한되고 동적 메모리가 2K로 제한되는 Arduino 프로젝트 에서이 변형을 사용했습니다. 디버그 문과 추적 디버그 문자열을 추가하면 공간이 빨리 소모됩니다. 따라서 컴파일 타임에 포함 된 디버그 추적을 코드가 작성 될 때마다 필요한 최소값으로 제한 할 수 있어야합니다.

debug.h

#ifndef DEBUG_H
#define DEBUG_H

#define PRINT(DEBUG_CATEGORY, VALUE)  do { if (DEBUG_CATEGORY & DEBUG_MASK) Serial.print(VALUE);} while (0);

#endif

.cpp 파일 호출

#define DEBUG_MASK 0x06
#include "Debug.h"

...
PRINT(4, "Time out error,\t");
...
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.