C语言中程序调试和宏使用技巧

来源:互联网 发布:数据库第六版pdf网盘 编辑:程序博客网 时间:2024/06/06 03:08

1)打印文件,函数和程序行   

     在linux使用GCC编译程序的时候,gcc在编译的过程中,会生成一些宏,可以使用这些宏分别打印当前源文件的信息。主要有当前文件(__FILE__,为字符串型char *),当前运行的函数(__FUNCTION__,为字符串型char *)和当前的程序行(__LINE__,为整型int)。使用实例如下:

printf("file:%s    function:%s    line:%d\n",__FILE__, __FUNCTON__, __LINE); 注意以上三个宏都是在编译的时候产生的宏,而不是变量。

2)#:字符串化操作符

     在gcc的编译系统中,可以使用#将当前的内容转换成字符串。例如:#define dprint(expr) printf("<main>%s=%d\n", #expr, expr);

int x= 100;

int y= 2;

dprint(x/y);              

结果如下:<main>x/y=50     这说明增加了#修饰之后的表达式,即代表了将宏中的参数名直接转换为字符串

进一步,看下面的调用:

dprint(/* printf variable information */x/y);

结果如下:<main>x/y=50   这说明编译器在生成字符串的时候,不会照搬宏参数中的所有内容,注释的内容是不会被放入字符串的宏,这也是因为去注释是编译器预处理阶段的内容,也就是说实际的编译过程之前,程序中注释已经去掉。

char* a="Test string"

dprint(a)    结果如下:<main>a=4206930   这是由于a不是整数,而是字符串的指针,因此打印出a的值是变量a的地址,而字符串的内容依然是a。再看:

int i=1234;

dprint(1234)     结果如下:<main>1234    这说明对于直接写入程序的数值(立即数),编译器也可以将它的内容转换为字符串。

上面的种种说明,那种方式的优点是可以通过统一的方法打印表达式的内容。同样可以定义如下的类似的宏,打印其它表达式的值:

#define printc(expr) printf("<char>%s = %c\n", #expr,expr);

#define printf(expr)  printf("<float>%s = %f\n", #expr,expr);

#define printd(expr) printf("<int 16>%s = %d\n", #expr,expr);

在gcc的编译器中,可以将两个字符串常量连接成一个新的字符串。如下方式表示的字符串是等价的:

“123456”与“123””456”

由于#expr本质就是一个字符串,因此程序中也可以不使用%s打印它的内容,而是可以将其直接和其它的字符串连接如下:

#define printc(expr) printf("<char>”#expr = "%c\n",expr);    其它的类似。

3)##:连接操作符

在GCC的编译系统中,##是C语言中的连接操作符,可以在编译的预处理阶段实现字符串的连接的操作。如下所示:

#define test(x) test##x
void test1(int a)
{
     printf(”Test1!\n“);   
}
void test2(int a)
{
     printf(”Test2!\n“);   
}

int main(void)
{
    test(1);
    test(2);
    return 0;
}
程序运行结果如下:
Test1
Test2
这说明test(1),这说明经过宏操作,由于##连接操作符,将test(1)变成了test1。

在程序的调试语句中,##常用的方式如下:

#define DPRINT(fmt,args…) printf(fmt, ##args)

4)调试宏的第一种方式如下:

#define DEBUG_OUT(fmt,args…)   \

{ \

     printf("File:%s    Function:%s    Line:%d\n",__FILE__, __FUNCTON__, __LINE__);   \

     printf(fmt, ##args);  \

}

在宏定义中,在每一行的结尾使用\表示下一行的内容是与上面连续的。经过这样处理的好处是可以在每行调试语句的前面打印出附加的信息,经过这样的宏处理后,DEBUG_OUT的方式和printf完全一致,可以支持可变参数列表和格式化输出,只是附加了一段当前文件,当前函数,当前行的信息。调用宏的方法,可以加语句结束的标志(;),这样像是函数调用,其实不是。这时的;表示程序多出来了一条空语句。如果不加的话, 也没错,宏调用本来就不加嘛,是吧?

但存在问题或者存在一些缺陷,实质是printf是一个有int返回值的函数,只是很少用而已。但根据DEBUG_OUT的定义,不可能再产生返回值,在绝大数的实际情况下 由于不用返回值,所以这还是挺常用的。

4)调试宏的第二种方式如下:

#define DEBUG_OUT(fmt,args…)  printf("File:%s    Function:%s    Line:%d"fmt, __FILE__, __FUNCTON__, __LINE__, ##args)

这个就解决了无法获得printf返回值的弊端。但同时却有另外一个弊端。这是由于printf函数的第一个参数是const char *格式,在使用的过程中,经常使用固定字符串表示格式输入参数。其实printf也可以使用变量作为第一个参数输入如下:

const char *s = "String";

printf(s);

以上格式是合法的,输出的结果就是s指向的字符串的值String。然而在上述DEBUG_OUT定义中,fmt必须是一个字符串,不能使用指针,只有这样才能实现字符串连接的功能。因此DEBUG_OUT(s)是不合法的。

5)使用一个变量USE_DEBUG然后用ifdef USE_DEBUG来开关控制调试宏。这样就不用在最终的发行版中为去掉调试宏而苦恼了。当然也可以USE_DEBUG定义一个值,当值不同时,根据实际情况使用不同的调试宏。

6)使用do…while的定义

使用宏定义可以将一些较为短小的功能封装,方便使用。宏的形式和函数类似,但是使用宏的话,就可以节省函数跳转的开销。在程序中常常使用do…while(0)来封装成一个宏。例如:

#define HELLO(str) do{\

printf("Hello:%s\n", str); \

}while(0)

原创粉丝点击