linux下调试程序用什么工具

linux下调试程序用什么工具,第1张

Linux调试程序大多数都是喜欢用gdb,gdb是一个命令行界面的程散卜序调试工具,任何调试 *** 作都是输入命令来实现的。从Windows过来的程序员可冲州穗能不习迹好惯用gdb这个调试器,可以去试试一些图形界面的的调试器,比如Kgdb、Affinic GUI Debugger,这两款调试器虽然是图形界面的调试器,但是它其实是调用gdb来完成它的调试功能的,也就是说它们其实是gdb的一个图形界面的外壳,所以要先安装好gdb再安装它们,不习惯gdb的命令界面的话,可以试试这两款软件。

要了解调试程序的最好方法,首先要分析一下调试过程的三个要素:

应该用什么工具调试一个程序?

用什么办法才能找出程序中的错误?

怎样才能从一开始就避免错误?

应该用什么工具调试一个程序?

有经验的程序员会使用许多工具来帮助调试程序,包括一组调试程序和一些"lint”程序,当然,编译程序本身也是一种调试工具。

在检查程序中的逻辑错误时,调试程序是特别有用的,因此许多程序员都把调试程序作为基本的调试工具。一般来说,调试程序能帮助程序员完成以下工作:

(1)观察程序的运行情况

仅这项功能就使一个漏局典型的调试程序具备了不可估量的价值。即使你花了几个月的时间精心编写了一个程序,你也不一定完全清楚这个程序每一步的运行情况。如果程序员忘记了某些if语句、函数调用或分支程序,可能会导致某些程序段被跳过或执行,而这种结果并不是程序员所期望的。不管怎样,在程序的执行过程中,尤其是当程序有异常表现时,如果程序员能随时查看当前被执行的是那几行代码,那么他就能很好地游嫌了解程序正在做什么以及错误发生在什么地方。

(2)设置断点

通过设置断点可以使程序在执行到某一点时暂时停住。当你知道错误发生在程序的哪一部分时,这种方法是特别有用的。你可以把断点设置在有问题的程序段的前面、中间或后面。当程序执行到断点时,就会暂时停住,此时你可以检查所有局部变量、参数和全局变量的值。如果一切正常,可以继续执行程序,直到遇到另一个断点,或者直到引起问题的原因暴露出来。

(3)设置监视

程序员可以通过调试程序监视一个变量,即连续地监视一个变量的值或内容。如果你清楚一个变量的取值范围或有效内容,那么通过这种方法就能很快地找出错误的原因。此外,你可以让调试程序替你监视变量,并且在某个变量超出预先定义的取值范围或某个条件满足时使程序暂停执行。如果你知道变量的所有行为,那么这么做是很方便的。

好的调试程序通常还提供一些其它功能来简化调试工作。然而,调试程序并不是唯一的调试工具,lint程序和编译程序本身也能提供很有价值的手段来分析程序的运行情况。

注意:lint程序能分辨数百种常见的编程错误,并且能报告这些错误发生在程序的哪一部分。尽管其中有一些并不是真正的错误,但大部分还是有价值的。

lint程序和编译程序所提供的一种典型功能是编译时检查(compile—time checks),这种功能是调试程序所不具备的。当用这些工具编译你的程序时,它们会找出程序中有问题的程序段,可能产生意想不到的效果的程序段,以及常见的错误。下面将分析几个这种检查方式的应用例返磨让子,相信对你会有所帮助。

等于运算符的误用

编译时检查有助于发现等于运算符的误用。请看下述程序段:

void foo(int a,int b)

{

if ( a = b )

{

/ * some code here * /

}

}

这种类型的错误一般很难发现!程序并没有比较两个变量,而是把b的值赋给了a,并且在b不为零的条件下执行if体。一般来说,这并不是程序员所希望的(尽管有可能)。这样一来,不仅有关的程序段将被执行错误的次数,并且在以后用到变量a时其值也是错误的。

未初始化的变量

编译时检查有助于发现未初始化的变量。请看下面的函数:

void average ( float ar[], int size )

{

float total

int a

for( a = 0a<size++a)

{

total+=ar[a]

}

printf(" %f\n", total / (float) size )

}

这里的问题是变量total没有被初始化,因此它很可能是一个随机的无用的数。数组所有元素的值的和将与这个随机数的值相加(这部分程序是正确的),然后输出包括这个随机数在内的一组数的平均值。

变量的隐式类型转换

在有些情况下,C语言会自动将一种类型的变量转换为另一种类型。这可能是一件好事(程序员不用再做这项工作),但是也可能会产生意想不到的效果。把指针类型隐式转换成整型恐怕是最糟糕的隐式类型转换。

void sort( int ar[],int size )

{

/* code to sort goes here * /

}

int main()

{

int arrgy[10]

sort( 10, array )

}

上述程序显然不是程序员所期望的,虽然它的实际运行结果难以预测,但无疑是灾难性的。

用什么办法才能找出程序中的错误?

在调试程序的过程中,程序员应该记住以下几种技巧:

先调试程序中较小的组成部分,然后调试较大的组成部分

如果你的程序编写得很好,那么它将包含一些较小的组成部分,最好先证实程序的这些部分是正确的。尽管程序中的错误并不一定发生在这些部分中,但是先调试它们有助于你理解程序的总体结构,并且证实程序的哪些部分不存在错误。进一步地,当你调试程序中较大的组成部分时,你就可以确信那些较小的组成部分是正常工作的。

彻底调试好程序的一个组成部分后,再调试下一个组成部分

这一点非常重要。如果证实了程序的一个组成部分是正确的,不仅能缩小可能存在错误的范围,而且程序的其它组成部分就能安全地使用这部分程序了。这里应用了一种很好的经验性原则,简单地说就是调试一段代码的难度与这段代码长度的平方成正比,因此,调试一段20行的代码比调试一段10行的代码要难4倍。因此,在调试过程中每次只把精力集中在一小段代码上是很有帮助的。当然,这仅仅是一个总的原则,具体使用时还要视具体情况而定。

连续地观察程序流(flow)和数据的变化

这一点也很重要!如果你小心仔细地设计和编写程序,那么通过监视程序的输出你就能准确地知道正在执行的是哪部分代码以及各个变量的内容都是什么。当然,如果程序表现不正常,你就无法做到这一点。为了做到这一点,通常只能借助于调试程序或者在程序中加入大量的print语句来观察控制流和重要变量的内容。

始终打开编译程序警告选项 并试图消除所有警告

在开发程序的过程中,你自始至终都要做到这一点,否则,你就会面临一项十分繁重的工作。尽管许多程序员认为消除编译程序警告是一项繁琐的工作,但它是很有价值的。编译程序给出警告的大部分代码至少都是有问题的,因此用一些时间把它们变成正确的代码是值得的;而且,通过消除这些警告,你往往会找到程序中真正发生错误的地方。

准确地缩小存在错误的范围

如果你能一下子确定存在错误的那部分程序并在其中找到错误,那就会节省许多调试时间,并且你能成为一个收入相当高的专业调试员。但事实上,我们并不能总是一下子就命中要害,因此,通常的做法是逐步缩小可能存在错误的程序范围,并通过这种过程找出真正存在错误的那部分程序。不管错误是多么难于发现,这种做法总是有效的。当你找到这部分程序后,就可以把所有的调试工作集中到这部分程序上了。不言而喻,准确地缩小范围是很重要的,否则,最终集中精力调试的那部分程序很可能是完全正确的。

如何从一开始就避免错误?

有这样一句谚语——“防患于未然”,它的意思是避免问题的出现比出现问题后再想办法弥补要好得多。这在计算机编程中也是千真万确的!在编写程序时,一个经验丰富的程序员所花的时间和精力要比一个缺乏经验的程序员多得,但正是这种耐心和严谨的编程风格使经验丰富的程序员往往只需花很少的时间来调试程序,而且,如果此后程序要解决某个问题或做某种改动,他便能很快地修正错误并加入相应的代码。相反,对于一个粗制滥造的程序,即使它总的来说还算正确,那么改动它或者修正其中一个很快就暴露出来的错误,都会是一场恶梦。

一般来说,按结构化程序设计原则编写的程序是易于调试和修改的,下面将介绍其中的一些原则。

程序中应有足够的注释

有些程序员认为注释程序是一项繁琐的工作,但即使你从来没想过让别人来读你的程序,你也应该在程序中加入足够的注释,因为即使你现在认为清楚明了的语句,在几个月以后往往也会变得晦涩难懂。这并不是说注释越多越好,过多的注释有时反而会混淆代码的原意。但是,在每个函数中以及在执行重要功能或并非一目了然的代码前加上几行注释是必要的。下面就是一段注释得较好的代码:

/*

* Compute an integer factorial value using recursion.

* Input an integer number.

* Output : another integer

* Side effects : may blow up stack if input value is * Huge *

*/

int factorial ( int number)

{

if ( number <= 1)

return 1 /* The factorial of one is oneQED * /

else

return n * factorial( n - 1 )

/ * The magic! This is possible because the factorial of a

number is the number itself times the factorial of the

number minus one. Neat! * /

}

函数应当简洁

按照前文中曾提到的这样一条原则——调试一段代码的难度和这段代码长度的平方成正比——函数编写得简洁无疑是有益的。但是,需要补充的是,如果一个函数很简洁,你就应该多花一点时间去仔细地分析和检查它,以确保它准确无误。此后你可以继续编写程序的其余部分,并且可以对刚才编写的函数的正确性充满信心,你再也不需要检查它了。对于一段又长又复杂的例程,你往往是不会有这样的信心的。

编写短小简洁的函数的另一个好处是,在编写了一个短小的函数之后,在程序的其它部分就可以使用这个函数了。例如,如果你在编写一个财务处理程序,那么你在程序的不同部分可能都需要按季、按月、按周或者按一月中的某一天等方式来计算利息。如果按非结构化原则编写程序,那么在计算利息的每一处都需要一段独立的代码,这些重复的代码将使程序变得冗长而难读。然而,你可以把这些任务的实现简化为下面这样的一个函数:

/*

*ComDllte what the "real" rate of interest would be

* for a given flat interest rate, divided into N segments

*/

double Compute Interest( double Rate, int Segments )

{

int a

double Result = 1.0

Rate /= (double) Segments

for( a = 0a<Segments ++a )

Result * =Rate

return Result

}

在编写了上述函数之后,你就可以在计算利息的每一处调用这个函数了。这样一来,你不仅能有效地消除每一段复制的代码中的错误,而且大大缩短了程序的长度,简化了程序的结构。这种技术往往还会使程序中的其它错误更容易被发现。

当你习惯了用这种方法把程序分解为可控制的模块后,你就会发现它还有更多的妙用。

程序流应该清晰,避免使用goto语句和其它跳转语句

这条原则在计算机技术领域内已被广泛接受,但在某些圈子中对此还很有争议。然而,人们也一致认为那些通过少数语句使程序流无条件地跳过部分代码的程序调试起来要容易得多,因为这样的程序通常更加清晰易懂。许多程序员不知道如何用结构化的程序结构来代替那些“非结构化的跳转”,下面的一些例子说明了应该如何完成这项工作:

for( a = 0a<100s ++a)

{

Func1( a )

if (a = = 2 ) continue

Func2( a )

}

当a等于2时,这段程序就通过continue语句跳过循环中的某余部分。它可以被改写成如下的形式:

for( a = 0a<100++a)

{

Func1 (a)

if (a !=2 )

Func2(a)

}

这段程序更易于调试,因为花括号内的代码清楚地显示了应该执行和不应该执行什么。那么,它是怎样使你的代码更易于修改和调试的呢?假设现在要加入一些在每次循环的最后都要被执行的代码,在第一个例子中,如果你注意到了continue语句,你就不得不对这段程序做复杂的修改(不妨试一下,因为这并非是显而易见的!);如果你没有注意到continue语句,那么你恐怕就要犯一个难以发现的错误了。在第二个例子中,要做的修改很简单,你只需把新的代码加到循环体的末尾。

当你使用break语句时,可能会发生另外一种错误。假设你编写了下面这样一段程序:

for (a =0) a<100 ++a)

{

if (Func1 (a) ==2 )

break

Func2 (a)

}

假设函数Funcl()的返回值永远不会等于2,上述循环就会从1进行到100;反之,循环在到达100以前就会结束。如果你要在循环体中加入代码,看到这样的循环体,你很可能就会认为它确实能从0循环到99,而这种假设很可能会使你犯一个危险的错误。另一种危险可能来自对a值的使用,因为当循环结束后,a的值并不一定就是100。

c语言能帮助你解决这样的问题,你可以按如下形式编写这个for循环:

for(a=O;a<100&&Func1(a)!=2;++a)

上述循环清楚地告诉程序员:“从0循环到99,但一旦Func1()等于2就停止循环”。因为整个退出条件非常清楚,所以程序员此后就很难犯前面提到的那些错误了。

函数名和变量名应具有描述性

使用具有描述性的函数和变量名能更清楚地表达代码的意思——并且在某种程度上这本身就是一种注释。以下几个例子就是最好的说明:

y=p+i-c;

YearlySum=Principal+Interest-Charges:

哪一个更清楚呢?

p=*(l+o);

page=&List[offset];

哪一个更清楚呢?

调试程序一般应经过以下几个步骤:

1先进行人工检查,即静态检查。在写好一个程序以后,不要匆匆忙忙上机,而应对纸面上的程序进行人工检查。这一步是十分重要的,它能发现程序设计人员由于疏忽而造成的多数错误。而这一步骤往往容易被人忽视。有人总希望把一切推给计算机系统去做,但这样就会多占用机器时间,作为一个程序人员应当养成严谨的科学作风,每一步都要严格把关,不把问题留给后面的程序。

为了更有效地进行人工检查,所编的程序应注意力求做到以下几点:应当采用结构化程序方法编程,以增加可衡含没读性;2尽可能多加注释,以帮助理解每段程序的作用;3在编写复杂的程序时不要将全部语句都写在main函数中,而要多利用函数,用一个函数来实现一个单独的功能。这样既易于阅读也便于调试,各函数之间除用参数传递数据这一渠道以外,数据间尽量少出现耦合关系,便于分别检查和处理。

2在人工检查无误后,才可以上机调试。通过上机发现错误称动态检查。在编译时给出语法错误的信息,可以根据提示的信息具体找出程序中出错之处并改正之。应当注意的是有时提示的出错并不是真正出错的行,如果在提示出错的行上找不到错误的话应当到上一行再找。有时提示出错的类型并非绝对准确,由于出错的情况繁多各种错误互有关联,因止要善于分析,找出真正的错误,而不要只从字面意义上找出错信息,钻牛角尖。

如果系统提示的出错信息老纯多,应当从上到下一一改正。有时显示出一大片出错信息往往使人感到问题严重,无从下手。其实可能只有一二个错误。例如,对使用的变量未定义,编译时就会对所有含该变量的语句发出出错信息;有的是少了“}”或多了“}”有的是书写语句时忘记写“;”或是全角的“;”了,只要加上一个变量定义,或填加“};”就所有错误都消除了。

3在改正语法错误后,程序经过连接就得到可执行的目标程序。运行程序,输入程序所需数据,就可得到运行结果。应当对运行结果作分析。看它是否符合要求。有的初学者看到运行结果就认为没问题了,不作认真分析,这是危险的。

有时,数据比较复杂,难以立即判断结果是否正确。可以事先考虑好一批“试验数据”,输入这些数据可以得出容易判断正确与否的结果。可咐纳以在计算的输出结果的程序地方加入一段输出到串口的程序,利用串口窗口可以方便看到结果的,比仿真的都好和直观。例如,if语句有两个分支,有可能在流程经过其中一个分支时结果正确,而经过其它一个分支时结果不对等。必须考虑周全。

事实上,当程序复杂时很难把所有的可能方案全部都试到,选择典型的情况作试验即可。

4 运行结果不对,大多属于逻辑错误。对这类错误往往需要仔细检查和分析才能发现。可以采用以下办法:

1 将程序与流程图仔细对照,如果流程图是正确的话,程序写错了,是很容易发现的。例如,复合语句忘记写花括弧,只要一对照流程图就能很快发现。

2 如果实在找不到错误,可以采用“分段检查”的方法。在程序不同的位置设几个printf函数语句,输出有关变量的值,往下检查。直到找到在哪一段中数据不对为止。这时就已经把错误局限在这一段中了。不断减小“查错区”,就可能发现错误所在。

3 也可以用“条件编译”命令进行程序调试(在程序调试阶段,若干printf函数语句就要进行编译并执行。当调试完毕,这些语句不要再编译了,也不再被执行了)。这种方法可以不必一一去printf函数语句,以提高效率。

4 如果在程序中没有发现问题,就要检查流程图有无错误,即算法有无问题,如有则改正之,接着修改程序。

5 有的系统还提供debug(调试)工具,跟踪流程并给出相应信息,使用更为方便,如KEILC51里的调试运行就很好用的,可以在命令的窗口输入程序中需要的数据和开关量、中断、I/O口的电平等来方便完成程序的运行,使用时要先打开Options for Target'Target1'(工程菜单里的目标‘TARGET1'属性)-点DEBUG-选中左边use Simulator\load Application at sta\Go till main按确定后退出到编辑画面,要先对源程序进行编译后没有错误才可以按主菜单上的调试按钮,进行调试的,F10为子程序运行式的单步、F11为单步调试;具体使用可以看本站提供的51单片机应用系统开发实例一书中的Dscope foe Windows一节。可以下载本站上中文版的KIELC51就直观了。

总之,程序调试是一项细致深入的工作,需要下功夫,动脑子,善于累积经验。在程序调试过程中往往反映出一个人的水平,经验和科学态度。希望读者能给以足够的重视。上机调试程序的目的决不 是为了“验证程序的正确”,而是“掌握调试的方法和技术”,不要一直依赖仿真器来解决,要学会自己找问题,这样慢慢自己就会写出错误较少的实用的程序,站长就没有仿真器的哦,靠KEILC51里的软件仿真就解决了。


欢迎分享,转载请注明来源:内存溢出

原文地址:https://www.54852.com/yw/12539732.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2025-08-26
下一篇2025-08-26

发表评论

登录后才能评论

评论列表(0条)

    保存