1. 在VSCode下编译运行lab5-1
编译代码
ERROR原因:menu.c中缺少了string类库,无法使用strcmp
解决ERROR:menu.c中include <string.h>
导入类库后,再进行编译
2. 通过VSCode+GDB调试程序找出quit命令无法运行的bug产生的原因
使用help打印出所有的command,可以看出只有
help
和quit
,挨个测试功能后发现quit
不能按照期望输出
step-2.1 排查错误原因(手工调试和使用GDB工具)
step-2.2 手工法排查:
排查错误过程:
可以看到,输出This is a wrong cmd!是因为p是空指针,原因是FindCmd函数返回为空,查看FindCmd函数内容:
FindCmd函数只有一个尾调用,执行SearchLinkTableNode(head,SearchCondition)函数,SearchLinkTableNode代码如下:
可以看到,Conditon函数是一个回调函数。详解参考这篇文章。SearchLinkTableNode是call-in方式函数,其中有一个函数作为参数,这个作为参数的函数就是callback函数,即代码中Conditon函数。回调函数的实现方式及优点见文末。
可以看到,SearchLinkTableNode为空的可能有:
1.链表为空
2.链表遍历完成后没有找到对应的command node
由于help命令是可以正常运行的,所以只可能是quit对应的node没有正常被遍历,通过下图对添加cmmand节点的过程进行分析出
quit node是添加在链表结尾的,每次添加新node时会把pNext置null,而SearchLinkTableNode函数中遍历链表时的条件为pNode != pLinkTable->pTail
,显然没有判断到最后一个节点。所以,只需要把判断条件改为pNode != null
后,重新编译运行,即可得到正确结果。
step-2.3 使用GDB工具排查(过程已通过注释列出):
xxx@xxx-virtual-machine:~/桌面/lab5-1/lab5.1$ gdb -q se2020
Reading symbols from se2020...done.
(gdb) list //列出menu.c中的内容
89 pNode->cmd = "quit";
90 pNode->desc = "Quit from Menu Program V1.0";
91 pNode->handler = Quit;
92 AddLinkTableNode(*ppLinktable,(tLinkTableNode *)pNode);
93
94 return 0;
95 }
96
97 /* menu program */
98
(gdb)
99 tLinkTable * head = NULL;
100
101 int main()
102 {
103 InitMenuData(&head);
104 /* cmd line begins */
105 while(1)
106 {
107 printf("Input a cmd number > ");
108 scanf("%s", cmd);
(gdb)
109 tDataNode *p = FindCmd(head, cmd);
110 if( p == NULL)
111 {
112 printf("This is a wrong cmd!
");
113 continue;
114 }
115 printf("%s - %s
", p->cmd, p->desc);
116 if(p->handler != NULL)
117 {
118 p->handler();
(gdb) break 109 //对109行的FindCmd函数加断点
Breakpoint 1 at 0x400dea: file menu.c, line 109.
(gdb) run //打完断点运行程序
Starting program: /home/dfx/桌面/lab5-1/lab5.1/se2020
Input a cmd number > quit //输入quit指令
Breakpoint 1, main () at menu.c:109
109 tDataNode *p = FindCmd(head, cmd);
(gdb) s //使用s进入FindCmd函数
FindCmd (head=0x603010, cmd=0x6020a0 <cmd> "quit") at menu.c:60
60 return (tDataNode*)SearchLinkTableNode(head,SearchCondition);
(gdb) s //使用s进入SearchLinkTableNode函数
SearchLinkTableNode (pLinkTable=0x603010, Conditon=0x400bf3 <SearchCondition>)
at linktable.c:144
144 if(pLinkTable == NULL || Conditon == NULL)
(gdb) n //n为单行调式
148 tLinkTableNode * pNode = pLinkTable->pHead;
(gdb) n
149 while(pNode != pLinkTable->pTail)
(gdb) n
151 if(Conditon(pNode) == SUCCESS)
(gdb) n
155 pNode = pNode->pNext;
(gdb) n
149 while(pNode != pLinkTable->pTail)
(gdb) n
151 if(Conditon(pNode) == SUCCESS)
(gdb) n
155 pNode = pNode->pNext;
(gdb) n
149 while(pNode != pLinkTable->pTail)
(gdb) n //第三次while循环执行结束,由手工法也可以知道,初始化一共有三个节点,所以quit node没有被遍历
157 return NULL;
(gdb) n
158 }
(gdb) n
FindCmd (head=0x603010, cmd=0x6020a0 <cmd> "quit") at menu.c:61 //FindCmd返回
61 }
(gdb) n
main () at menu.c:110
110 if( p == NULL) //判断的quit cmd为null
(gdb) n
112 printf("This is a wrong cmd!
");
(gdb) n
This is a wrong cmd!
113 continue;
(gdb)
3. 分析callback接口的运行机制,总结callback接口设计的方法
Definition:回调函数是异步执行或稍后执行的函数。其是通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数
Example:有一家旅馆提供叫醒服务,但是要求旅客自己决定叫醒的方法。可以是打客房电话,也可以是派服务员去敲门,睡得死怕耽误事的,还可以要求往自己头上浇盆水。这里,“叫醒”这个行为是旅馆提供的,相当于库函数,但是叫醒的方式是由旅客决定并告诉旅馆的,也就是回调函数。
Advantage:回调机制提供了非常大的灵活性,在回调中,我们利用某种方式,把回调函数像参数一样传入中间函数。可以这么理解,在传入一个回调函数之前,中间函数是不完整的。换句话说,程序可以在运行时,通过登记不同的回调函数,来决定、改变中间函数的行为。
设计方法:回调机制是一种常见的设计模型,他把工作流内的某个功能,按照约定的接口暴露给外部使用者,为外部使用者提供数据,或要求外部使用者提供数据。比如java回调机制:
软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。
- 同步调用:一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;
- 回 调:一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;
- 异步调用:一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。
参考自: