1.错误类型:功能定义错误,设计规划错误,代码编写错误。
2.程序调试可以分为如下5个阶段。
测试:找出程序中存在的缺陷或错误。
固化:让程序的错误可重现。
定位:确定相关的代码行。
纠正:修改代码纠正错误。
验证:确定修改解决了问题。
3.一些调试技巧:
根据编译器提示修改语法错误
添加打印检查程序是否正常工作
如果发现程序的运行情况和预期不同,重新阅读程序,检查代码,可以在编译的时候添加警告选项帮助检查,尤其是-Wall 如:gcc -Wall abc.c
在大多数系统中,操作系统分配给程序的内存一般都会比程序实际需要使用的大一些。如果非法内存访问出现在这部分内存区域内,硬件就可能检测不到,这就是并非所有版本的Linux和UNIX系统都会产生段错误的原因。
如果想捕捉到数组访问方面的错误,最好增加数组元素的大小,因为这样同时也增加了错误的大小,更容易检查出来。
取样法是指在程序中添加一些代码以收集更多与程序运行时的行为相关的信息的方法。
取样法的小技巧:
(1)用预处理器有选择的包括取样代码,这样只需要重新编译程序就可以包含或去除调试代码,在编译时加上-DDEBUG
#ifdef DEBUG
printf("error....\n");
#endif
(2)用数值调试宏完成更复杂的调试应用:定义一组宏分别代表不同的调试级别,编译传入一个参数来控制各级别调试宏的开关。
#define BASIC_DEBUG 1
#define EXTRA_DEBUG 2
#define SUPER_DEBUG 4
#if (DEBUG & EXTRA_DEBUG)
printf.....
#endif
假如编译时带参数-DDEBUG=5,则EXTRA_DEBUG关闭,BASIC_DEBUG和SUPER_DEBUG开启。标志-DDEBUG=0将禁用所有调试信息,也可以再程序中添加以下语句,则不需要调试时不用在编译时带-DDEBUG
#ifndef DEBUG
#define DEBUG 0
#endif
预处理器定义的一些宏也可以帮助调试:
__LINE__代表当前行号
__FILE__代表当前文件名的字符串
__DATE__代表当前日期
__TIME__代表当前时间
#include <stdio.h>
#include <stdlib.h>
int main()
{
#ifdef DEBUG
printf("Compiled: " __DATE__ " at " __TIME__ "\n");
printf("This is line %d of file %s\n", __LINE__, __FILE__);
#endif
printf("hello world\n");
exit(0);
}
4.使用gdb调试
编译时加-g选项,gdb 可执行文件名 进入gdb,run开始运行,运行到出错的地方会停止并输出当前位置的代码,然后可以用backtrace或者where命令查询一下函数的调用路径,可以用print 变量名查看当前一些参数变量等的值。如下例,调用函数传入有5个元素的数组,代码中访问越界引发段错误。list命令可以输出错误前后的代码,还可以给list一个参数,行号或者函数名
gcc -g -o debug3 debug3.c
gdb debug3
break 行号设置断点
print 数组名,打印出全部数组元素值,print 数组名[下标],打印出某个元素,print 数组名[a]@b,打印出从a开始的连续b个元素的值
display 变量名,程序运行到断点处自动打印出变量值
continue 到断点停下后输入继续运行
command n:设置代码走到n号断点后执行的操作,假如设置为continue,display a则程序走到断点不会停,只是会打印出a的值,end结束commands输入。
info 命令:查看该命令设置的内容
disable 命令:暂时禁用某个命令
set variable n = n+1:打补丁,每次运行到断点处加上一句n=n+1
gdb还可以对正在运行时的程序进行调试,而不必先停止它,然后再重启它。
quit退出gdb
5.其他一些调试工具:
lint:代码静态分析工具,检查空指针,未初始化变量,代码不规范,赋值次序,操作符等等等,。
ctags:创建所有函数的索引表
cxref:生成变量交叉引用表
cflow:生成函数调用树
6.断言
assert宏对表达式进行求值,如果结果非零,它就往标准错误写一些诊断信息,然后调用abort函数结束程序的运行。
#include<assert.h>
void assert(int expression)
头文件assert.h定义的宏受NDEBUG的影响。如果程序在处理这个头文件时已经定义了NDEBUG,就不定义assert宏。这意味着可以在编译期间使用-DNDEBUG关闭断言功能或将
#define NDEBUG加到每个源文件中,但这条语句必须放在#include <assert.h>语句之前。
7.内存调试
如果在一个已分配的内存块尾部的后面(或在它头部的前面)写数据,就很可能会损坏malloc库用于记录内存分配情况的数据结构。出现这种情况后,经过一段时间,一个malloc调用,甚至是一个free调用都会引发段错误并导致程序崩溃。要想查出错误发生的准确地点是非常困难的,因为错误可能是在引发程序崩溃的事件之前很久发生的。
两个检查内存问题的工具:
ElectricFence函数库:它尝试用Linux的虚拟内存机制来保护malloc和free所使用的内存,当它发现内存被破坏时就停止程序的运行。
下载个安装包或者源码https://launchpad.net/ubuntu/+source/electric-fence
下了源码,tar zxvf electric-fence_2.2.5.tar.gz解压一下,进目录make一下,然后把生成的libefence.a复制到/usr/bin下面
#include <stdio.h>
#include <stdlib.h>
int main()
{
char ch;
char *ptr = (char *) malloc(1024);
ptr[0] = 0;
ch = ptr[1024];
ptr[1024] = 0;
exit(0);
}
直接编译运行一切正常没有报错,加上efence之后,运行报错
valgrind:查找内存泄露利器,很强大的工具,好多参数,好多功能,日后用到详细总结
下载:https://www.valgrind.org/downloads/current.html#current
解压:tar -jxvf valgrind-3.17.0.tar.bz2
进目录./configure
make
make install
安装完输入valgrind提示你输入--help...就是安装成功了
简单测试一下功能
gcc -g -o efence efence.c
valgrind --leak-check=yes -v ./efence