首页 > 教程 > 正文

阅读排行

FAQ之 Debug单步调试
2014-02-20 14:17:14   来源:Fcode研讨团队   评论:0 点击:

本文介绍了 IVF+VS 和 Compaq Visual Fortran 编译器的单步调试操作方法,其他编译器可以类比参考。适合初学者阅读。

第一,Debug 调试是什么?

据传,世界上最早一批程序员中的一位,葛丽丝·霍普,在调试程序时出现故障,拆开继电器后,发现有只飞蛾被夹扁在触点中间,从而“卡”住了机器的运行。于是,霍波诙谐的把程序故障统称为“臭虫(BUG)”,把排除程序故障叫DeBug。后来,Debug 就成为了一个专业的计算机术语。

Debug 是一个程序员的基本功。它有很多层含义,比如源码级Debug,汇编级Debug,系统级Debug等等。Fortran 程序员,通常只会接触到源码级Debug。本文也只描述源码级Debug,如您对汇编和反汇编有一定的了解,还可尝试汇编级Debug。

源码级 Debug,意思是,让程序逐行逐行的运行,运行在中途时,可暂停运行,并将此时的若干状态呈现给程序员查看,以便程序员分析,此时各变量各过程及程序流程,是否符合自己的预期。同时,调试者可随时改变这些状态,例如变量的值,然后继续运行,以便测试在不同情况下程序的反应。

目前的计算机运行速度已经很快了,往往很长的代码运行完毕也就一眨眼的功夫。如果运行过程中出现错误,或者计算结果不正确。往往程序员不知道问题发生在哪里。而 Debug 则可以让程序在可疑的程序段暂停下来,程序员可以查看此时是否符合自己的预期?从而为查找问题提供更多的依据。

源码级 Debug,可便于程序员查找运行时错误(Run-time Error),计算结果不正确,这两种问题。(编译链接错误不能通过Debug查找

第二,Debug 的前提

Debug 实际上是一种运行程序的方式。所以,首要前提是,程序已经可以正常链接,获得可执行文件。对于不单独运行的程序,例如 DLL,LIB 等,需要额外的 Loader 来加载它,并进行调试(本文暂不涉及,如您有疑问,可于本站论坛提出)。如果您的代码编译链接还有错误,那么请先解决编译链接问题。

其次,Debug 需要调试器(Debugger),这是一种软件。一般商业编译器都会附带调试器。免费开源编译器也会有附属的开源调试器。如果你的编译器产品需要选择组件安装,请确保自己勾选了相应的调试器并进行了安装。

最后,很多编译器允许两种编译链接方式:Debug模式 Release模式
这两种链接方式的区别主要是:

1.Debug 模式:程序几乎不进行优化。产生的可执行程序具有调试信息,执行效率低,文件尺寸大。
2.Release 模式:程序进行合理优化。产生的可执行程序不具有调试信息,执行效率高,文件尺寸小。
(实际上,Debug模式 和 Release模式只是编译器预设的两种方式,我们可以通过调节编译链接参数来获得更自由的搭配,产生介于Debug和Release之间的编译方式)

想要进行 Debug 调试,我们需要程序中存在调试信息,需采用 Debug 模式 编译链接程序才可以。

第三,Debug 的操作

调试器的操作,因不同调试器而不同,这里以集成在 Visual Studio 中的 Intel 调试器为例。Intel Visual Fortran 会默认安装这个调试器。

其他的调试器,如果也是集成在IDE中的,则操作方式大同小异。如果是单独的命令行程序,则需要通过命令来进行调试(本文暂不涉及)

首先我们来看一个示范代码:


Program www_fcode_cn
  !// 本程序用于演示 Intel 调试器的使用
  Implicit None
  Integer , parameter :: N = 10
  Real :: a( N ) , b( N )
  integer :: i
  Do i = 1 , N
    a(i) = i
    b(i) = 100 - i
  End Do
  !a(5) = 0.0
  Do i = 1 , N
    write( * , * ) b(i) / a(i)
  End Do
End Program www_fcode_cn

程序第11行,是故意将a(5) 设定为 0.0 的。如果不执行它,则程序应该输出: 

   99.00000
   49.00000
   32.33333
   24.00000
   19.00000
   15.66667
   13.28571
   11.50000
   10.11111
   9.000000
 
如果执行了11行,则可能会输出 Infinity,也可能会出现运行时错误forrtl: error (73): floating divide by zero
(根据不同编译器或编译参数中,浮点数的设置而不同)

假设我们程序执行时,遇到了错误,分母为零了。我们来看看如何通过 Debug 发现这个错误。

首先第一步,我们要在可疑的位置插入断点(Insert breakpoint),调试器会让程序运行到断点的位置并暂停下来。如果可疑的位置有多处,可以分别插入断点。
集成开发环境里,在编辑代码时,右键既可插入断点。(在某些环境下,直接点击某行最前方也可插入断点)

请注意,如果你修改了代码,则需要保存,然后编译链接,才能够插入断点。另外,断点不可位于注释语句上,比如上例,想在11行插入断点,就需要取消11行的注释感叹号(然后保存重新编译链接)

\

需要注意的是,断点需要执行到它的位置,只能位于执行语句(定义语句并不执行)。

\

如上图,我们插入了两个断点。存在断点的行,一般会有红色的实心圆。

第二步,启动调试程序。可通过菜单栏,工具栏上的按钮进行。在不同的环境下,具体菜单栏和按钮位置不同。你或许需要自定义一下工具栏。

\

之后,我们可以看到,程序执行到它遇到的第一个断点处。

\

此时,黄色的箭头指向的行,即为当前程序执行到的位置。(一般来说,一开始都在断点上,所以是红色圆内一个黄色箭头)

此时,程序就在一开始的位置等待我们。我们可以查看到各种状态,例如变量的值。在局部变量窗口我们会看到 I B A 三个变量(或数组),他们的值很乱。因为此时还没有对他们进行初始化。

\

如果你无法看到局部变量窗口,可能是被隐藏起来了,你或许需要在角落里找一下它,或者通过某些菜单把他“召唤”出来。(由于VS版本的不同,具体位置和图标外观,菜单名称可能稍有不同)

\

在局部变量里,我们已经可以修改 i 或者 a b 数组的值了,但是目前修改,还没有太大的意义。

我们按下逐语句按钮:\

黄色箭头就会下移到下一条语句,这表示程序又向前执行了一行。

\

同时,局部变量里,i 的值变成了 1 ,这表示循环变量 i 有了值,并且当前是 1。红色,表示此时与上一步相比值发生了改变。相比而言,B 和 A 数组未改变,因此不是红色。

再次点击逐语句按钮(或快捷键F11),黄色箭头继续下移,且 A(1) 变为红色。这表示数组 a 的第一个元素发生了改变。

多次点击逐语句按钮。会发现,黄色箭头开始在循环体内来回移动,i 的值从1开始变大,a 数组慢慢变为 1-10 之间的数。这是完全符合我们的代码预期的。

\

如果循环较多,比如1000次循环,点击逐语句就需要点击几千次。此时,我们可以在循环外插入第二个断点,例如在本例的第11行。然后点击继续按钮(表示直接运行到下一个断点处,而不是一步一步执行了)。\

此时,可发现黄色箭头已到了下一个断点的位置,而且 A 数组和 B 数组已经赋值完毕。均符合我们的预期。

(假设我们此时无法确定第11行导致了 a(5) = 0.0)

再次点击“继续”按钮。编译器一直运行,直到发生了错误为止,会弹出错误:

\

我们点击“中断”,会发现程序虽然没有碰到断点,但依然暂停了。黄色箭头停留在第13行。我们把鼠标移动到 i 变量上,看到他的值是 5,说明第5次循环出现了错误。

\

分别移动鼠标到 b 和 a 上,查看 b(5) 和 a(5) 的值。会发现 a(5) 的值为 0.000,这就是导致错误的原因

\

在某些情况下,某些编译器,或者因为设置原因,并不会抛出错误,也无法中断,编译器会让 b(5) / 0.0 = Infinity。
此时,就需要更细致的断点,跟踪,检查各变量的值是否符合预期,然后再来确定原因了。(当然也可以通过设置让编译器抛出浮点数错误),例如 IVF 如此设置:

\


关于调试,还有很多问题。下面简单陈述,具体效果请读者自行测试,很容易理解。

上述的逐语句按钮,在遇到函数调用时,会进入函数内部。如果不希望进入函数内部,可使用逐过程按钮(或跟踪步过),如果已进入函数内,可使用跳出当前函数执行到返回。

\

读者可以自行拿出自己以前的100行以内有函数调用的代码来测试一下,点击这些按钮,观察黄色箭头的位置,这些概念并不难理解。

最后,有一个比较常用的东西,叫做“条件断点”,它会在满足某些条件时才会触发这个断点。例如,一个10000次的循环,循环到 1234 次出现了错误。我们不能点1234次逐语句吧?此时,我们可设置断点的触发条件为 i>=1234。
或者说,循环到不知道多少次的时候,某个数 ff 变为 0 了,我们可设置断点的触发条件为 ff < 0.00001。

首先插入常规断点,然后在断点处右键:
\

\


调试还有很多可利用的工具,例如调用堆栈可查看程序调用函数的全部路线,对某些变量数组添加监视,查看内存中的数据,寄存器的值,windows返回的错误,调试字符串等等。

请读者朋友们根据现有的提示自行琢磨。祝大家的代码都调通!

相关热词搜索:Fortran调试 单步调试 Debug

上一篇:新语法系列 之 定义变量和数组的那点事
下一篇:FAQ之 文件行列与二维数组行列

分享到: 收藏