C++中的DEBUG宏

56 c c++ debugging c-preprocessor

我刚刚在C中遇到了一个我非常喜欢的DEBUG宏

#ifdef DEBUG_BUILD
#  define DEBUG(x) fprintf(stderr, x)
#else
#  define DEBUG(x) do {} while (0)
#endif
Run Code Online (Sandbox Code Playgroud)

我猜一个C++模拟会是: -

#ifdef DEBUG_BUILD
#  define DEBUG(x) cerr << x
#else
#  define DEBUG(x) do {} while (0)
#endif
Run Code Online (Sandbox Code Playgroud)
  1. 第二个代码片段是否类似于C中的代码片段?
  2. 你有任何喜欢的C++调试宏吗?

编辑:通过"调试宏"我的意思是"在调试模式下运行程序时可能派上用场的宏".

MvG*_*MvG 43

第二个代码片段是否类似于C中的代码片段?

或多或少.它更强大,因为你可以<<在参数中包含-separated值,所以使用单个参数可以获得需要在C中使用可变数量的宏参数的东西.另一方面,人们滥用的可能性很小它通过在参数中包含分号.甚至在电话会议后由于忘记分号而导致错误.所以我将其包含在do块中:

#define DEBUG(x) do { std::cerr << x; } while (0)
Run Code Online (Sandbox Code Playgroud)

你有任何喜欢的C++调试宏吗?

我喜欢上面那个,经常使用它.我的无操作通常只是阅读

#define DEBUG(x)
Run Code Online (Sandbox Code Playgroud)

这对优化编译器具有相同的效果.虽然下面@Tony D的评论是正确的:这可能会留下一些未检测到的语法错误.

我有时也包括运行时检查,因此提供某种形式的调试标志.正如@Tony D提醒我的那样,拥有一个endl通常也很有用.

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

有时我也想打印表达式:

#define DEBUG2(x) do { std::cerr << #x << ": " << x << std::endl; } while (0)
Run Code Online (Sandbox Code Playgroud)

在某些宏中,我喜欢包含__FILE__,__LINE__或者__func__,但这些通常是断言而不是简单的调试宏.

  • "我的no-op通常只读`#define DEBUG(x)`'...如果DEBUG调用的分号丢失,`do-while(false)`替换往往会产生错误,而不是留下下一个语句滑进DEBUG的地方.例如"if(expr)DEBUG",下一行:"++ i;",你得到"if(expr)++ i;".喜欢做什么的小理由.很多时候把"`<<'\n'`"放入宏观中也是最好的. (8认同)
  • 在`std :: cerr`的情况下,它通常是行缓冲的,所以无论如何都要刷新,使用`std :: endl`是便宜的多余,但是 - 让我感到困扰的是人们在流媒体中使用`std :: endl`可能用于`std :: cout`的操作符,一个文件流等 - 这会严重阻碍缓冲并导致性能大幅下降. (3认同)

Ste*_* Lu 34

这是我的最爱

#ifdef DEBUG 
#define D(x) x
#else 
#define D(x)
#endif
Run Code Online (Sandbox Code Playgroud)

它非常方便,可以实现干净(重要的是,快速发布模式!!)代码.

#ifdef DEBUG_BUILD遍布各处的大量块(过滤掉与调试相关的代码块)非常难看,但是当你用一个包装几行时并不是那么糟糕D().

如何使用:

D(cerr << "oopsie";)
Run Code Online (Sandbox Code Playgroud)

如果你仍然太丑陋/怪异/长,

#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
Run Code Online (Sandbox Code Playgroud)

(我建议不要使用using namespace std;虽然可能using std::cout; using std::cerr;是一个好主意)

请注意,当您考虑"调试"时,您可能希望执行更多操作,而不仅仅是打印到stderr.发挥创意,您​​可以构建能够深入了解程序中最复杂交互的构造,同时允许您快速切换到构建一个不受调试工具影响的超高效版本.

例如,在我最近的一个项目中,我有一个巨大的仅调试块,它开始FILE* file = fopen("debug_graph.dot");并以点格式转储graphviz兼容图,以显示我的数据结构中的大树.甚至更酷的是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
Run Code Online (Sandbox Code Playgroud)

请注意,对于大块代码,我只使用常规块#ifdef条件,因为这有点提高了可读性,因为对于大块,使用极短的宏更是一种障碍!

N(x)宏必须存在的原因是指定禁用单元测试时要添加的内容.

在这部分:

U(GraphNode(uint64_t i, Component* c, int first_edge):)
N(GraphNode(uint64_t i, int first_edge):)
Run Code Online (Sandbox Code Playgroud)

如果我们能说出类似的话会很好

GraphNode(uint64_t i, U(Component* c,) int first_edge):
Run Code Online (Sandbox Code Playgroud)

但我们不能,因为逗号是预处理器语法的一部分.省略逗号会产生无效的C++语法.

如果在没有编译调试时有一些额外的代码,可以使用这种类型的相应反调试宏.

现在这段代码可能不是"非常好的代码"的一个例子,但它说明了你可以通过巧妙地应用宏来完成的一些事情,如果你仍然遵守纪律,那不一定是邪恶的.

在想知道这些东西之后,我刚才遇到了这个宝石do{} while(0),而且你真的希望这些宏中的所有那些好看!

希望我的例子可以提供一些洞察力,至少可以做一些聪明的事情来改进你的C++代码.在编写代码时对代码进行编码非常有价值,而不是在您不了解正在发生的情况时再回过头来执行代码.但是,在使其稳健并按时完成之间,必须始终保持平衡.

我喜欢将其他调试构建健全性检查视为工具箱中的不同工具,类似于单元测试.在我看来,它们可能更强大,因为不是将你的理智检查逻辑放在单元测试中并将它们与实现隔离,如果它们包含在实现中并且可以随意变换,那么完整的测试就不是必要的了.因为你可以简单地启用检查并像往常一样运行.


Neo*_*low 9

问题1]答案是肯定的.它只会将消息打印到标准错误流.

问题2]有很多.我的收藏是

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

这将允许一个包含任意数量的变量以包含在调试消息中.


Mat*_*son 8

我喜欢用用宏__LINE__,__FILE__作为参数,以显示其中的代码打印输出是-它的情况并不少见在几个地方进行打印相同的变量名,那么fprintf(stderr, "x=%d", x);将没有多大的意义,如果你再添加另外一个相同的十行进一步下.

我还使用了覆盖某些函数的宏并存储它的调用位置(例如内存分配),以便稍后,我可以找出泄漏的那个.对于内存分配,这在C++中有点困难,因为你倾向于使用new/delete,并且它们不能轻易被替换,但其他资源(如锁定/解锁操作)对于跟踪这种方式非常有用[当然,如果你有一个像一个优秀的C++程序员一样使用构造/破坏的锁定包装器,你可以将它添加到构造函数中,一旦获得锁定就将文件/行添加到内部结构中,你可以看到它在别处的位置.你不能在某处获得它].


fir*_*oke 6

这是我目前使用的日志宏:

#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
Run Code Online (Sandbox Code Playgroud)

用法:

log(">>> test...");
Run Code Online (Sandbox Code Playgroud)

输出:

xxxx/proj.ios_mac/Classes/IntroScene.cpp][gotoNextScene][Line 58] >>> test...
Run Code Online (Sandbox Code Playgroud)

  • 观察:如图所示,您始终启用调试.您还将输出限制为100个字符(这不是很长),但是使用`snprintf()`而不是`sprintf()`不能确保没有缓冲区溢出.那生活危险,不是吗?您的日志记录不应该转到`std :: cerr`或`std :: clog`吗?与其他人相比,这个答案有什么新的和截然不同的? (3认同)

zau*_*ufi 5

…以及所有答复的附录:

我个人从来没有使用宏象DEBUG以不同的调试从发布代码,而不是我用NDEBUG必须定义为发布版本,以消除assert()电话(是的,我使用assert()广泛)。如果未定义后者,则它是调试版本。简单!因此,实际上没有理由再引入一个调试宏!(和手柄可能情况下,当DEBUGNDEBUG两者都没有定义)。