本次实验任务:
-
在VSCode下编译运行lab5-1.tar.gz 即http://pan.baidu.com/s/1pJ0qAIv
-
通过VSCode+GDB调试程序找出quit命令无法运行的bug产生的原因
- 分析callback接口的运行机制,总结callback接口设计的方法
实验环境:
- Windows10+VS code+MinGW64
一、实验准备
在这里可以下载相应平台的VS code并进行安装,用VS code打开从实验所用的c代码文件夹,发现无法识别头文件
此时需要下载mingw-w64,mingw-w64用来对C/C++程序进行编译,以生成可执行文件,配合VS code进行代码的编写以及调试执行。
你可以在这里进行mingw-w64的离线下载,为了节省篇幅,具体的安装过程可以参考这篇博客。
二、编译执行
安装好后重启VS code,发现代码中头文件问题已经消失了,此时可以打开一个终端,执行gcc -o test menu.c linktable.c命令编译得到exe文件,编译发现menu.c文件中缺少string.h头文件
添加头文件后重新执行gcc -o test menu.c linktable.c,得到test.exe,终端输入./test,执行,发现程序成功运行
继续运行quit命令,发现程序不能正常退出
三、调试
回到代码中,在menu.c文件中找到main函数,发现当指针p==NULL时,控制台输出This is a wrong cmd,代码如下:
1 while(1) 2 { 3 printf("Input a cmd number > "); 4 scanf("%s", cmd); 5 tDataNode *p = FindCmd(head, cmd); 6 if( p == NULL) 7 { 8 printf("This is a wrong cmd!\n "); 9 continue; 10 } 11 printf("%s - %s\n", p->cmd, p->desc); 12 if(p->handler != NULL) 13 { 14 p->handler(); 15 } 16 17 }
使用gdb调试,注意要想使用gdb调试,需要删除原来的test.exe文件,重新编译文件,加上-g参数,即gcc -o test menu.c linktable.c -g。随后修改launch.json文件,主要修改这两处
F5启动断点调试,监视p的值,发现输入quit后,p值返回为0x0,进入if判断,返回This is a wrong cmd!
指针p的值来自FindCmd()函数的返回值,定位到FindCmd()函数
1 tDataNode* FindCmd(tLinkTable * head, char * cmd) 2 { 3 return (tDataNode*)SearchLinkTableNode(head,SearchCondition); 4 }
函数体内调用了SearchLinkTableNode()函数,定位到SearchLinkTableNode()函数
1 tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode)) 2 { 3 if(pLinkTable == NULL || Conditon == NULL) 4 { 5 return NULL; 6 } 7 tLinkTableNode * pNode = pLinkTable->pHead; 8 while(pNode != pLinkTable->pTail) 9 { 10 if(Conditon(pNode) == SUCCESS) 11 { 12 return pNode; 13 } 14 pNode = pNode->pNext; 15 } 16 return NULL; 17 }
在这里输入quit时返回的会是NULL,分析代码,可以看到while的判断条件是pNode != pLinkTable->pTail,查询p;InkTable结构体定义及引用,在如下红色代码处可知,pTail是链表的尾
1 int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode) 2 { 3 if(pLinkTable == NULL || pNode == NULL) 4 { 5 return FAILURE; 6 } 7 pNode->pNext = NULL; 8 pthread_mutex_lock(&(pLinkTable->mutex)); 9 if(pLinkTable->pHead == NULL) 10 { 11 pLinkTable->pHead = pNode; 12 } 13 if(pLinkTable->pTail == NULL) 14 { 15 pLinkTable->pTail = pNode; 16 } 17 else 18 { 19 pLinkTable->pTail->pNext = pNode; 20 pLinkTable->pTail = pNode; 21 } 22 pLinkTable->SumOfNode += 1 ; 23 pthread_mutex_unlock(&(pLinkTable->mutex)); 24 return SUCCESS; 25 }
于是可知,quit不能正常执行是因为这里while的判断条件有问题,链表的最后一个节点没有得到判断,修改while循环停止的条件为pNode !=NULL,此时即可做到判断所有节点,删除test.exe文件,重新编译,调试运行,发现quit命令工作正常,不再进入判断
四、回调函数分析
本次实验代码中使用了回调函数,即tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode))函数,什么是回调函数,知乎作者常溪玲是这样解说的:
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。
通过将函数指针作为另一个函数的参数而成为回调函数,回调函数就是通过函数指针调用的函数,本次代码中调用conditon应为回调函数,如果参数写为int(*Conditon)(tLinkTableNode * pNode))应当更加准确。
通过callback函数,实现了程序的异步,在程序运行至需要函数值处时进行函数调用,拿到返回值。
使用回调函数还有根据需要调用不同函数的好处,因为是函数指针作为函数形参,所以可以接受各种函数作为实参,只要被调用函数的参数符合即可,从而达到更大的灵活性。
而之所以叫做回调函数,是因为在代码中,SearchLinkTableNode()函数调用了Conditon()函数,而Conditon()函数用调用了SearchLinkTableNode()给它的参数,它们相互调用,这就是回调!