如何实现一个“万能”的调试打印函数
原文链接:http://www.juzicode.com/cpp-note-debug-print-code-model
在阅读第三方库或者大型工程的源码时,只是走读源码通常很难获得详细的代码运行过程,这时候我们可能就希望通过在源码中增加些输出信息再编译运行,从输出上就可以很方便地看到函数的调用关系,特别是包含很多if、for等流程控制语句时,通过插入打印信息可以观察到程序运行到哪个分支。
下面是一个简化处理的例子,通过在被调用函数内部的入口和出口分别输入2个cout语句,这样在main中就能方便的观察到程序的调用过程:
//juzicode.com/vx:桔子code
#include "iostream"
int add(int a, int b)
{
std::cout << "add() enter-----" <<std::endl;
int ret = a + b;
/* ......
*/
std::cout << "add() -----QUIT" << std::endl;
return ret;
}
int sub(int a, int b)
{
std::cout << "sub() enter-----" << std::endl;
int ret = a - b;
/* ......
*/
std::cout << "sub() -----QUIT" << std::endl;
return ret;
}
int main(void)
{
int sum = add(10, 60);
int diff = sub(100, 50);
return 0;
}
运行结果:
add() enter-----
add() -----QUIT
sub() enter-----
sub() -----QUIT
如果还希望观察到函数的入参的值是多少、流程控制语句的跳转情况,我们就需要再增加些语句:
int add(int a, int b)
{
std::cout << "add() enter-----" <<std::endl;
std::cout << "a=" << a << std::endl; //继续增加
std::cout << "b=" << b << std::endl;
int ret = a + b;
/* ......
*/
std::cout << "add() -----QUIT" << std::endl;
return ret;
}
随着要观察的信息增多,需要输入越来越多的cout语句,对于桔子菌这种强迫症来说一个”<“符号需要同时按下shift和”<“键都觉得有点麻烦,有没有更简便的方法呢?
当然有了,就是用函数做封装:
//juzicode.com/vx:桔子code
#include "iostream"
#include "string"
void dbgprt(std::string str)
{
std::cout << str << std::endl;
}
void dbgprt(std::string str, int a)
{
std::cout << str << " " << a << std::endl;
}
int add(int a, int b)
{
dbgprt("add() enter-----");
dbgprt("a=", a);
dbgprt("b=", b);
int ret = a + b;
/* ......
*/
dbgprt("add() -----quit");
return ret;
}
运行结果:
add() enter-----
a= 10
b= 60
add() -----quit
sub() enter-----
sub() -----quit
看起来效果还不错,但是这里只定义了2种类型的dbgprt()函数,如果要是再有其他的float,double等类型入参的函数又得要重新增加一个入参是float或者double型的dbgprt()函数。
这里插播一句,虽然可以像下面这样直接调用dbgprt()函数不会出现编译错误:
double add(double a, double b)
{
dbgprt("add() enter-----");
dbgprt("a=", a);
dbgprt("b=", b);
double ret = a + b;
/* ......
*/
dbgprt("add() -----quit");
return ret;
}
int main(void)
{
double sum2 = add(10.1, 60.1);
return 0;
}
但是运行结果显示,double型的入参a和b被转换成了整型的10和60了,而不是真实的10.1和60.1:
add() enter-----
a= 10
b= 60
add() -----quit
要解决这个问题,需要定义一个能将2个double型的变量显示出来的dbgprt()函数,但是随着dbgprt()入参个数的增加,假设要显示的入参种类有m种,就会出现n个入参有n*m种组合的情况,一个劲地重载n*m个dbgprt()函数头都要秃了。
其实我们可以使用函数模板的方法,下面就是将原来的2个dbgprt()函数改造后,不再需要指定函数的入参类型,交给编译器在编译的时候去自动推导参数类型选择合适的函数调用,这样处理后n个参数就只需要定义n个函数了。
template <typename T1>
void dbgprt(T1 v1)
{
std::cout << v1 << std::endl;
}
template <typename T1, typename T2>
void dbgprt(T1 v1, T2 v2)
{
std::cout << v1 << " "<< v2 << std::endl;
}
完整的例子:
//juzicode.com/vx:桔子code
#include "iostream"
#include "string"
template <typename T1>
void dbgprt(T1 v1)
{
std::cout << v1 << std::endl;
}
template <typename T1, typename T2>
void dbgprt(T1 v1, T2 v2)
{
std::cout << v1 << " "<< v2 << std::endl;
}
double add(double a, double b)
{
dbgprt("add() enter-----");
dbgprt("a=", a);
dbgprt("b=", b);
double ret = a + b;
/* ......
*/
dbgprt("add() -----quit");
return ret;
}
int add(int a, int b)
{
dbgprt("add() enter-----");
dbgprt("a=", a);
dbgprt("b=", b);
int ret = a + b;
/* ......
*/
dbgprt("add() -----quit");
return ret;
}
int sub(int a, int b)
{
dbgprt("sub() enter-----");
dbgprt("a=", a);
dbgprt("b=", b);
int ret = a - b;
/* ......
*/
dbgprt("sub() -----quit");
return ret;
}
int main(void)
{
int sum = add(10, 60);
double sum2 = add(10.1, 60.1);
int diff = sub(100, 50);
return 0;
}
从下面的运行结果可以看到double类型的数据能正确显示了:
add() enter-----
a= 10
b= 60
add() -----quit
add() enter-----
a= 10.1 //这里可以看到double型的数值能正确显示了
b= 60.1
add() -----quit
sub() enter-----
a= 100
b= 50
sub() -----quit
前面的函数模板还只能适用最多2个参数的情况,如果希望能增加更多个数的入参,还可以像下面这样继续增加更多的函数模板:
//juzicode.com/vx:桔子code
#include "iostream"
#include "string"
template <typename T1>
void dbgprt(T1 v1)
{
std::cout << v1 << std::endl;
}
template <typename T1, typename T2>
void dbgprt(T1 v1, T2 v2)
{
std::cout << v1 << " " << v2 <<std::endl;
}
template <typename T1, typename T2, typename T3>
void dbgprt(T1 v1, T2 v2, T3 v3)
{
std::cout << v1 << " " << v2 << " " << v3 << std::endl;
}
template <typename T1, typename T2, typename T3, typename T4>
void dbgprt(T1 v1, T2 v2, T3 v3, T4 v4)
{
std::cout << v1 << " " << v2 << " " << v3 << " " << v4 << std::endl;
}
template <typename T1, typename T2, typename T3, typename T4, typename T5>
void dbgprt(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5)
{
std::cout << v1 << " " << v2 << " " << v3 << " " << v4 << " " << v5 << std::endl;
}
......
到目前为止,解决了随着参数增加需要大量增加编写dbgprt()的问题。
当打印非常多的时候,满屏都是各种变量名称 ,为了分辨某一个变量是在哪个函数内部,往往还需要上下翻动查找在什么地方进入了哪个函数内部。比如前面的add()和sub()的例子中都有名称为a和b的变量,要分别清楚是在哪个函数内部的a和b的变量还需要往前往后翻看进入的是哪个函数,最好的解决办法是在输出变量名称前将函数名称也输出了,可以像下面这样在dbgprt()的第1个入参输入函数名称:
int add(int a, int b)
{
dbgprt("add", "enter-----"); //在第1个入参加入函数名称
dbgprt("add", " a=", a);
dbgprt("add", " b=", b);
int ret = a + b;
/* ......
*/
dbgprt("add", "-----quit");
return ret;
}
int sub(int a, int b)
{
dbgprt("sub", "enter-----");
dbgprt("sub", " a=", a);
dbgprt("sub", " b=", b);
int ret = a - b;
/* ......
*/
dbgprt("sub", "-----quit");
return ret;
}
运行结果:
add enter-----
add a= 10
add b= 60
add -----quit
sub enter-----
sub a= 100
sub b= 50
sub -----quit
这样看每个变量位于哪个函数内部就一目了然了。
但是,这样真的好吗,当有100个函数要跟踪的时候,你真的会一行一行的敲,而不是偷个懒复制粘贴再修改?修改的过程是不是会很烦人,会不会偶尔改错?更偷懒的办法就是不用每次输入不同的函数名称,而是用一个统一的“变量”?是的,有这么个宏:__FUNCTION__,它在哪个函数内部,就代表了哪个函数的字符串形式的名称:
int add(int a, int b)
{
dbgprt( __FUNCTION__, "enter-----");
dbgprt( __FUNCTION__, " a=", a);
dbgprt( __FUNCTION__, " b=", b);
int ret = a + b;
/* ......
*/
dbgprt( __FUNCTION__, "-----quit");
return ret;
}
int sub(int a, int b)
{
dbgprt( __FUNCTION__, "enter-----");
dbgprt( __FUNCTION__, " a=", a);
dbgprt( __FUNCTION__, " b=", b);
int ret = a - b;
/* ......
*/
dbgprt( __FUNCTION__, "-----quit");
return ret;
}
运行结果:
add enter-----
add a= 10
add b= 60
add -----quit
sub enter-----
sub a= 100
sub b= 50
sub -----quit
其实从上面的代码可以看到,每个dbgprt()函数的第1个入参都是__FUNCTION__,何必要每次都输入呢,是不是想到把它直接挪到dbgprt()函数内部?试过之后是不行的,因为__FUNCTION__所代表的含义和它所在的位置强相关,它在哪个函数内部表示的就是哪个函数的名称,如果都挪到dbgprt()中最后显示的都是“dbgprt”了。
木有办法了吗?不是的,还是有办法的,召唤宏啊,因为宏是先在当前位置展开后再编译的,在哪个函数内部调用就在这个函数内部展开后再编译。可以像下面这样:
#define DBGPRT(...) dbgprt(__FUNCTION__,__VA_ARGS__)
int add(int a, int b)
{
DBGPRT("enter-----");
DBGPRT(" a=", a);
DBGPRT(" b=", b);
int ret = a + b;
/* ......
*/
DBGPRT("-----quit");
return ret;
}
int sub(int a, int b)
{
DBGPRT("enter-----");
DBGPRT(" a=", a);
DBGPRT(" b=", b);
int ret = a - b;
/* ......
*/
DBGPRT("-----quit");
return ret;
}
上面这样就和在函数内部调用dbgprt( __FUNCTION__, “enter—–“)效果一致了。
有时候我们可能在发布程序的时候不想将过多的内容显示出来,这时就需要再增加一个开关PRT_LEVEL,在dbgprt()函数内部根据这个开关做判断,决定是否输出打印:
#define PRT_LEVEL 1
template <typename T1>
void dbgprt(int level,T1 v1)
{
if (level < PRT_LEVEL) return;
std::cout << v1 << std::endl;
}
template <typename T1, typename T2>
void dbgprt(int level, T1 v1, T2 v2)
{
if (level < PRT_LEVEL) return;
std::cout << v1 << " " << v2 << std::endl;
}
template <typename T1, typename T2, typename T3>
void dbgprt(int level, T1 v1, T2 v2, T3 v3)
{
if (level < PRT_LEVEL) return;
std::cout << v1 << " " << v2 << " " << v3 << std::endl;
}
#define DBGPRT(...) dbgprt(1,__FUNCTION__,__VA_ARGS__)
像上面这样调用DBGPRT(…),在发布程序的时候不需要显示过多的打印内容,增大PRT_LEVEL的值就可以关闭输出了。
有了这些内容,再将dbgprt()这些内容封装到头文件中,在其他文件内包含这个头文件就可以愉快地用DBGPRT(…)跟踪代码了。
完整的头文件代码:
//juzicode.com/vx:桔子code
#ifndef __DBGPRT
#define __DBGPRT
#include <iostream>
#include <string>
#define PRT_LEVEL 1
template <typename T1>
void dbgprt(int level,T1 v1)
{
if (level < PRT_LEVEL) return;
std::cout << v1 << std::endl;
}
template <typename T1, typename T2>
void dbgprt(int level, T1 v1, T2 v2)
{
if (level < PRT_LEVEL) return;
std::cout << v1 << " " << v2 << std::endl;
}
template <typename T1, typename T2, typename T3>
void dbgprt(int level, T1 v1, T2 v2, T3 v3)
{
if (level < PRT_LEVEL) return;
std::cout << v1 << " " << v2 << " " << v3 << std::endl;
}
template <typename T1, typename T2, typename T3, typename T4>
void dbgprt(int level, T1 v1, T2 v2, T3 v3, T4 v4)
{
if (level < PRT_LEVEL) return;
std::cout << v1 << " " << v2 << " " << v3 << " " << v4 << std::endl;
}
template <typename T1, typename T2, typename T3, typename T4, typename T5>
void dbgprt(int level, T1 v1, T2 v2, T3 v3, T4 v4, T5 v5)
{
if (level < PRT_LEVEL) return;
std::cout << v1 << " " << v2 << " " << v3 << " " << v4 << " " << v5 << std::endl;
}
template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
void dbgprt(int level, T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6)
{
if (level < PRT_LEVEL) return;
std::cout << v1 << " " << v2 << " " << v3 << " " << v4 << " " << v5 << " " << v6 << std::endl;
}
template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7>
void dbgprt(int level, T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7)
{
if (level < PRT_LEVEL) return;
std::cout << v1 << " " << v2 << " " << v3 << " " << v4 << " " << v5 << " " << v6 << " " << v7 << std::endl;
}
#define DBGPRT(...) dbgprt(1,__FUNCTION__,__VA_ARGS__)
#endif
推荐阅读: