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)
编辑:通过"调试宏"我的意思是"在调试模式下运行程序时可能派上用场的宏".
MvG*_*MvG 43
或多或少.它更强大,因为你可以<<在参数中包含-separated值,所以使用单个参数可以获得需要在C中使用可变数量的宏参数的东西.另一方面,人们滥用的可能性很小它通过在参数中包含分号.甚至在电话会议后由于忘记分号而导致错误.所以我将其包含在do块中:
#define DEBUG(x) do { std::cerr << x; } while (0)
Run Code Online (Sandbox Code Playgroud)
我喜欢上面那个,经常使用它.我的无操作通常只是阅读
#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__,但这些通常是断言而不是简单的调试宏.
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++代码.在编写代码时对代码进行编码非常有价值,而不是在您不了解正在发生的情况时再回过头来执行代码.但是,在使其稳健并按时完成之间,必须始终保持平衡.
我喜欢将其他调试构建健全性检查视为工具箱中的不同工具,类似于单元测试.在我看来,它们可能更强大,因为不是将你的理智检查逻辑放在单元测试中并将它们与实现隔离,如果它们包含在实现中并且可以随意变换,那么完整的测试就不是必要的了.因为你可以简单地启用检查并像往常一样运行.
问题1]答案是肯定的.它只会将消息打印到标准错误流.
问题2]有很多.我的收藏是
#define LOG_ERR(...) fprintf(stderr, __VA_ARGS__)
这将允许一个包含任意数量的变量以包含在调试消息中.
我喜欢用用宏__LINE__,__FILE__作为参数,以显示其中的代码打印输出是-它的情况并不少见在几个地方进行打印相同的变量名,那么fprintf(stderr, "x=%d", x);将没有多大的意义,如果你再添加另外一个相同的十行进一步下.
我还使用了覆盖某些函数的宏并存储它的调用位置(例如内存分配),以便稍后,我可以找出泄漏的那个.对于内存分配,这在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
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)
…以及所有答复的附录:
我个人从来没有使用宏象DEBUG以不同的调试从发布代码,而不是我用NDEBUG它必须定义为发布版本,以消除assert()电话(是的,我使用assert()广泛)。如果未定义后者,则它是调试版本。简单!因此,实际上没有理由再引入一个调试宏!(和手柄可能情况下,当DEBUG和NDEBUG两者都没有定义)。