热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

c语言调试输出,C语言中几种输出调试信息的方法

在调试程序时,输出调试信息是一种普遍、有效的方法。输出调试信息一般有以下五种方法:方法一:直接使用屏幕打印函数printf。该方法直接在需

在调试程序时,输出调试信息是一种普遍、有效的方法。输出调试信息一般有以下五种方法:

方法一:直接使用屏幕打印函数printf。

该方法直接在需要输出调试信息的位置使用函数printf输出相应的调试信息&#xff0c;以及某些关键变量的值。我们通过以下求阶层的函数fact来看看该方法的调试程序过程。#include int fact(int n){int i,f&#61;1;for( i&#61;1; i<&#61;n; i&#43;&#43;){f &#43;&#61; i;}return f;}int main(){printf( "4!&#61;%d/n", fact(4) );return 0;}程序1: 有bug的求阶层函数

程序1编译运行的结果如下&#xff1a;4!&#61;11

结果错误。为了找到结果错误的原因&#xff0c;我们在语句"f &#43;&#61; i;"之后插入函数printf输出调试信息&#xff0c;如程序2。#include int fact(int n){int i,f&#61;1;for( i&#61;1; i<&#61;n; i&#43;&#43;){f &#43;&#61; i;printf("i&#61;%d ; f&#61;%d/n", i, f);}return f;}int main(){printf( "4!&#61;%d/n", fact(4) );return 0;}程序2: 加入函数printf输出调试信息的求阶层函数

再编译运行该程序&#xff0c;屏幕输出如下&#xff1a;i&#61;1 ; f&#61;2 i&#61;2 ; f&#61;4 i&#61;3 ; f&#61;7 i&#61;4 ; f&#61;11 4!&#61;11

原来语句"f &#43;&#61; i"错了&#xff0c;应该为"f *&#61;i"。修改过来(见程序3)&#xff0c;再编译运行&#xff0c;结果如下&#xff1a;i&#61;1 ; f&#61;1 i&#61;2 ; f&#61;2 i&#61;3 ; f&#61;6 i&#61;4 ; f&#61;24 4!&#61;24#include

int fact(int n)

{

int i,f&#61;1;

for( i&#61;1; i<&#61;n; i&#43;&#43;)

{

f *&#61; i;

printf("i&#61;%d ; f&#61;%d/n", i, f);

}

return f;

}

int main()

{

printf( "4!&#61;%d/n", fact(4) );

return 0;

}

程序3: 修改正确的求阶层函数

调试完成&#xff0c;bug找到&#xff0c;并修改正确。然后将加入的调试的函数printf 删除或注释掉。

该方法的缺点是(1)在正式发布的程序中需要去除或注释掉这些调试语句&#xff1b;(2)若程序又出现bug&#xff0c;则又需要重新插入函数printf输出调试信息&#xff0c;造成工作的重复。

方法二&#xff1a;自定义调试函数debug。

为 了避免方法一的缺点&#xff0c;可以利用条件编译技术&#xff0c;如程序4自定义调试函数debug。当程序正式发布的编译时取消宏定义__DEBUG__&#xff0c;在正式发布的程序 中就不会输出调试信息。若又出现bug&#xff0c;只要重新在编译程序时定义宏__DEBUG__即可恢复原来的调试信息输出。可以在编写程序时就有目的事先插入些 调试语句&#xff0c;这将有益于调试程序。另外&#xff0c;可以根据需要编写函数debug&#xff0c;将调试信息输出到除屏幕以外的其它地方&#xff0c;如文件或syslog服务器等。#include #ifdef __DEBUG__#include void debug(const char *fmt, ...){va_list ap;va_start(ap, fmt);vprintf(fmt, ap);va_end(ap);}#elsevoid debug(const char *fmt, ...){}#endifint fact(int n){int i, f &#61; 1;for( i&#61;1; i<&#61;n; i&#43;&#43;){f *&#61; i;debug("i&#61;%d ; f&#61;%d/n", i, f);}return f;}int main(){printf( "4!&#61;%d/n", fact(4) );return 0;}程序4: 自定义调试函数debug

该方法的缺点是(1)调试信息要么全部输出&#xff0c;要么全不输出&#xff1b;(2)要重新输出调试信息时需要重新编译程序。

方法三&#xff1a;含调试等级的自定义调试函数debug。

可 以继续改进方法&#xff0c;避免方法二中的缺点。我们可以根据调试信息的细节程度&#xff0c;将调试信息分成不同的等级。调试信息的等级必须大于0&#xff0c;若调试信息细节程度越高&#xff0c; 则等级越高。在输出调试信息时&#xff0c;若调试等级高于调试信息等级才输出调试信息&#xff0c;否则忽略该调试信息&#xff0c;如程序5。当调试等级为0时&#xff0c;则不输出任何调试信息。#include #include    /* atoi() */#include int debug_level;void debug(int level, const char *fmt, ...){if( level <&#61; debug_level ){va_list ap;va_start(ap, fmt);vprintf(fmt, ap);va_end(ap);}}int fact(int n){int i, f &#61; 1;for( i&#61;1; i<&#61;n; i&#43;&#43;){f *&#61; i;debug(250, "i&#61;%d ; f&#61;%d/n", i, f);}return f;}int main(int argc, char *argv[]){if ( argc <2 ){debug_level &#61; 0;}else{debug_level &#61; atoi(argv[1]);}printf( "4!&#61;%d/n", fact(4) );return 0;}程序5: 含调试等级的自定义调试函数debug

用命令"gcc -Wall -o fact fact.c"编译程序5&#xff0c;得到可执行文件 fact。若需要输出调试信息&#xff0c;只需要指定调试等级不低于250即可&#xff0c;如运行命令"./fact 250"&#xff0c;否则将不会输出调试信息。

这样&#xff0c;在正式发布版中包含调试信息也无伤大雅了&#xff0c;因为只需将调试等级配置为0&#xff0c;将不会出现任何调试信息。

该方法的缺点是效率不太高&#xff0c;因为不管调试信息是否需要输出&#xff0c;都会进行一次函数调用。若不需要输出调试信息&#xff0c;这次函数调用就多余了。

方法四&#xff1a;调试等级的判断放在自定义调试函数debug之外。

为了减少不必要的函数调用&#xff0c;可以用宏定义将调试等级的判断放在函数debug之外&#xff0c;如程序6。#include #include    /* atoi() */#include int debug_level;#define debug(level, fmt, arg...) /if( level <&#61; debug_level ) __debug(fmt, ##arg)void __debug(const char *fmt, ...){va_list ap;va_start(ap, fmt);vprintf(fmt, ap);va_end(ap);}int fact(int n)

{

int i, f &#61; 1;

for( i&#61;1; i<&#61;n; i&#43;&#43;)

{

f *&#61; i;

debug(250, "i&#61;%d ; f&#61;%d/n", i, f);

}

return f;

}

int main(int argc, char *argv[])

{

if ( argc <2 )

{

debug_level &#61; 0;

}

else

{

debug_level &#61; atoi(argv[1]);

}

printf( "4!&#61;%d/n", fact(4) );

return 0;

}

程序6: 调试等级的判断放在自定义调试函数debug之外

这种方法对于不需要输出的高等级的调试信息操作来说&#xff0c;仅仅多了个两个整数之间的大小判断。在正式的程序运行时&#xff0c;效率是有所提高的。

但 这种调试信息输出的方法依然不够完美。对于一个大项目&#xff0c;一般分为若干个模块&#xff0c;bug将会定位到某个或某几个模块。若整个项目的调试信息都输出&#xff0c;信息量将会 非常大&#xff0c;也容易干扰调试人员的思维。这时&#xff0c;我们需要的是只输出我们关心的那些模块的调试信息&#xff0c;但该方法并不能达到我们的要求。它只能根据调试等级输出信 息&#xff0c;对于同一调试等级的信息要么全输出&#xff0c;要么全不输出。

方法五&#xff1a;根据不同的功能模块分别定义不同的调试等级。

在squid[1]中&#xff0c;定义了以下的功能模块调试等级变量和调试函数&#xff1a;int debugLevels[MAX_DEBUG_SECTIONS];#define debug(SECTION, LEVEL) /((_db_level &#61; (LEVEL)) > debugLevels[SECTION]) ? (void) 0 : _db_print

然后在程序中如下使用它&#xff1a;debug(17, 3) ("fwdStateFree: %p/n", fwdState);

上述调试函数很灵活&#xff0c;可以在不同的模块中定义有不同的调试等级&#xff0c;当需要调试某功能时&#xff0c;只需将该模块的调试等级定义为相应的等级&#xff0c;就可输出需要的调试信息。

根据方法五的思想&#xff0c;本人编写了my_debug.h(见程序7)和my_debug.c 文件(见程序8)。该文件可以应用于C语言程序中&#xff0c;支持根据不同的功能模块分别定义不同的调试等级。

#ifndef MY_DEBUG_H

#define MY_DEBUG_H

#include

// 模块功能号

enum {

MY_SECTION_FACT &#61; 0,

MY_SECTION_nnn1,

MY_SECTION_nnn2,

MY_SECTION_nnnn,

MY_SECTION_END,

};

// 非my_debug.c文件的外部变量声明

#ifndef MY_DEBUG_C

extern int __my_allow_debug_levels[MY_SECTION_END];

#endif

// (内部使用) 判断"SECTION"模块功能号是否允许"DEBUG_LEVEL"等级的调试信息输出

#define __my_unallow_debug(SECTION, DEBUG_LEVEL) /

( DEBUG_LEVEL > __my_allow_debug_levels[SECTION] )

// (内部使用) 调试信息输出函数

#define __my_debug(FORMAT, ARG...) /

printf("%s:%d %s: " FORMAT, __FILE__, __LINE__, __FUNCTION__, ##ARG)

// 初始化"SECTION"模块功能号的调试等级

#define my_init_debug_levels(SECTION, ALLOW_DEBUG_LEVEL) /

( __my_allow_debug_levels[SECTION] &#61; ALLOW_DEBUG_LEVEL )

// 调试信息输出函数&#xff0c;该信息为"SECTION"模块功能号"DEBUG_LEVEL"等级的调试信息

#define my_debug(SECTION, DEBUG_LEVEL) /

( __my_unallow_debug(SECTION, DEBUG_LEVEL) ) ? (void) 0 : __my_debug

#endif //MY_DEBUG_H

程序7: my_debug.h

#define  MY_DEBUG_C

#include "my_debug.h"

int __my_allow_debug_levels[MY_SECTION_END];

程序8: my_debug.c

要使用上述文件&#xff0c;先得根据功能模块的数目扩展my_debug.h中的“模块功能号”枚举类型&#xff0c;然后在程序相应位置中调用宏定义my_init_debug_levels 初始化相应模块的调试等级&#xff0c;在所有需要输出调试信息的位置如下编写即可。my_debug(MY_SECTION_FACT, 250)("i&#61;%d ; f&#61;%d/n", i, f);

下面我们来看看如何在fact.c中使用它们(见程序9)。#include #include #include "my_debug.h"int fact(int n){int i, f &#61; 1;for( i&#61;1; i<&#61;n; i&#43;&#43;){f *&#61; i;my_debug(MY_SECTION_FACT, 250)("i&#61;%d ; f&#61;%d/n", i, f);}return f;}int main(int argc, char *argv[]){if ( argc <2 ){my_init_debug_levels(MY_SECTION_FACT, 0);}else{my_init_debug_levels(MY_SECTION_FACT, atoi(argv[1]));}printf( "4!&#61;%d/n", fact(4) );return 0;}程序9: fact.c

参考文献&#xff1a;



推荐阅读
  • 本文介绍了GTK+中的GObject对象系统,该系统是基于GLib和C语言完成的面向对象的框架,提供了灵活、可扩展且易于映射到其他语言的特性。其中最重要的是GType,它是GLib运行时类型认证和管理系统的基础,通过注册和管理基本数据类型、用户定义对象和界面类型来实现对象的继承。文章详细解释了GObject系统中对象的三个部分:唯一的ID标识、类结构和实例结构。 ... [详细]
  • C语言自带的快排和二分查找
    Author🚹:CofCaiEmail✉️:cai.dongjunnexuslink.cnQQ😙:1664866311personalPage&#x ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 本文介绍了在Windows系统上使用C语言命令行参数启动程序并传递参数的方法,包括接收参数程序的代码和bat文件的编写方法,同时给出了程序运行的结果。 ... [详细]
  • C语言判断正整数能否被整除的程序
    本文介绍了使用C语言编写的判断正整数能否被整除的程序,包括输入一个三位正整数,判断是否能被3整除且至少包含数字3的方法。同时还介绍了使用qsort函数进行快速排序的算法。 ... [详细]
  • 利用空间换时间减少时间复杂度以及以C语言字符串处理为例减少空间复杂度
    在处理字符串的过程当中,通常情况下都会逐个遍历整个字符串数组,在多个字符串的处理中,处理不同,时间复杂度不同,这里通过利用空间换时间等不同方法,以字符串处理为例来讨论几种情况:1: ... [详细]
  • 该楼层疑似违规已被系统折叠隐藏此楼查看此楼*madebyebhrz*#include#include#include#include#include#include#include ... [详细]
  • C语言的经典程序有哪些
    本篇内容介绍了“C语言的经典程序有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • linux 字符串数组初始化,C++字符数组初始化方法的分析
    发现了一个字符数组初始化的误区,而这个往往能导致比较严重的性能问题,分析介绍如下:往往我们在初始化一个字符数组,大概有如下几 ... [详细]
  • golang源码分析调度概述
    golang源码分析-调度过程概述本文主要概述一下golang的调度器的大概工作的流程,众所周知golang是基于用户态的协程的调度来完成多任务的执行。在Linux ... [详细]
author-avatar
mobiledu2502882333
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有