《話(huà)說(shuō)程序調試》示例--分享(轉載)
4.2流程觀(guān)察分析法
流程觀(guān)察分析法,是通過(guò)觀(guān)察分析程序執行流程,了解程序執行信息、并分析這些信息,從而找出錯誤原因及位置。如果,程序的輸出信息不足以提供程序執行流程,那么,需要在程序的控制結構中插入信息輸出(即程序插裝)語(yǔ)句,使得程序試運行時(shí)能輸出相關(guān)信息以獲取程序執行流程信息,使調試者能根據這些信息進(jìn)行分析,獲知程序運行流程與錯誤結果間的關(guān)系,進(jìn)而判斷出錯原因。對于一些新手遇到的“不見(jiàn)程序運行結果”的情形,通過(guò)在程序流程的適當位置插入輸出語(yǔ)句后,能夠了解程序運行的大致情況,從而進(jìn)入下一步調試。
例4.1 需求描述:設計程序,將一維數組(元素為int型)中指定位置pos處的1個(gè)元素刪除。
程序:
/*ch4_ex1.c*/
#include stdio.h
#define MAX 10
main()
int a[MAX],i,pos,num;
num=MAX;
printf("Input array a(%d integer numbers):",MAX);
for(i=0;iMAX;i++)/*輸入數組元素*/
scanf("%d",a[i]);
printf("Specify the position:");
scanf("%d",pos);/*輸入刪除位置,首元從1開(kāi)始*/
if(pos1||posMAX)/*刪除位置合法性檢查*/
printf("Eroor! Enter a key to continue...\n");
getchar();
return;
while(posMAX) /*將pos及其后的元素復制到前一單元*/
a[pos-1]=a[pos];
pos++;
num--;
printf("Now,array a:\n");
for(i=0;inum;i++)/*輸出刪除后的數組元素*/
printf("%8d",a[i]);
程序運行情況:
程序運行后,根據提示信息Input array a (10 integer numbers):輸入數據
1 2 3 4 5 6 7 8 9 10
再根據提示信息Specify the position:輸入 5,程序沒(méi)有反應,強行退出程序執行。分析程序出錯誤情況,從交互信息看,程序已經(jīng)執行到while循環(huán),可能無(wú)法退出循環(huán)而表現為沒(méi)有反應。那么,看看循環(huán)體的語(yǔ)句吧。從while語(yǔ)句到程序結束的語(yǔ)句
while(posMAX) /*將pos及其后的元素復制到前一單元*/
a[pos-1]=a[pos];
pos++;
num--;
printf("Now,array a:\n");
for(i=0;inum;i++)/*輸出刪除后的數組元素*/
printf("%8d",a[i]);
中,按算法意圖可以看出,while循環(huán)是將pos及其后的元素復制到前一單元,在重復復制過(guò)程中,pos++應該與復制操作(a[pos-1]=a[pos];)同步進(jìn)行,但原程序中卻沒(méi)有用花括號將這兩條語(yǔ)句括起來(lái)構成循環(huán)體,先將發(fā)現的這個(gè)問(wèn)題改正了
while(posMAX) /*將pos及其后的元素覆蓋前一單元*/
a[pos-1]=a[pos];
pos++;
num--;
printf("Now,array a:\n");
for(i=0;inum;i++)/*輸出刪除后的數組元素*/
printf("%8d",a[i]);
運行改正后的程序,結果正常。這樣,程序的錯誤就算排除了。
更多的時(shí)候,情形并非如此,可能不能直接從程序試運行所得結果中了解程序執行流程信息。有些程序在運行時(shí)看不到運行結果,或者程序的輸出結果看不出程序的執行流程。確實(shí)遇到過(guò)這樣的情形,程序運行后沒(méi)有任何輸出,也就不知道程序運行后做了些什么,是否結束運行還是停在什么位置,這與程序設計風(fēng)格有關(guān),有些程序甚至在數據鍵盤(pán)輸入的語(yǔ)句之前,也沒(méi)有相應的提示信息輸出,應該避免這樣的做法。
例4.3 需求描述:老鼠走迷宮(參見(jiàn)例2.8)
算法描述:(參見(jiàn)文獻[6])以二維數組maze表示迷宮,其中元素為0表示走得通、元素為1表示走不通(受阻),路徑只考慮水平和垂直兩個(gè)方向(上下左右)。假設老鼠從maze[1][1]進(jìn)入迷宮,而迷宮的唯一出口在maze[m][n](右下角),任一時(shí)刻老鼠在迷宮中的位置為maze[i][j],它有四個(gè)方向可以試探,設下一個(gè)位置為maze[g][h],顯然,若向右走一步,則g=i,h=j+1;向上走一步,則g=i-1,h=j。但當老鼠走到迷宮邊緣時(shí),不可能再有四個(gè)方向可以試探,因此可以將迷宮數組擴大為maze[m+2][n+2],并令第0、m+1行和第0、n+1列元素均為1(受阻)。那么,老鼠走迷宮的步驟如下:
1) 老鼠走進(jìn)迷宮入口;
2) 在當前位置上從右方開(kāi)始,然后依下、左、上的順序探測前進(jìn)方向;
3) 向可以進(jìn)入的位置前進(jìn),即該位置的maze和mark(標志數組)的對應元素均為0。前進(jìn)一步后,將原來(lái)位置改為當前位置,將標志數組mark的相應元素標志為1(已走過(guò)),并將前一位置坐標及進(jìn)入當前位置的方向入棧;
4) 重復步驟2和3;
5) 若找不到前進(jìn)通路,從原路后退一步(退棧),改變探測方向,再重復步驟2和3,以尋找另一條新的通路。
6) 重復步驟2-5,直到走出迷宮或宣布無(wú)通路為止。
程序:例2.8改正了語(yǔ)法錯誤后的函數mazepath()如下
/*ch2_ex8.c*/
void mazepath(int move[2][4],int m,int n)
int i,j,d,g,h;/*d記錄方向, i,j 為移動(dòng)前坐標,g,h為新坐標*/
mark[1][1]=1;
top=0;
i=1;
j=1;
d=0;
do
g=i+move[0][d];
h=j+move[1][d]; /*try*/
if((maze[g][h]==0)(mark[g][h]==0))
mark[g][h]=1; /*Enter new pos*/
top++;
stack[top].i=i; /*old pos push*/
stack[top].j=j;
stack[top].d=d;
i=g;
j=h;
d=0;
else
if(d3) d=d+1;
else
if(top0)
{ i=stack[top].i;
j=stack[top].j;
d=stack[top].d;
top--;
} /*try again*/
else
printf("No path has been found\n");
} while((g==m)(h==n));/*do-while*/
printf("Out maze:g=%d,h=%d\n",g,h);
在例2.8中省略的函數init_array()和disp_path()如下:
void init_array(int a[MAX_SIZE][MAX_SIZE],int m,int n,int fac)
{ /*將數組元素賦初值fac*/
int i,j;
for(i=0;i=m+1;i++)
for(j=0;j=n+1;j++)
a[i][j]=fac;
void disp_path()
{ /*輸出走出迷宮的路徑*/
int k;
for(k=0;k=top;k++)
printf("row=%d,col=%d\n",stack[k].i,stack[k].j);
程序運行情況:
用如圖4.2所示的迷宮布局數據,運行該程序。
運行程序并輸入圖4.2b)的迷宮矩陣,結果輸出:
Out maze
row=0,col=0
row=1,col=1
該結果顯然有問(wèn)題,因為,既然老鼠走出迷宮了,應該顯示出完整的路徑,但這里僅顯示老鼠到達位置(1,1),路徑是不完整的。
調試:
首先用數據透視法查看數組mark和maze中的數據是否正確,即在main函數調用新增的如下函數:
Void disp_array(int a[MAX_SIZE][MAX_SIZE],int m,int n)
int i,j;
printf("The array:\n");
for(i=0;i=row+1;i++)
for(j=0;j=col+1;j++)
printf("%3d",mark[i][j]);
printf("\n");
將數組maze和mark的元素輸出顯示后,發(fā)現數組中的數據是正確的。這樣,把調試重點(diǎn)放到函數mazepath中。該函數主要部分是do循環(huán),也是老鼠走迷宮算法的主體。不妨用流程觀(guān)察分析法來(lái)調試該函數。為此,在mazepath函數的do循環(huán)中增加語(yǔ)句
printf("i=%d\n",i);
且作為do循環(huán)體的第一句。再運行該程序,顯示結果為:
i=1
Out maze
row=0,col=0
row=1,col=1
從顯示結果可以看出,do循環(huán)僅僅執行了1(i=1)次。分析該循環(huán)控制條件,根據算法描述,只有當老鼠到達最右下角 (m,n)時(shí),才能走出迷宮,也就是說(shuō),當(g==m)(h==m)時(shí)應結束循環(huán),而繼續循環(huán)的條件應是上述退出循環(huán)條件的非,即(gm)||(hn),但程序中循環(huán)體末的}后的循環(huán)條件是:
while((g==m)(h==n));/*do-while*/
當程序初始進(jìn)入循環(huán)后g、h的取值只可能是0、1、2,所以執行1次就退出循環(huán)了。顯然,這個(gè)條件的表述搞反了。其次,分析if結構,發(fā)現
if(top0)
{ i=stack[top].i;
j=stack[top].j;
d=stack[top].d;
top--;
else
printf("No path has been found\n");
的else分支在輸出了"No path has been found"信息、報告沒(méi)有通路后,沒(méi)有及時(shí)退出該函數,這將在迷宮無(wú)通路時(shí)導致死循環(huán)。因此,將其改正如下
if(top0)
{ i=stack[top].i;
j=stack[top].j;
d=stack[top].d;
top--;
} /*try again*/
else
printf("No path has been found\n");
return;
} while((gm)||(hn));/*do-while*/
去掉剛剛插入do循環(huán)體的語(yǔ)句Printf("i=%d\n",i),再試運行程序。顯示如下
Out maze
row=0,col=0
row=1,col=1
row=1,col=2
row=2,col=2
row=2,col=3
row=3,col=3
這個(gè)結果又出現了新的問(wèn)題,按算法描述,數組maze的四周為受阻(不可走),怎么會(huì )輸出0行0列坐標,又出口應該是(3,4)而堆棧中保存的路徑卻沒(méi)有該坐標呢?為了找出原因,在if((maze[g][h]==0)(mark[g][h]==0))分支復合語(yǔ)句中,增加如下輸出語(yǔ)句:
if((g==m)||(h==n)) printf("(%d,%d)\n",g,h);
作為該復合語(yǔ)句中的第一條語(yǔ)句,以查看老鼠是否曾到達坐標(3,4)。運行結果顯示,老鼠到達了出口位置(3,4)。那么,為什么堆棧中沒(méi)有記錄呢?分析函數mazepath代碼,其4行將top賦初值0,do循環(huán)的if分支中第一個(gè)坐標入棧前已將top++。這樣,使得stack棧底(0)元素不是老鼠曾經(jīng)所在的位置;同時(shí),老鼠走出迷宮時(shí)的最后坐標(3,4)也不在棧所保存的路徑中,因為從程序流程發(fā)現,其總是將老鼠的上一次所在位置入棧,而當老鼠到達出口時(shí)的位置卻沒(méi)有入棧。正確的做法應該是,首先將入口位置壓入棧底,而后每前進(jìn)一步就將當前位置入棧。依據上述分析,應將mazepath函數修改為:
void mazepath(int move[2][4],int m,int n)
int i,j,d,g,h;
mark[1][1]=1;
top=0;
i=1;
j=1;
d=0;
stack[top].i=i; /*迷宮入口位置入棧*/
stack[top].j=j;
stack[top].d=d;
do
g=i+move[0][d];
h=j+move[1][d];
if((maze[g][h]==0)(mark[g][h]==0))
mark[g][h]=1; /*Enter new pos*/
top++;
stack[top].i=g; /*old pos push*/
stack[top].j=h;
stack[top].d=d;
i=g;
j=h;
d=0;
else {
if(d3) d=d+1;
else
if(top0)
{ i=stack[top].i;
j=stack[top].j;
d=stack[top].d;
top--;
} /*try again*/
else
printf("No path has been found\n");
return;
}while((gm)||(hn));/*do-while*/
printf("Out maze\n");
再運行程序,顯示結果正確。費了這么多工夫,終于把它調試正確了,聽(tīng)聽(tīng)音樂(lè )輕松一下吧。
好了,輕松之后,我們來(lái)討論一下迷宮布局的一般情形,如果迷宮的入口不是本例算法描述所指定的(1,1)、以及出口也不是位置(m,n),那么只需要簡(jiǎn)單地修改mazepath函數中的i、j初始化語(yǔ)句,以及do循環(huán)末尾的while()條件表達式,即可使程序具有通用性。
通常情況下,流程觀(guān)察分析法可以幫助我們了解程序執行的流程,通過(guò)分析程序的執行流程,可以發(fā)現程序在實(shí)現算法時(shí)存在的一些差錯,從而加以改正。對于算法流程復雜、規模較大的程序調試,該法是有效的方法。