program story

C ++의 DEBUG 매크로

inputbox 2021. 1. 11. 08:08
반응형

C ++의 DEBUG 매크로


C에서 정말 좋아하는 DEBUG 매크로를 만났습니다.

#ifdef DEBUG_BUILD
#  define DEBUG(x) fprintf(stderr, x)
#else
#  define DEBUG(x) do {} while (0)
#endif

C ++ 아날로그가 다음과 같을 것이라고 생각합니다.

#ifdef DEBUG_BUILD
#  define DEBUG(x) cerr << x
#else
#  define DEBUG(x) do {} while (0)
#endif
  1. 두 번째 코드 조각이 C의 코드 조각과 유사합니까?
  2. 좋아하는 C ++ 디버그 매크로가 있습니까?

편집 : "디버그 매크로"로 "디버그 모드에서 프로그램을 실행하는 동안 유용 할 수있는 매크로"를 의미합니다.


두 번째 코드 조각이 C의 코드 조각과 유사합니까?

다소간. <<인수에-분리 된 값을 포함 할 수 있기 때문에 더 강력 합니다. 따라서 단일 인수를 사용하면 C에서 다양한 수의 매크로 인수가 필요한 무언가를 얻을 수 있습니다. 반면에 사람들이 남용 할 가능성은 거의 없습니다. 인수에 세미콜론을 포함하면됩니다. 또는 호출 후 세미콜론을 잊어 버려서 실수를 범하기도합니다. 그래서 나는 이것을 do 블록에 포함시킬 것입니다.

#define DEBUG(x) do { std::cerr << x; } while (0)

좋아하는 C ++ 디버그 매크로가 있습니까?

나는 위의 것을 좋아하고 꽤 자주 사용합니다. 내 no-op은 보통 읽습니다.

#define DEBUG(x)

컴파일러 최적화에도 동일한 효과가 있습니다. 아래 @Tony D의 주석은 정확하지만 일부 구문 오류가 감지되지 않을 수 있습니다.

때로는 런타임 검사도 포함하여 어떤 형태의 디버그 플래그를 제공합니다. @Tony D가 나에게 상기 시켰 듯이 endl을 넣는 것도 종종 유용합니다.

#define DEBUG(x) do { \
  if (debugging_enabled) { std::cerr << x << std::endl; } \
} while (0)

때로는 식을 인쇄하고 싶습니다.

#define DEBUG2(x) do { std::cerr << #x << ": " << x << std::endl; } while (0)

일부 매크로, 난 포함 할 __FILE__, __LINE__또는 __func__, 그러나 이들은 더 자주 주장하지 간단한 디버그 매크로입니다.


내가 제일 좋아하는 것

#ifdef DEBUG 
#define D(x) x
#else 
#define D(x)
#endif

매우 편리하고 깔끔한 (중요하게는 릴리스 모드에서 빠르다 !!) 코드를 만듭니다.

#ifdef DEBUG_BUILD(디버그 관련 코드 블록을 필터링하기 위해) 사방에있는 많은 블록은 매우 추악하지만 D().

사용하는 방법:

D(cerr << "oopsie";)

그래도 너무 못 생기거나 이상하거나 길다면

#ifdef DEBUG
#define DEBUG_STDERR(x) (std::cerr << (x))
#define DEBUG_STDOUT(x) (std::cout << (x))
//... etc
#else 
#define DEBUG_STDERR(x)
#define DEBUG_STDOUT(x)
//... etc
#endif

(나는 제안 사용하지 않는 using namespace std;하지만 어쩌면 using std::cout; using std::cerr;좋은 아이디어가 될 수있다)

"디버깅"에 대해 생각할 때 stderr로 인쇄하는 보다 더 많은 작업 을 수행 할 수 있습니다 . 창의력을 발휘하면 프로그램 내에서 가장 복잡한 상호 작용에 대한 통찰력을 제공하는 구조를 구축 할 수 있으며, 디버그 계측으로 방해받지 않는 매우 효율적인 버전을 구축하는 것으로 매우 빠르게 전환 할 수 있습니다.

예를 들어, 최근 프로젝트 중 하나에서 시작하여 내 데이터 구조 내에서 큰 나무를 시각화하기 위해 graphviz 호환 그래프를 도트 형식으로 FILE* file = fopen("debug_graph.dot");덤프 하는 거대한 디버그 전용 블록이 있었습니다. 더 멋진 점은 OS X graphviz 클라이언트가 파일이 변경 될 때 디스크에서 파일을 자동으로 읽으므로 프로그램이 실행될 때마다 그래프가 새로 고쳐진다는 것입니다!

특히 디버그 전용 멤버와 함수를 사용하여 클래스 / 구조체를 "확장"하는 것을 좋아합니다. 이는 버그를 추적하는 데 도움이되는 기능과 상태를 구현할 수있는 가능성을 열어 주며, 디버그 매크로에 래핑 된 다른 모든 것과 마찬가지로 빌드 매개 변수를 전환하여 제거됩니다. 모든 상태 업데이트에서 각 코너 케이스를 열심히 확인하는 거대한 루틴? 문제가 아니다. D()주위를 치고 . 작동하는 것을 확인한 후에 -DDEBUG는 빌드 스크립트에서 제거하십시오 . 즉, 릴리스를 위해 빌드하십시오. 그러면 단위 테스트 또는 당신이 무엇을 가지고 있는지 잠시 고지하여 다시 활성화 할 준비가 된 것입니다.

이 개념의 사용을 설명하기위한 크고 다소 완전한 예 :

#ifdef DEBUG
#  define D(x) x
#else
#  define D(x)
#endif // DEBUG

#ifdef UNITTEST
#  include <UnitTest++/UnitTest++.h>
#  define U(x) x // same concept as D(x) macro.
#  define N(x)
#else
#  define U(x)
#  define N(x) x // N(x) macro performs the opposite of U(x)
#endif

struct Component; // fwd decls
typedef std::list<Component> compList;

// represents a node in the graph. Components group GNs
// into manageable chunks (which turn into matrices which is why we want
// graph component partitioning: to minimize matrix size)
struct GraphNode {
    U(Component* comp;) // this guy only exists in unit test build
    std::vector<int> adj; // neighbor list: These are indices
    // into the node_list buffer (used to be GN*)
    uint64_t h_i; // heap index value
    U(int helper;) // dangling variable for search algo to use (comp node idx)
    // todo: use a more space-efficient neighbor container?
    U(GraphNode(uint64_t i, Component* c, int first_edge):)
    N(GraphNode(uint64_t i, int first_edge):)
        h_i(i) {
        U(comp = c;)
        U(helper = -1;)
        adj.push_back(first_edge);
    }
    U(GraphNode(uint64_t i, Component* c):)
    N(GraphNode(uint64_t i):)
        h_i(i)
    {
        U(comp=c;)
        U(helper=-1;)
    }
    inline void add(int n) {
        adj.push_back(n);
    }
};

// A component is a ugraph component which represents a set of rows that
// can potentially be assembled into one wall.
struct Component {
#ifdef UNITTEST // is an actual real struct only when testing
    int one_node; // any node! idx in node_list (used to be GN*)
    Component* actual_component;
    compList::iterator graph_components_iterator_for_myself; // must be init'd
    // actual component refers to how merging causes a tree of comps to be
    // made. This allows the determination of which component a particular
    // given node belongs to a log-time operation rather than a linear one.

    D(int count;) // how many nodes I (should) have

    Component(): one_node(-1), actual_component(NULL) {
        D(count = 0;)
    }
#endif
};

#ifdef DEBUG
// a global pointer to the node list that makes it a little
// easier to reference it
std::vector<GraphNode> *node_list_ptr;

#  ifdef UNITTEST
std::ostream& operator<<(std::ostream& os, const Component& c) {
    os << "<s=" << c.count << ": 1_n=" << node_list_ptr->at(c.one_node).h_i;
    if (c.actual_component) {
        os << " ref=[" << *c.actual_component << "]";
    }
    os << ">";
    return os;
}
#  endif
#endif

#ifdef큰 블록의 경우 매우 짧은 매크로를 사용하는 것이 더 방해가되므로 가독성이 다소 향상되기 때문에 저는 일반 블록 조건문을 사용합니다.

N(x)매크로가 있어야하는 이유는 단위 테스트가 비활성화 되었을 때 추가 할 항목 을 지정하기 위한 것입니다 .

이 부분에서 :

U(GraphNode(uint64_t i, Component* c, int first_edge):)
N(GraphNode(uint64_t i, int first_edge):)

우리가 다음과 같이 말할 수 있다면 좋을 것입니다.

GraphNode(uint64_t i, U(Component* c,) int first_edge):

그러나 쉼표는 전 처리기 구문의 일부이기 때문에 불가능합니다. 쉼표를 생략하면 잘못된 C ++ 구문이 생성됩니다.

디버그 용으로 컴파일 하지 않을 때 추가 코드가있는 경우이 유형의 해당 역 디버그 매크로를 사용할 수 있습니다.

이제이 코드는 "정말 좋은 코드"의 예가 아닐 수 있지만 매크로를 현명하게 적용하여 수행 할 수있는 몇 가지 작업을 보여줍니다. 매크로를 잘 활용하면 반드시 악한 것은 아닙니다 .

나는 물건 에 대해 궁금해 한 직후이 보석do{} while(0) 을 발견 했으며이 매크로에서도 모든 환상을 원합니다!

제 예제가 C ++ 코드를 개선하기 위해 수행 할 수있는 영리한 작업에 대한 통찰력을 제공 할 수 있기를 바랍니다. 무슨 일이 일어나고 있는지 이해할 수 없을 때 다시 돌아 오는 것보다 작성하는 동안 코드를 계측하는 것이 정말 중요합니다. 그러나 견고하게 만드는 것과 제 시간에 완료하는 것 사이에는 항상 균형을 유지해야합니다.

추가 디버그 빌드 온 전성 검사를 단위 테스트와 유사한 도구 상자의 다른 도구로 생각하고 싶습니다. 제 생각에는 건전성 검사 논리를 단위 테스트에 넣고 구현에서 격리하는 것보다 구현에 포함되어 있고 마음대로 사용할 수 있다면 완전한 테스트가 필요하지 않기 때문에 훨씬 더 강력 할 수 있습니다. 단순히 검사를 활성화하고 평소처럼 핀치로 작업을 실행할 수 있기 때문입니다.


질문 1] 대답은 '예'입니다. 표준 오류 스트림에 메시지를 인쇄합니다.

질문 2] 많이 있습니다. 내 Fav는

#define LOG_ERR(...) fprintf(stderr, __VA_ARGS__)

디버그 메시지에 포함 할 임의의 수의 변수를 포함 할 수 있습니다.


함께 사용 매크로에 같은 나는 __LINE__, __FILE__인수 보여 같은 코드에서 인쇄물에서 - 여러 장소에서 같은 변수 이름을 인쇄하는 드문 일이 아니에요, 너무 fprintf(stderr, "x=%d", x);많은 의미하지 않습니다 당신이 다음 또 다른 하나 같은 10 줄을 추가하는 경우 하위.

또한 특정 함수를 재정의하고 호출 된 위치 (예 : 메모리 할당)를 저장하는 매크로를 사용하여 나중에 어떤 것이 유출되었는지 파악할 수 있습니다. 메모리 할당의 경우 C ++에서는 new / delete를 사용하는 경향이 있고 쉽게 교체 할 수 없기 때문에 약간 더 어렵지만 잠금 / 잠금 해제 작업과 같은 다른 리소스는 이러한 방식으로 추적하는 데 매우 유용 할 수 있습니다 [물론, 좋은 C ++ 프로그래머처럼 구성 / 파괴를 사용하는 잠금 래퍼가있는 경우 생성자에 추가하여 잠금을 획득 한 후 내부 구조에 파일 / 줄을 추가하고 언제 어디서나 보관 된 위치를 볼 수 있습니다. 어딘가에서 구할 수 없습니다].


이것은 현재 사용중인 로그 매크로입니다.

#ifndef DEBUG 
#define DEBUG 1 // set debug mode
#endif

#if DEBUG
#define log(...) {\
    char str[100];\
    sprintf(str, __VA_ARGS__);\
    std::cout << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << str << std::endl;\
    }
#else
#define log(...)
#endif

용법:

log(">>> test...");

산출:

xxxx/proj.ios_mac/Classes/IntroScene.cpp][gotoNextScene][Line 58] >>> test...

… 그리고 모든 응답에 대한 부록 :

Personally I never use macros like DEBUG to distinct debug from release code, instead I use NDEBUG which is must be defined for release builds to eliminate assert() calls (yes, I use assert() extensively). And if latter is not defined, then it is a debug build. Easy! So, actually there is no reason to introduce one more debug macro! (and handle possible cases when DEBUG and NDEBUG both are not defined).


This is my version, using a variadic template print function:

template<typename... ArgTypes>
inline void print(ArgTypes... args)
{
  // trick to expand variadic argument pack without recursion
  using expand_variadic_pack = int[];
  // first zero is to prevent empty braced-init-list
  // void() is to prevent overloaded operator, messing things up
  // trick is to use the side effect of list-initializer to call a function
  // on every argument.
  // (void) is to suppress "statement has no effect" warnings
  (void)expand_variadic_pack{0, ((cout << args), void(), 0)... };
}

#ifndef MYDEBUG
#define debug_print(...)
#else
#define debug_print(...) print(__VA_ARGS__)
#endif

The version I makes the debug_print a variadic template function which accepts a debug level which allows me to select what kind of output I want to output at runtime:

template<typename... ArgTypes>
inline void debug_print(debug::debug level, ArgTypes... args)
{
  if(0 != (debug::level & level))
    print(args...);
}

Note the print function crashes Visual Studio 2013 Preview (I haven't tested the RC). I have noticed it is faster (on Windows, where console output is slow) than my previous solution which used an ostream child class that overloaded operator<<.

You can also use a temporary stringstream inside print if you only want to call the real output function once (or write your own typesafe printf ;-))


I use the code below for logging. There are a few advantages:

  1. I can turn them on/off at runtime.
  2. I can compile out statements at a particular log level. For example, at the moment, I've unconditionally compiled in the KIMI_PRIVATE macro because I'm debugging something in the release build but since there is a lot of potentially secret sauce stuff being logged (lol), I compile it out of release builds.

This pattern has served me very well over the years. Note: although there is a global logMessage function, the code usually queues the log to a logging thread.

#define KIMI_LOG_INTERNAL(level,EXPR)           \
  if(kimi::Logger::loggingEnabled(level))       \
  {                                             \
    std::ostringstream os;                      \
    os << EXPR;                                 \
    kimi::Logger::logMessage(level ,os.str());  \
  }                                             \
  else (void) 0

#define KIMI_LOG(THELEVEL,EXPR)                 \
  KIMI_LOG_INTERNAL(kimi::Logger::LEVEL_ ## THELEVEL,EXPR)

#define KIMI_ERROR(EXPR)   KIMI_LOG(ERROR,EXPR)
#define KIMI_VERBOSE(EXPR) KIMI_LOG(VERBOSE,EXPR)
#define KIMI_TRACE(EXPR)   KIMI_LOG(TRACE,EXPR)
#define KIMI_INFO(EXPR)    KIMI_LOG(INFO,EXPR)
#define KIMI_PROFILE(EXPR) KIMI_LOG(TRACE,EXPR)

// Use KIMI_PRIVATE for sensitive tracing
//#if defined(_DEBUG)
#  define KIMI_PRIVATE(EXPR) KIMI_LOG(PRIVATE,EXPR)
// #else
// #  define KIMI_PRIVATE(EXPR) (void)0
// #endif

I use following micro,

#if DEBUG
#define LOGE2(x,y) std::cout << "ERRO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y <<std::endl;
#define LOGI2(x,y) std::cout << "INFO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y << std::endl;
#define LOGD2(x,y) std::cout << "DEBG : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y << std::endl;
#define LOGE(x) std::cout << "ERRO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#define LOGI(x) std::cout << "INFO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#define LOGD(x) std::cout << "DEBG : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#else
#define LOGE2(x,y) NULL
#define LOGI2(x,y) NULL
#define LOGD2(x,y) NULL
#define LOGE(x) NULL
#define LOGI(x) NULL
#define LOGD(x) NULL
#endif

USE:

LOGE("ERROR.");
LOGE2("ERROR1","ERROR2");

ReferenceURL : https://stackoverflow.com/questions/14251038/debug-macros-in-c

반응형